1/*
2 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23#include <CoreFoundation/CFBundlePriv.h>
24
25#include <IOKit/kext/OSKext.h>
26#include <IOKit/kext/OSKextPrivate.h>
27#include <IOKit/kext/fat_util.h>
28#include <IOKit/kext/macho_util.h>
29
30#include "kextfind_commands.h"
31#include "kextfind_main.h"
32#include "kextfind_query.h"
33
34/*******************************************************************************
35*
36*******************************************************************************/
37CFStringRef copyPathForKext(
38    OSKextRef theKext,
39    PathSpec  pathSpec)
40{
41    CFStringRef result          = CFSTR("(can't determine kext path)");
42
43    CFURLRef    kextURL         = OSKextGetURL(theKext);  // do not release
44    CFURLRef    absURL          = NULL;  // must release
45    OSKextRef   containerKext   = NULL;  // must release
46    CFURLRef    containerURL    = NULL;  // do not release
47    CFURLRef    containerAbsURL = NULL;  // must release
48    CFURLRef    repositoryURL   = NULL;  // must release
49    CFStringRef repositoryPath  = NULL;  // must release
50    CFStringRef kextPath        = NULL;  // must release
51
52
53    if (!kextURL) {
54        OSKextLog(theKext,
55            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
56            "Kext has no URL!");
57        goto finish;
58    }
59
60    if (pathSpec == kPathsNone) {
61        result = CFURLCopyLastPathComponent(kextURL);
62    } else if (pathSpec == kPathsFull) {
63        absURL = CFURLCopyAbsoluteURL(kextURL);
64        if (!absURL) {
65            OSKextLogMemError();
66            goto finish;
67        }
68        result = CFURLCopyFileSystemPath(absURL, kCFURLPOSIXPathStyle);
69    } else if (pathSpec == kPathsRelative) {
70        CFRange relativeRange;
71        absURL = CFURLCopyAbsoluteURL(kextURL);
72        if (!absURL) {
73            OSKextLogMemError();
74            goto finish;
75        }
76
77        containerKext = OSKextCopyContainerForPluginKext(theKext);
78        if (containerKext) {
79            containerURL = OSKextGetURL(containerKext);
80            if (!containerURL) {
81                OSKextLog(containerKext,
82                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
83                    "Container kext has no URL!");
84                goto finish;
85            }
86            containerAbsURL = CFURLCopyAbsoluteURL(containerURL);
87            if (!containerAbsURL) {
88                OSKextLogMemError();
89                goto finish;
90            }
91            repositoryURL = CFURLCreateCopyDeletingLastPathComponent(
92                kCFAllocatorDefault, containerAbsURL);
93            if (!repositoryURL) {
94                OSKextLogMemError();
95                goto finish;
96            }
97        } else {
98            repositoryURL = CFURLCreateCopyDeletingLastPathComponent(
99                kCFAllocatorDefault, absURL);
100            if (!repositoryURL) {
101                OSKextLogMemError();
102                goto finish;
103            }
104        }
105
106        repositoryPath = CFURLCopyFileSystemPath(repositoryURL, kCFURLPOSIXPathStyle);
107        kextPath = CFURLCopyFileSystemPath(absURL, kCFURLPOSIXPathStyle);
108        if (!repositoryPath || !kextPath) {
109            OSKextLogMemError();
110            goto finish;
111        }
112
113       /* We add 1 to the length of the repositoryPath to handle the
114        * intermediate '/' character.
115        */
116        relativeRange = CFRangeMake(1+CFStringGetLength(repositoryPath),
117            CFStringGetLength(kextPath) - (1+CFStringGetLength(repositoryPath)));
118        result = CFStringCreateWithSubstring(kCFAllocatorDefault,
119            kextPath, relativeRange);
120    } else {
121        OSKextLog(/* kext */ NULL,
122            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
123            "Internal error.");
124        goto finish;
125    }
126
127finish:
128    SAFE_RELEASE(absURL);
129    SAFE_RELEASE(containerKext);
130    SAFE_RELEASE(containerAbsURL);
131    SAFE_RELEASE(repositoryURL);
132    SAFE_RELEASE(repositoryPath);
133    SAFE_RELEASE(kextPath);
134
135    return result;
136}
137
138/*******************************************************************************
139*
140*******************************************************************************/
141void printKext(
142    OSKextRef theKext,
143    PathSpec pathSpec,
144    Boolean extra_info,
145    char lineEnd)
146{
147    CFStringRef   bundleID      = NULL;  // do NOT release
148    CFStringRef   bundleVersion = NULL;  // do NOT release
149
150    CFStringRef   kextPath      = NULL;  // must release
151    char        * kext_path     = NULL;  // must free
152    char        * bundle_id     = NULL;  // must free
153    char        * bundle_version = NULL;  // must free
154
155    kextPath = copyPathForKext(theKext, pathSpec);
156    if (!kextPath) {
157        OSKextLogMemError();
158        goto finish;
159    }
160
161    kext_path = createUTF8CStringForCFString(kextPath);
162    if (!kext_path) {
163        OSKextLogMemError();
164        goto finish;
165    }
166
167    if (extra_info) {
168        bundleID = OSKextGetIdentifier(theKext);
169        bundleVersion = OSKextGetValueForInfoDictionaryKey(theKext,
170            kCFBundleVersionKey);
171
172        bundle_id = createUTF8CStringForCFString(bundleID);
173        bundle_version = createUTF8CStringForCFString(bundleVersion);
174        if (!bundle_id || !bundle_version) {
175            OSKextLogMemError();
176            goto finish;
177        }
178
179        fprintf(stdout, "%s\t%s\t%s%c", kext_path, bundle_id,
180            bundle_version, lineEnd);
181    } else {
182        fprintf(stdout, "%s%c", kext_path, lineEnd);
183    }
184
185finish:
186    SAFE_RELEASE(kextPath);
187    SAFE_FREE(kext_path);
188    SAFE_FREE(bundle_id);
189    SAFE_FREE(bundle_version);
190
191    return;
192}
193
194/*******************************************************************************
195*
196*******************************************************************************/
197#define kMaxPrintableCFDataLength    (36)
198#define kByteGroupSize                (4)
199
200char * stringForData(const UInt8 * data, int numBytes)
201{
202    char * result = NULL;
203    char * scan = NULL;
204    int numByteGroups;
205    int i;
206    char digits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
207        '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
208
209   /* Put a space between every 2 bytes (4 chars).
210    */
211    numByteGroups = numBytes / kByteGroupSize;
212
213    result = malloc(((numBytes * 2) + numByteGroups + 1) * sizeof(char));
214    if (!result) {
215        goto finish;
216    }
217
218    scan = result;
219
220    for (i = 0; i < numBytes; i++) {
221        int binaryDigit1 = (data[i] & 0xF0) >> 4;
222        int binaryDigit2 = data[i] & 0xF;
223        if (i > 0 && i % kByteGroupSize == 0) {
224            *scan++ = ' ';
225        }
226        *scan++ = digits[binaryDigit1];
227        *scan++ = digits[binaryDigit2];
228    }
229    *scan++ = '\0';
230#pragma unused(scan)
231
232finish:
233    return result;
234}
235
236/*******************************************************************************
237*
238*******************************************************************************/
239void printProperty(
240    CFStringRef label,
241    CFStringRef propKey,
242    CFTypeRef value,
243    char lineEnd)
244{
245    CFTypeID type = CFGetTypeID(value);
246    CFStringRef propString = NULL;     // must release
247    CFStringRef labeledString = NULL;  // must release
248    CFStringRef outputString = NULL;   // do not release
249    char * allocString = NULL;         // must free
250    char * dataString = NULL;  // must free
251
252    if (type == CFStringGetTypeID()) {
253        propString = CFStringCreateWithFormat(
254            kCFAllocatorDefault, NULL, CFSTR("%@ = \"%@\"%c"),
255            propKey, value, lineEnd);
256    } else if (type == CFNumberGetTypeID()) {
257        propString = CFStringCreateWithFormat(
258            kCFAllocatorDefault, NULL, CFSTR("%@ = %@%c"),
259            propKey, value, lineEnd);
260    } else if (type == CFBooleanGetTypeID()) {
261        propString = CFStringCreateWithFormat(
262            kCFAllocatorDefault, NULL, CFSTR("%@ = %@%c"),
263            propKey, value, lineEnd);
264    } else if (type == CFDataGetTypeID()) {
265        CFIndex length = 0;
266        length = CFDataGetLength(value);
267        const UInt8 * data = CFDataGetBytePtr(value);
268        if (!data) {
269            propString = CFStringCreateWithFormat(
270                kCFAllocatorDefault, NULL, CFSTR("%@[%zd] = <null data pointer>%c"),
271                propKey, length, lineEnd);
272        } else {
273            int numBytes = (int)MIN(length, kMaxPrintableCFDataLength);
274            dataString = stringForData(data, MIN(numBytes, kMaxPrintableCFDataLength));
275            if (length > kMaxPrintableCFDataLength) {
276                propString = CFStringCreateWithFormat(
277                    kCFAllocatorDefault, NULL,
278                    CFSTR("%@ = <data (%zd bytes): %s...>%c"),
279                    propKey, length, dataString, lineEnd);
280            } else {
281                propString = CFStringCreateWithFormat(
282                    kCFAllocatorDefault, NULL,
283                    CFSTR("%@ = <data (%zd bytes): %s>%c"),
284                    propKey, length, dataString, lineEnd);
285            }
286        }
287    } else if (type == CFDictionaryGetTypeID()) {
288        propString = CFStringCreateWithFormat(
289            kCFAllocatorDefault, NULL, CFSTR("%@ = <dictionary of %zd items>%c"),
290            propKey, CFDictionaryGetCount(value), lineEnd);
291    } else if (type == CFArrayGetTypeID()) {
292        propString = CFStringCreateWithFormat(
293            kCFAllocatorDefault, NULL, CFSTR("%@ = <array of %zd items>%c"),
294            propKey, CFArrayGetCount(value), lineEnd);
295    } else {
296        propString = CFStringCreateWithFormat(
297            kCFAllocatorDefault, NULL, CFSTR("%@ = <value of unknown type>%c"),
298            propKey, lineEnd);
299    }
300
301    if (!propString) {
302        goto finish;
303    }
304
305    if (label) {
306        labeledString = CFStringCreateWithFormat(
307            kCFAllocatorDefault, NULL, CFSTR("%@: %@"), label, propString);
308        outputString = labeledString;
309    } else {
310        labeledString = CFStringCreateWithFormat(
311            kCFAllocatorDefault, NULL, CFSTR("%@"), propString);
312        outputString = labeledString;
313    }
314
315    if (!outputString) {
316        goto finish;
317    }
318
319    allocString = createUTF8CStringForCFString(outputString);
320    if (!allocString) {
321        goto finish;
322    }
323
324    fprintf(stdout, "%s", allocString);
325
326finish:
327    if (propString)     CFRelease(propString);
328    if (labeledString)  CFRelease(labeledString);
329    if (allocString)    free(allocString);
330    if (dataString)     free(dataString);
331    return;
332}
333
334/*******************************************************************************
335*
336*******************************************************************************/
337void printKextProperty(
338    OSKextRef theKext,
339    CFStringRef propKey,
340    char lineEnd)
341{
342    CFTypeRef       value = NULL;
343
344    value = OSKextGetValueForInfoDictionaryKey(theKext, propKey);
345    if (value) {
346        printProperty(NULL, propKey, value, lineEnd);
347    }
348
349    return;
350}
351
352/*******************************************************************************
353*
354*******************************************************************************/
355void printKextMatchProperty(
356    OSKextRef theKext,
357    CFStringRef propKey,
358    char lineEnd)
359{
360    CFDictionaryRef personalitiesDict = NULL;
361    CFStringRef * names = NULL;
362    CFDictionaryRef * personalities = NULL;
363    CFIndex numPersonalities;
364    CFIndex i;
365
366    personalitiesDict = OSKextGetValueForInfoDictionaryKey(theKext,
367        CFSTR(kIOKitPersonalitiesKey));
368    if (!personalitiesDict) {
369        goto finish;
370    }
371
372    numPersonalities = CFDictionaryGetCount(personalitiesDict);
373    if (!numPersonalities) {
374        goto finish;
375    }
376
377    names = malloc(numPersonalities * sizeof(CFStringRef));
378    personalities = malloc(numPersonalities * sizeof(CFDictionaryRef));
379    if (!names || !personalities) {
380        goto finish;
381    }
382
383    CFDictionaryGetKeysAndValues(personalitiesDict, (const void **)names,
384        (const void **)personalities);
385
386    for (i = 0; i < numPersonalities; i++) {
387        CFTypeRef value = CFDictionaryGetValue(personalities[i], propKey);
388        if (value) {
389            printProperty(names[i], propKey, value, lineEnd);
390        }
391    }
392
393finish:
394    if (names)             free(names);
395    if (personalities)     free(personalities);
396
397    return;
398}
399
400/*******************************************************************************
401*
402*******************************************************************************/
403void printKextArches(
404    OSKextRef theKext,
405    char lineEnd,
406    Boolean printLineEnd)
407{
408    fat_iterator fiter = NULL;
409    struct mach_header * farch = NULL;
410    const NXArchInfo * archinfo = NULL;
411    Boolean printedOne = false;
412
413    fiter = createFatIteratorForKext(theKext);
414    if (!fiter) {
415        goto finish;
416    }
417
418    while ((farch = fat_iterator_next_arch(fiter, NULL))) {
419        int swap = ISSWAPPEDMACHO(farch->magic);
420        archinfo = NXGetArchInfoFromCpuType(CondSwapInt32(swap, farch->cputype),
421            CondSwapInt32(swap, farch->cpusubtype));
422        if (archinfo) {
423            fprintf(stdout, "%s%s", printedOne ? "," : "", archinfo->name);
424            printedOne = true;
425        }
426    }
427
428finish:
429    if (printLineEnd && printedOne) {
430        fprintf(stdout, "%c", lineEnd);
431    }
432    if (fiter)  fat_iterator_close(fiter);
433
434    return;
435}
436
437/*******************************************************************************
438*
439*******************************************************************************/
440void printKextDependencies(
441    OSKextRef theKext,
442    PathSpec pathSpec,
443    Boolean extra_info,
444    char lineEnd)
445{
446    CFArrayRef kextDependencies = OSKextCopyAllDependencies(theKext,
447        /* needAll? */ false);
448    CFIndex count, i;
449
450    if (!kextDependencies) {
451        goto finish;
452    }
453
454    count = CFArrayGetCount(kextDependencies);
455    for (i = 0; i < count; i++) {
456        OSKextRef thisKext = (OSKextRef)CFArrayGetValueAtIndex(kextDependencies, i);
457        printKext(thisKext, pathSpec, extra_info, lineEnd);
458    }
459
460finish:
461    if (kextDependencies) CFRelease(kextDependencies);
462    return;
463}
464
465/*******************************************************************************
466*
467*******************************************************************************/
468void printKextDependents(
469    OSKextRef theKext,
470    PathSpec pathSpec,
471    Boolean extra_info,
472    char lineEnd)
473{
474    CFArrayRef kextDependents = OSKextCopyDependents(theKext,
475        /* direct? */ false);
476    CFIndex count, i;
477
478    if (!kextDependents) {
479        goto finish;
480    }
481
482    count = CFArrayGetCount(kextDependents);
483    for (i = 0; i < count; i++) {
484        OSKextRef thisKext = (OSKextRef)CFArrayGetValueAtIndex(kextDependents, i);
485        printKext(thisKext, pathSpec, extra_info, lineEnd);
486    }
487
488finish:
489    if (kextDependents) CFRelease(kextDependents);
490    return;
491}
492
493/*******************************************************************************
494*
495*******************************************************************************/
496void printKextPlugins(
497    OSKextRef theKext,
498    PathSpec pathSpec,
499    Boolean extra_info,
500    char lineEnd)
501{
502    CFArrayRef plugins = OSKextCopyPlugins(theKext);  // must release
503    CFIndex count, i;
504
505    if (!plugins) {
506        goto finish;
507    }
508
509    count = CFArrayGetCount(plugins);
510    for (i = 0; i < count; i++) {
511        OSKextRef thisKext = (OSKextRef)CFArrayGetValueAtIndex(plugins, i);
512        printKext(thisKext, pathSpec, extra_info, lineEnd);
513    }
514
515finish:
516    SAFE_RELEASE(plugins);
517    return;
518}
519
520/*******************************************************************************
521* copyAdjustedPathForURL()
522*
523* This function takes an URL with a given kext, and adjusts it to be absolute
524* or relative to the kext's containing repository, properly handling plugin
525* kexts to include the repository-path of the containing kext as well.
526*******************************************************************************/
527CFStringRef copyAdjustedPathForURL(
528    OSKextRef theKext,
529    CFURLRef  urlToAdjust,
530    PathSpec  pathSpec)
531{
532    CFStringRef result       = NULL;
533    CFURLRef    absURL       = NULL;  // must release
534    CFStringRef absPath      = NULL;  // must release
535    CFStringRef kextAbsPath  = NULL;  // must release
536    CFStringRef kextRelPath  = NULL;  // must release
537    CFStringRef pathInKext   = NULL;  // must release
538    CFRange     scratchRange;
539
540    if (pathSpec != kPathsFull && pathSpec != kPathsRelative) {
541        OSKextLog(theKext,
542            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
543            "Invalid argument to copyAdjustedPathForURL().");
544    }
545
546    absURL = CFURLCopyAbsoluteURL(urlToAdjust);
547    if (!absURL) {
548        OSKextLogMemError();
549        goto finish;
550    }
551
552    if (pathSpec == kPathsFull) {
553        result = CFURLCopyFileSystemPath(absURL, kCFURLPOSIXPathStyle);
554        goto finish;
555    }
556
557   /*****
558    * Okay, we are doing repository-relative paths here. Here's how!
559    * We are strip the matching part of the kext's absolute path
560    * from the URL/path handed in, which gives us the path in the kext.
561    * Then we tack that back onto the kext's repository-relative path. Got it?
562    */
563
564    kextAbsPath = copyPathForKext(theKext, kPathsFull);
565    kextRelPath = copyPathForKext(theKext, kPathsRelative);
566    absPath = CFURLCopyFileSystemPath(absURL, kCFURLPOSIXPathStyle);
567    if (!kextAbsPath || !kextRelPath || !absPath) {
568        goto finish;
569    }
570
571    scratchRange = CFRangeMake(CFStringGetLength(kextAbsPath),
572        CFStringGetLength(absPath) - CFStringGetLength(kextAbsPath));
573    pathInKext = CFStringCreateWithSubstring(kCFAllocatorDefault, absPath,
574        scratchRange);
575    if (!pathInKext) {
576        OSKextLogMemError();
577    }
578    result = CFStringCreateWithFormat(kCFAllocatorDefault, /* options */ 0,
579        CFSTR("%@%@"), kextRelPath, pathInKext);
580
581finish:
582    SAFE_RELEASE(absURL);
583    SAFE_RELEASE(absPath);
584    SAFE_RELEASE(kextAbsPath);
585    SAFE_RELEASE(kextRelPath);
586    SAFE_RELEASE(pathInKext);
587    return result;
588}
589
590/*******************************************************************************
591* XXX: I'm really not sure this is completely reliable for getting a relative
592* XXX: path.
593*******************************************************************************/
594CFStringRef copyKextInfoDictionaryPath(
595    OSKextRef theKext,
596    PathSpec  pathSpec)
597{
598    CFStringRef   result      = NULL;
599    CFURLRef      kextURL     = NULL;  // do not release
600    CFURLRef      kextAbsURL  = NULL;  // must release
601    CFBundleRef   kextBundle  = NULL;  // must release
602    CFURLRef      infoDictURL = NULL;  // must release
603
604    kextURL = OSKextGetURL(theKext);
605    if (!kextURL) {
606        OSKextLog(theKext,
607            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
608            "Kext has no URL!");
609        goto finish;
610    }
611    kextAbsURL = CFURLCopyAbsoluteURL(kextURL);
612    if (!kextAbsURL) {
613        OSKextLogMemError();
614        goto finish;
615    }
616
617    kextBundle = CFBundleCreate(kCFAllocatorDefault, kextAbsURL);
618    if (!kextBundle) {
619        OSKextLogMemError();
620        goto finish;
621    }
622    infoDictURL = _CFBundleCopyInfoPlistURL(kextBundle);
623    if (!infoDictURL) {
624        // not able to determine error here, bundle might have no plist
625        // (well, we should never have gotten here if that were the case)
626        result = CFStringCreateWithCString(kCFAllocatorDefault, "",
627            kCFStringEncodingUTF8);
628        goto finish;
629    }
630
631    result = copyAdjustedPathForURL(theKext, infoDictURL, pathSpec);
632
633finish:
634    SAFE_RELEASE(infoDictURL);
635    SAFE_RELEASE(kextBundle);
636    SAFE_RELEASE(kextAbsURL);
637    return result;
638}
639
640/*******************************************************************************
641*
642*******************************************************************************/
643void printKextInfoDictionary(
644    OSKextRef theKext,
645    PathSpec pathSpec,
646    char lineEnd)
647{
648    CFStringRef   infoDictPath = NULL;  // must release
649    char        * infoDictPathCString = NULL;  // must free
650
651    infoDictPath = copyKextInfoDictionaryPath(theKext, pathSpec);
652    if (!infoDictPath) {
653        OSKextLogMemError();
654        goto finish;
655    }
656
657    infoDictPathCString = createUTF8CStringForCFString(infoDictPath);
658    if (!infoDictPathCString) {
659        OSKextLogMemError();
660        goto finish;
661    }
662
663    printf("%s%c", infoDictPathCString, lineEnd);
664
665
666finish:
667    SAFE_FREE(infoDictPathCString);
668    SAFE_RELEASE(infoDictPath);
669    return;
670}
671
672/*******************************************************************************
673* XXX: I'm really not sure this is completely reliable for getting a relative
674* XXX: path.
675*******************************************************************************/
676CFStringRef copyKextExecutablePath(
677    OSKextRef theKext,
678    PathSpec  pathSpec)
679{
680    CFStringRef   result = NULL;
681    CFURLRef      kextURL       = NULL;  // do not release
682    CFURLRef      kextAbsURL    = NULL;  // must release
683    CFURLRef      executableURL = NULL;    // must release
684
685    kextURL = OSKextGetURL(theKext);
686    if (!kextURL) {
687        OSKextLog(theKext,
688            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
689            "Kext has no URL!");
690        goto finish;
691    }
692    kextAbsURL = CFURLCopyAbsoluteURL(kextURL);
693    if (!kextAbsURL) {
694        OSKextLogMemError();
695        goto finish;
696    }
697
698    executableURL = _CFBundleCopyExecutableURLInDirectory(kextAbsURL);
699    if (!executableURL) {
700        // not able to determine error here, bundle might have no executable
701        result = CFStringCreateWithCString(kCFAllocatorDefault, "",
702            kCFStringEncodingUTF8);
703        goto finish;
704    }
705    result = copyAdjustedPathForURL(theKext, executableURL, pathSpec);
706
707finish:
708    SAFE_RELEASE(executableURL);
709    SAFE_RELEASE(kextAbsURL);
710    return result;
711}
712
713/*******************************************************************************
714* XXX: I'm really not sure this is completely reliable for getting a relative
715* XXX: path.
716*******************************************************************************/
717void printKextExecutable(
718    OSKextRef theKext,
719    PathSpec pathSpec,
720    char lineEnd)
721{
722    CFStringRef   executablePath = NULL;  // must release
723    char        * executablePathCString = NULL;  // must free
724
725    executablePath = copyKextExecutablePath(theKext, pathSpec);
726    if (!executablePath) {
727        OSKextLogMemError();
728        goto finish;
729    }
730
731    executablePathCString = createUTF8CStringForCFString(executablePath);
732    if (!executablePathCString) {
733        OSKextLogMemError();
734        goto finish;
735    }
736
737    printf("%s%c", executablePathCString, lineEnd);
738
739
740finish:
741    SAFE_FREE(executablePathCString);
742    SAFE_RELEASE(executablePath);
743    return;
744}
745