1/*
2 * Copyright (c) 2008 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
24#include <TargetConditionals.h>
25#if !TARGET_OS_EMBEDDED
26    #include <bless.h>
27    #include "bootcaches.h"
28#endif  // !TARGET_OS_EMBEDDED
29
30#include <libc.h>
31#include <sysexits.h>
32#include <asl.h>
33#include <syslog.h>
34#include <sys/resource.h>
35#include <IOKit/kext/OSKext.h>
36#include <IOKit/kext/OSKextPrivate.h>
37
38#include "kext_tools_util.h"
39
40#if PRAGMA_MARK
41#pragma mark Basic Utility
42#endif /* PRAGMA_MARK */
43
44/*********************************************************************
45*********************************************************************/
46char * createUTF8CStringForCFString(CFStringRef aString)
47{
48    char     * result = NULL;
49    CFIndex    bufferLength = 0;
50
51    if (!aString) {
52        goto finish;
53    }
54
55    bufferLength = sizeof('\0') +
56        CFStringGetMaximumSizeForEncoding(CFStringGetLength(aString),
57        kCFStringEncodingUTF8);
58
59    result = (char *)malloc(bufferLength * sizeof(char));
60    if (!result) {
61        goto finish;
62    }
63    if (!CFStringGetCString(aString, result, bufferLength,
64        kCFStringEncodingUTF8)) {
65
66        SAFE_FREE_NULL(result);
67        goto finish;
68    }
69
70finish:
71    return result;
72}
73
74/*******************************************************************************
75* createCFMutableArray()
76*******************************************************************************/
77Boolean createCFMutableArray(CFMutableArrayRef * array,
78    const CFArrayCallBacks * callbacks)
79{
80    Boolean result = true;
81
82    *array = CFArrayCreateMutable(kCFAllocatorDefault, 0,
83        callbacks);
84    if (!*array) {
85        result = false;
86    }
87    return result;
88}
89
90/*******************************************************************************
91* createCFMutableDictionary()
92*******************************************************************************/
93Boolean createCFMutableDictionary(CFMutableDictionaryRef * dict)
94{
95    Boolean result = true;
96
97    *dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
98        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
99    if (!*dict) {
100        result = false;
101    }
102    return result;
103}
104
105/*******************************************************************************
106* createCFMutableSet()
107*******************************************************************************/
108Boolean createCFMutableSet(CFMutableSetRef * setOut,
109    const CFSetCallBacks * callbacks)
110{
111    Boolean result = true;
112
113    *setOut = CFSetCreateMutable(kCFAllocatorDefault, 0,
114        callbacks);
115    if (!*setOut) {
116        result = false;
117    }
118    return result;
119}
120
121/*******************************************************************************
122*******************************************************************************/
123void addToArrayIfAbsent(CFMutableArrayRef array, const void * value)
124{
125    if (kCFNotFound == CFArrayGetFirstIndexOfValue(array, RANGE_ALL(array),
126        value)) {
127
128        CFArrayAppendValue(array, value);
129    }
130    return;
131}
132
133/*******************************************************************************
134 * createCFDataFromFile()
135 *******************************************************************************/
136Boolean createCFDataFromFile(CFDataRef  *dataRefOut,
137                             const char *filePath)
138{
139    int             fd = -1;
140    Boolean         result = false;
141    struct stat     statBuf;
142    void            *buffer;
143    CFIndex         length;
144
145    *dataRefOut = NULL;
146    fd = open(filePath, O_RDONLY, 0);
147    if (fd < 0) {
148        goto finish;
149    }
150    if (fstat(fd, &statBuf) != 0) {
151        goto finish;
152    }
153    if ((statBuf.st_mode & S_IFMT) != S_IFREG) {
154        goto finish;
155    }
156    if (statBuf.st_size == 0) {
157        goto finish;
158    }
159
160    // fill buffer used for CFData passed to caller
161    length = (CFIndex) statBuf.st_size;
162    buffer = CFAllocatorAllocate(kCFAllocatorDefault, length, 0);
163    if (read(fd, buffer, length) < 0) {
164        goto finish;
165    }
166
167    *dataRefOut = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
168                                (const UInt8 *)buffer,
169                                length,
170                                kCFAllocatorDefault);
171    if (*dataRefOut == NULL) {
172        CFAllocatorDeallocate(kCFAllocatorDefault, buffer);
173        goto finish;
174    }
175    result = true;
176finish:
177    if (fd != -1) {
178        close(fd);
179    }
180    if (result == false) {
181        OSKextLog(/* kext */ NULL,
182                  kOSKextLogErrorLevel,
183                  "%s: failed for '%s'", __func__, filePath);
184    }
185    return result;
186}
187
188
189/*******************************************************************************
190 *******************************************************************************/
191ExitStatus writeToFile(
192                       int           fileDescriptor,
193                       const UInt8 * data,
194                       CFIndex       length)
195{
196    ExitStatus result = EX_OSERR;
197    ssize_t bytesWritten = 0;
198    ssize_t totalBytesWritten = 0;
199
200    while (totalBytesWritten < length) {
201        bytesWritten = write(fileDescriptor, data + totalBytesWritten,
202                             length - totalBytesWritten);
203        if (bytesWritten < 0) {
204            OSKextLog(/* kext */ NULL,
205                      kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
206                      "Write failed - %s", strerror(errno));
207            goto finish;
208        }
209        totalBytesWritten += bytesWritten;
210    }
211
212    result = EX_OK;
213finish:
214    return result;
215}
216
217#if !TARGET_OS_EMBEDDED
218/******************************************************************************
219 ******************************************************************************/
220
221void postNoteAboutKexts( CFStringRef theNotificationCenterName,
222                         CFMutableDictionaryRef theDict )
223{
224    CFNotificationCenterRef     myCenter    = NULL;
225
226    if (theDict == NULL || theNotificationCenterName == NULL)
227        return;
228
229    myCenter = CFNotificationCenterGetDistributedCenter();
230    CFRetain(theDict);
231
232    CFNotificationCenterPostNotificationWithOptions(
233        myCenter,
234        theNotificationCenterName,
235        NULL,
236        theDict,
237        kCFNotificationDeliverImmediately | kCFNotificationPostToAllSessions );
238
239    SAFE_RELEASE(theDict);
240
241    return;
242}
243
244/******************************************************************************
245 * postNoteAboutKextLoadsMT will use CFNotificationCenter to post a notification.
246 * The notification center is named by theNotificationCenterName.  This routine
247 * is used to notify kextd about a kext that is getting loaded for message
248 * tracing.
249 ******************************************************************************/
250
251void postNoteAboutKextLoadsMT(CFStringRef theNotificationCenterName,
252                              CFMutableArrayRef theKextPathArray)
253{
254    CFMutableDictionaryRef      myInfoDict  = NULL; // must release
255    CFNotificationCenterRef     myCenter    = NULL;
256
257    if (theKextPathArray == NULL || theNotificationCenterName == NULL)
258        return;
259
260    myCenter = CFNotificationCenterGetDistributedCenter();
261    myInfoDict = CFDictionaryCreateMutable(
262                                           kCFAllocatorDefault, 0,
263                                           &kCFCopyStringDictionaryKeyCallBacks,
264                                           &kCFTypeDictionaryValueCallBacks);
265
266    if (myInfoDict && myCenter) {
267        CFDictionaryAddValue(myInfoDict,
268                             CFSTR("KextArrayKey"),
269                             theKextPathArray);
270
271        CFNotificationCenterPostNotificationWithOptions(
272                                                        myCenter,
273                                                        theNotificationCenterName,
274                                                        NULL,
275                                                        myInfoDict,
276                                                        kCFNotificationDeliverImmediately |
277                                                        kCFNotificationPostToAllSessions );
278    }
279
280    SAFE_RELEASE(myInfoDict);
281
282    return;
283}
284
285/*******************************************************************************
286 ******************************************************************************/
287void addKextToAlertDict( CFMutableDictionaryRef *theDictPtr, OSKextRef theKext )
288{
289    CFStringRef         myBundleID;                 // do NOT release
290    CFStringRef         myBundleVersion;            // do NOT release
291    CFMutableArrayRef   myKextArray;                // do NOT release
292    CFURLRef            myKextURL = NULL;           // must release
293    CFStringRef         myKextPath = NULL;          // must release
294    CFMutableDictionaryRef  myKextInfoDict = NULL;  // must release
295    CFMutableDictionaryRef  myAlertInfoDict = NULL; // do NOT release
296    CFIndex                myCount, i;
297
298    if ( theDictPtr == NULL || theKext == NULL ) {
299        return;
300    }
301
302    myAlertInfoDict = *theDictPtr;
303    if (myAlertInfoDict == NULL) {
304        /* caller wants us to create Alert Info Dictionary */
305        myAlertInfoDict = CFDictionaryCreateMutable(
306                                        kCFAllocatorDefault, 0,
307                                        &kCFCopyStringDictionaryKeyCallBacks,
308                                        &kCFTypeDictionaryValueCallBacks );
309        if (myAlertInfoDict == NULL) {
310            return;
311        }
312        *theDictPtr = myAlertInfoDict;
313    }
314
315    myBundleID = OSKextGetIdentifier(theKext);
316    if ( myBundleID == NULL ) {
317        goto finish;
318    }
319
320    /* We never alert about Apple Kexts */
321    if ( CFStringHasPrefix(myBundleID, __kOSKextApplePrefix) == true) {
322        goto finish;
323    }
324
325    myBundleVersion = OSKextGetValueForInfoDictionaryKey(theKext,
326                                                         kCFBundleVersionKey);
327    if (myBundleVersion == NULL) {
328        goto finish;
329    }
330
331    myKextURL = CFURLCopyAbsoluteURL(OSKextGetURL(theKext));
332    if (myKextURL == NULL) {
333        goto finish;
334    }
335
336    myKextPath = CFURLCopyFileSystemPath(myKextURL, kCFURLPOSIXPathStyle);
337    if (myKextPath == NULL) {
338        goto finish;
339    }
340
341    /* add kext info to the Alert Dictionary.
342     * We want BundleID, Version and full path to the kext
343     */
344    myKextArray = (CFMutableArrayRef)
345        CFDictionaryGetValue(myAlertInfoDict, CFSTR("KextInfoArrayKey"));
346    if (myKextArray == NULL) {
347        /* first kext info so create the kext info array */
348        myKextArray = CFArrayCreateMutable(kCFAllocatorDefault,
349                                           0,
350                                           &kCFTypeArrayCallBacks);
351        if (myKextArray == NULL) {
352            goto finish;
353        }
354        CFDictionarySetValue(myAlertInfoDict,
355                             CFSTR("KextInfoArrayKey"),
356                             myKextArray);
357    }
358
359    /* check for dup of this kext */
360    myCount = CFArrayGetCount(myKextArray);
361    if (myCount > 0) {
362        for (i = 0; i < myCount; i++) {
363            CFMutableDictionaryRef myDict;
364            myDict = (CFMutableDictionaryRef)
365            CFArrayGetValueAtIndex(myKextArray, i);
366            if (myDict == NULL)   continue;
367
368            if ( !CFDictionaryContainsValue(myDict, myBundleID) ) {
369                continue;
370            }
371            if ( !CFDictionaryContainsValue(myDict,
372                                            myBundleVersion) ) {
373                continue;
374            }
375            /* already have this one so bail */
376            goto finish;
377        }
378    }
379
380    /* new kext info to add */
381    myKextInfoDict = CFDictionaryCreateMutable(
382                                        kCFAllocatorDefault, 0,
383                                        &kCFCopyStringDictionaryKeyCallBacks,
384                                        &kCFTypeDictionaryValueCallBacks);
385    if (myKextInfoDict == NULL) {
386        goto finish;
387    }
388    CFDictionaryAddValue(myKextInfoDict,
389                         kCFBundleIdentifierKey,
390                         myBundleID);
391    CFDictionaryAddValue(myKextInfoDict,
392                         kCFBundleVersionKey,
393                         myBundleVersion);
394    CFDictionaryAddValue(myKextInfoDict,
395                         CFSTR("KextPathKey"),
396                         myKextPath);
397
398    CFArrayAppendValue(myKextArray,
399                       myKextInfoDict);
400
401finish:
402    SAFE_RELEASE(myKextURL);
403    SAFE_RELEASE(myKextPath);
404    SAFE_RELEASE(myKextInfoDict);
405
406    return;
407}
408
409
410/*******************************************************************************
411 * isDebugSetInBootargs() - check to see if boot-args has debug set.  We cache
412 * the result.
413 *******************************************************************************/
414Boolean isDebugSetInBootargs(void)
415{
416    static int          didOnce         = 0;
417    static Boolean      result          = false;
418    io_registry_entry_t optionsNode     = MACH_PORT_NULL;   // must release
419    CFStringRef         bootargsEntry   = NULL;             // must release
420
421    if (didOnce) {
422        return(result);
423    }
424    optionsNode = IORegistryEntryFromPath(kIOMasterPortDefault,
425                                          "IODeviceTree:/options");
426    if (optionsNode) {
427        bootargsEntry = (CFStringRef)
428        IORegistryEntryCreateCFProperty(optionsNode,
429                                        CFSTR("boot-args"),
430                                        kCFAllocatorDefault, 0);
431        if (bootargsEntry &&
432            (CFGetTypeID(bootargsEntry) == CFStringGetTypeID())) {
433            CFRange     findRange;
434            findRange = CFStringFind(bootargsEntry, CFSTR("debug"), 0);
435
436            if (findRange.length != 0) {
437                result = true;
438            }
439        }
440    }
441    didOnce++;
442    if (optionsNode)  IOObjectRelease(optionsNode);
443    SAFE_RELEASE(bootargsEntry);
444
445    return(result);
446}
447
448#endif  // !TARGET_OS_EMBEDDED
449
450#if PRAGMA_MARK
451#pragma mark Path & File
452#endif /* PRAGMA_MARK */
453/*******************************************************************************
454*******************************************************************************/
455ExitStatus checkPath(
456    const char * path,
457    const char * suffix,  // w/o the dot
458    Boolean      directoryRequired,
459    Boolean      writableRequired)
460{
461    Boolean result  = EX_USAGE;
462    Boolean nameBad = FALSE;
463    struct  stat statBuffer;
464
465    if (!path) {
466        OSKextLog(/* kext */ NULL,
467            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
468            "Internal error - %s - NULL path.",
469                __FUNCTION__);
470        result = EX_SOFTWARE;
471        goto finish;
472    }
473
474    result = EX_USAGE;
475    if (suffix) {
476        size_t pathLength   = strlen(path);
477        size_t suffixLength = strlen(suffix);
478        size_t suffixIndex = 0;
479        size_t periodIndex = 0;
480
481        nameBad = TRUE;
482        if (!pathLength || !suffixLength) {
483            OSKextLog(/* kext */ NULL,
484                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
485                "Internal error - %s - empty string.",
486                __FUNCTION__);
487            result = EX_SOFTWARE;
488            goto finish;
489        }
490
491       /* Peel off any trailing '/' characters (silly shell completion),
492        * then advance the length back to point to the character past
493        * the real end (which will be a slash or '\0').
494        */
495        while (pathLength-- && path[pathLength] == '/') {
496            /* just scanning for last non-slash */
497            if (!pathLength) {
498                goto finish;
499            }
500        }
501        pathLength++;
502
503        if (suffixLength >= pathLength) {
504            goto finish;
505        }
506        suffixIndex = pathLength - suffixLength;
507        periodIndex = suffixIndex - 1;
508        if (path[periodIndex] != '.' ||
509            strncmp(path + suffixIndex, suffix, suffixLength)) {
510            goto finish;
511        }
512        nameBad = FALSE;
513    }
514
515    result = EX_NOINPUT;
516    if (0 != stat(path, &statBuffer)) {
517        OSKextLog(/* kext */ NULL,
518            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
519            "Can't stat %s - %s.", path,
520            strerror(errno));
521        goto finish;
522    }
523
524    if (directoryRequired && ((statBuffer.st_mode & S_IFMT) != S_IFDIR) ) {
525        OSKextLog(/* kext */ NULL,
526            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
527            "%s is not a directory.",
528            path);
529        goto finish;
530    }
531
532    result = EX_NOPERM;
533    if (writableRequired && access(path, W_OK) == -1) {
534        OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel,
535            "%s is not writable.", path);
536        goto finish;
537    }
538
539    result = EX_OK;
540
541finish:
542    if (nameBad) {
543        OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel,
544            "%s not of type '%s'.", path, suffix);
545    }
546    return result;
547}
548
549/*******************************************************************************
550*******************************************************************************/
551void
552saveFile(const void * vKey, const void * vValue, void * vContext)
553{
554    CFStringRef       key      = (CFStringRef)vKey;
555    CFDataRef         fileData = (CFDataRef)vValue;
556    SaveFileContext * context  = (SaveFileContext *)vContext;
557
558    long              length;
559    int               fd = -1;
560    mode_t            mode = 0666;
561    struct  stat      statBuf;
562    CFURLRef          saveURL = NULL;     // must release
563    Boolean           fileExists = false;
564    char              savePath[PATH_MAX];
565
566    if (context->fatal) {
567        goto finish;
568    }
569
570    saveURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault,
571        context->saveDirURL, key, /* isDirectory */ false);
572    if (!saveURL) {
573        context->fatal = true;
574        goto finish;
575    }
576
577    if (!CFURLGetFileSystemRepresentation(saveURL, /* resolveToBase */ true,
578        (u_char *)savePath, sizeof(savePath))) {
579        context->fatal = true;
580        goto finish;
581    }
582
583    if (!context->overwrite) {
584        fileExists = CFURLResourceIsReachable(saveURL, NULL);
585        if (fileExists) {
586           switch (user_approve(/* ask_all */ TRUE, /* default_answer */ REPLY_YES,
587                "%s exists, overwrite", savePath)) {
588
589                case REPLY_YES:
590                    // go ahead and overwrite.
591                    break;
592                case REPLY_ALL:
593                    // go ahead and overwrite this and all following.
594                    fprintf(stderr,
595                        "Overwriting all symbol files for kexts in dependency graph.\n");
596                    context->overwrite = TRUE;
597                    break;
598                case REPLY_NO:
599                    goto finish;
600                    break;
601                default:
602                    context->fatal = true;
603                    goto finish;
604                    break;
605            }
606        }
607        else {
608            OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel,
609                      "%s missing '%s'", __func__, savePath);
610            context->fatal = true;
611            goto finish;
612        }
613    }
614
615    /* Write data.
616     */
617    length = CFDataGetLength(fileData);
618    if (0 == stat(savePath, &statBuf)) {
619        mode = statBuf.st_mode;
620    }
621    fd = open(savePath, O_WRONLY|O_CREAT|O_TRUNC, mode);
622    if (fd != -1 && length) {
623        ExitStatus result;
624        result = writeToFile(fd, CFDataGetBytePtr(fileData), length);
625        if (result != EX_OK) {
626            OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel,
627                      "%s write failed for '%s'", __func__, savePath);
628        }
629    }
630    else {
631        /* Is this fatal to the whole program? I'd rather soldier on.
632         */
633        OSKextLog(/* kext */ NULL,
634                  kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
635                  "%s Failed to save '%s'", __func__, savePath);
636    }
637
638finish:
639    if (fd != -1) {
640        fsync(fd);
641        close(fd);
642    }
643    SAFE_RELEASE(saveURL);
644    return;
645}
646
647/*******************************************************************************
648*******************************************************************************/
649CFStringRef copyKextPath(OSKextRef aKext)
650{
651    CFStringRef result = NULL;
652    CFURLRef    absURL = NULL;  // must release
653
654    if (!OSKextGetURL(aKext)) {
655        goto finish;
656    }
657
658    absURL = CFURLCopyAbsoluteURL(OSKextGetURL(aKext));
659    if (!absURL) {
660        goto finish;
661    }
662    result = CFURLCopyFileSystemPath(absURL, kCFURLPOSIXPathStyle);
663finish:
664    SAFE_RELEASE(absURL);
665    return result;
666}
667
668
669/*******************************************************************************
670 * Returns the access and mod times from the file in the given array of
671 * fileURLs with the latest mod time.
672 * 11860417 - support multiple extensions directories
673 *******************************************************************************/
674ExitStatus
675getLatestTimesFromCFURLArray(
676                             CFArrayRef       dirURLArray,
677                             struct timeval   dirTimeVals[2])
678{
679    ExitStatus      result     = EX_SOFTWARE;
680    int             i;
681    CFURLRef        myURL;
682    struct stat     myStatBuf;
683    struct timeval  myTempModTime;
684    struct timeval  myTempAccessTime;
685
686    if (dirURLArray == NULL) {
687        goto finish;
688    }
689    bzero(dirTimeVals, (sizeof(struct timeval) * 2));
690
691    for (i = 0; i < CFArrayGetCount(dirURLArray); i++) {
692        myURL = (CFURLRef) CFArrayGetValueAtIndex(dirURLArray, i);
693        if (myURL == NULL) {
694            OSKextLog(NULL,
695                      kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
696                      "%s: NO fileURL at index %d!!!! ", __FUNCTION__, i);
697            goto finish;
698        }
699
700        result = statURL(myURL, &myStatBuf);
701        if (result != EX_OK) {
702            goto finish;
703        }
704        TIMESPEC_TO_TIMEVAL(&myTempAccessTime, &myStatBuf.st_atimespec);
705        TIMESPEC_TO_TIMEVAL(&myTempModTime, &myStatBuf.st_mtimespec);
706
707        if (timercmp(&myTempModTime, &dirTimeVals[1], >)) {
708            dirTimeVals[0].tv_sec = myTempAccessTime.tv_sec;
709            dirTimeVals[0].tv_usec = myTempAccessTime.tv_usec;
710            dirTimeVals[1].tv_sec = myTempModTime.tv_sec;
711            dirTimeVals[1].tv_usec = myTempModTime.tv_usec;
712        }
713    }
714
715    result = EX_OK;
716finish:
717    return result;
718}
719
720/*******************************************************************************
721 * Returns the access and mod times from the file in the given directory with
722 * the latest mod time.
723 *******************************************************************************/
724ExitStatus
725getLatestTimesFromDirURL(
726                         CFURLRef       dirURL,
727                         struct timeval dirTimeVals[2])
728{
729    ExitStatus          result              = EX_SOFTWARE;
730    CFURLEnumeratorRef  myEnumerator        = NULL; // must release
731    struct stat         myStatBuf;
732    struct timeval      myTempModTime;
733    struct timeval      myTempAccessTime;
734
735    bzero(dirTimeVals, (sizeof(struct timeval) * 2));
736
737    if (dirURL == NULL) {
738        goto finish;
739    }
740
741    myEnumerator = CFURLEnumeratorCreateForDirectoryURL(
742                                            NULL,
743                                            dirURL,
744                                            kCFURLEnumeratorDefaultBehavior,
745                                            NULL );
746    if (myEnumerator == NULL) {
747        OSKextLogMemError();
748        goto finish;
749    }
750    CFURLRef myURL = NULL;
751    while (CFURLEnumeratorGetNextURL(
752                                     myEnumerator,
753                                     &myURL,
754                                     NULL) == kCFURLEnumeratorSuccess) {
755        if (statURL(myURL, &myStatBuf) != EX_OK) {
756            goto finish;
757        }
758        TIMESPEC_TO_TIMEVAL(&myTempAccessTime, &myStatBuf.st_atimespec);
759        TIMESPEC_TO_TIMEVAL(&myTempModTime, &myStatBuf.st_mtimespec);
760
761        if (timercmp(&myTempModTime, &dirTimeVals[1], >)) {
762            dirTimeVals[0].tv_sec = myTempAccessTime.tv_sec;
763            dirTimeVals[0].tv_usec = myTempAccessTime.tv_usec;
764            dirTimeVals[1].tv_sec = myTempModTime.tv_sec;
765            dirTimeVals[1].tv_usec = myTempModTime.tv_usec;
766        }
767    } // while loop...
768
769    result = EX_OK;
770finish:
771    if (myEnumerator)   CFRelease(myEnumerator);
772    return result;
773}
774
775/*******************************************************************************
776 * Returns the access and mod times from the file in the given directory with
777 * the latest mod time.
778 *******************************************************************************/
779ExitStatus
780getLatestTimesFromDirPath(
781                          const char *   dirPath,
782                          struct timeval dirTimeVals[2])
783{
784    ExitStatus          result              = EX_SOFTWARE;
785    CFURLRef            kernURL             = NULL; // must release
786
787    if (dirPath == NULL) {
788        goto finish;
789    }
790
791    kernURL = CFURLCreateFromFileSystemRepresentation(
792                                                      NULL,
793                                                      (const UInt8 *)dirPath,
794                                                      strlen(dirPath),
795                                                      true );
796    if (kernURL == NULL) {
797        OSKextLogMemError();
798        goto finish;
799    }
800
801    result = getLatestTimesFromDirURL(kernURL, dirTimeVals);
802finish:
803    if (kernURL)        CFRelease(kernURL);
804    return result;
805}
806
807/*******************************************************************************
808 *******************************************************************************/
809ExitStatus
810getParentPathTimes(
811                   const char        * thePath,
812                   struct timeval      cacheFileTimes[2] )
813{
814    ExitStatus          result          = EX_SOFTWARE;
815    char *              lastSlash       = NULL;
816    char                myTempPath[PATH_MAX];
817
818    if (thePath == NULL) {
819        goto finish;
820    }
821
822    lastSlash = strrchr(thePath, '/');
823    // bail if no '/' or if length is < 2 (shortest possible dir path "/a/")
824    if (lastSlash == NULL || (lastSlash - thePath) < 2) {
825        goto finish;
826    }
827    // drop off everything at last '/' and beyond
828    if (strlcpy(myTempPath,
829                thePath,
830                (lastSlash - thePath) + 1) >= PATH_MAX) {
831        goto finish;
832    }
833
834    result = getFilePathTimes(myTempPath, cacheFileTimes);
835finish:
836    return result;
837}
838
839/*******************************************************************************
840 *******************************************************************************/
841ExitStatus
842getFilePathTimes(
843                 const char        * filePath,
844                 struct timeval      cacheFileTimes[2])
845{
846    struct stat         statBuffer;
847    ExitStatus          result          = EX_SOFTWARE;
848
849    result = statPath(filePath, &statBuffer);
850    if (result != EX_OK) {
851        goto finish;
852    }
853
854    TIMESPEC_TO_TIMEVAL(&cacheFileTimes[0], &statBuffer.st_atimespec);
855    TIMESPEC_TO_TIMEVAL(&cacheFileTimes[1], &statBuffer.st_mtimespec);
856
857    result = EX_OK;
858finish:
859    return result;
860}
861
862
863/*******************************************************************************
864 *******************************************************************************/
865ExitStatus
866statURL(CFURLRef anURL, struct stat * statBuffer)
867{
868    ExitStatus result = EX_OSERR;
869    char path[PATH_MAX];
870
871    if (!CFURLGetFileSystemRepresentation(anURL, /* resolveToBase */ true,
872                                          (UInt8 *)path, sizeof(path)))
873    {
874        OSKextLogStringError(/* kext */ NULL);
875        goto finish;
876    }
877
878    result = statPath(path, statBuffer);
879    if (!result) {
880        goto finish;
881    }
882
883    result = EX_OK;
884finish:
885    return result;
886}
887
888/*******************************************************************************
889 *******************************************************************************/
890ExitStatus
891statPath(const char *path, struct stat *statBuffer)
892{
893    ExitStatus result = EX_OSERR;
894
895    if (stat(path, statBuffer)) {
896        OSKextLog(/* kext */ NULL,
897                  kOSKextLogDebugLevel | kOSKextLogGeneralFlag,
898                  "Can't stat %s - %s.", path, strerror(errno));
899        goto finish;
900    }
901
902    result = EX_OK;
903
904finish:
905    return result;
906}
907
908/*******************************************************************************
909 *******************************************************************************/
910ExitStatus
911statParentPath(const char *thePath, struct stat *statBuffer)
912{
913    ExitStatus          result          = EX_SOFTWARE;
914    char *              lastSlash       = NULL;
915    char                myTempPath[PATH_MAX];
916
917    if (thePath == NULL) {
918        goto finish;
919    }
920
921    lastSlash = strrchr(thePath, '/');
922    // bail if no '/' or if length is < 2 (shortest possible dir path "/a/")
923    if (lastSlash == NULL || (lastSlash - thePath) < 2) {
924        goto finish;
925    }
926    // drop off everything at last '/' and beyond
927    if (strlcpy(myTempPath,
928                thePath,
929                (lastSlash - thePath) + 1) >= PATH_MAX) {
930        goto finish;
931    }
932
933    result = statPath(myTempPath, statBuffer);
934finish:
935    return result;
936}
937
938/*******************************************************************************
939 * caller must free returned pointer
940 *******************************************************************************/
941char *
942getPathExtension(const char * pathPtr)
943{
944    char *              suffixPtr       = NULL; // caller must free
945    CFURLRef            pathURL         = NULL; // must release
946    CFStringRef         tmpCFString     = NULL; // must release
947
948    pathURL = CFURLCreateFromFileSystemRepresentation(
949                                                      NULL,
950                                                      (const UInt8 *)pathPtr,
951                                                      strlen(pathPtr),
952                                                      true );
953    if (pathURL == NULL) {
954        goto finish;
955    }
956    tmpCFString =  CFURLCopyPathExtension(pathURL);
957    if (tmpCFString == NULL) {
958        goto finish;
959    }
960    suffixPtr = createUTF8CStringForCFString(tmpCFString);
961
962finish:
963    SAFE_RELEASE(pathURL);
964    SAFE_RELEASE(tmpCFString);
965
966    return suffixPtr;
967}
968
969#if PRAGMA_MARK
970#pragma mark Logging
971#endif /* PRAGMA_MARK */
972/*******************************************************************************
973*******************************************************************************/
974OSKextLogSpec _sLogSpecsForVerboseLevels[] = {
975    kOSKextLogErrorLevel    | kOSKextLogVerboseFlagsMask,   // [0xff1] -v 0
976    kOSKextLogBasicLevel    | kOSKextLogVerboseFlagsMask,   // [0xff3] -v 1
977    kOSKextLogProgressLevel | kOSKextLogVerboseFlagsMask,   // [0xff4] -v 2
978    kOSKextLogStepLevel     | kOSKextLogVerboseFlagsMask,   // [0xff5] -v 3
979    kOSKextLogDetailLevel   | kOSKextLogVerboseFlagsMask,   // [0xff6] -v 4
980    kOSKextLogDebugLevel    | kOSKextLogVerboseFlagsMask,   // [0xff7] -v 5
981    kOSKextLogDebugLevel    | kOSKextLogVerboseFlagsMask |  // [0xfff] -v 6
982        kOSKextLogKextOrGlobalMask
983};
984
985/*******************************************************************************
986* getopt_long_only() doesn't actually handle optional args very well. So, we
987* jump through some hoops here to handle all six possibilities:
988*
989*   cmd line      optarg  argv[optind]
990*   ----------------------------------------------
991*   -v            (null)  (following arg or null)
992*   -v arg        (null)  arg
993*   -v=arg        (null)  -v=arg -- ILLEGAL
994*   -verbose      (null)  (following arg or null)
995*   -verbose arg  (null)  arg
996*   -verbose=arg  arg     (following arg or null)
997*
998* Note that only in the -verbose=arg case does optarg actually get set
999* correctly!
1000*
1001* If we have not optarg but a following argv[optind], we check it to see if
1002* it looks like a legal arg to -v/-verbose; if it matches we increment optind.
1003* -v has never allowed the argument to immediately follow (as in -v2), so
1004* we still don't handle that.
1005*******************************************************************************/
1006#define kBadVerboseOptPrefix  "-v="
1007
1008ExitStatus setLogFilterForOpt(
1009    int            argc,
1010    char * const * argv,
1011    OSKextLogSpec  forceOnFlags)
1012{
1013    ExitStatus      result       = EX_USAGE;
1014    OSKextLogSpec   logFilter    = 0;
1015    const char    * localOptarg  = NULL;
1016
1017   /* Must be a bare -v; just use the extra flags.
1018    */
1019    if (!optarg && optind >= argc) {
1020        logFilter = _sLogSpecsForVerboseLevels[1];
1021
1022    } else {
1023
1024        if (optarg) {
1025            localOptarg = optarg;
1026        } else {
1027            localOptarg = argv[optind];
1028        }
1029
1030        if (!strncmp(localOptarg, kBadVerboseOptPrefix,
1031            sizeof(kBadVerboseOptPrefix) - 1)) {
1032
1033            OSKextLog(/* kext */ NULL,
1034                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
1035                "%s - syntax error (don't use = with single-letter option args).",
1036                localOptarg);
1037            goto finish;
1038        }
1039
1040       /* Look for '-v0x####' with no space and advance to the 0x part.
1041        */
1042        if (localOptarg[0] == '-' && localOptarg[1] == kOptVerbose &&
1043            localOptarg[2] == '0' && (localOptarg[3] == 'x' || localOptarg[3] == 'X')) {
1044
1045            localOptarg += 2;
1046        }
1047
1048       /* Look for a 0x#### style verbose arg.
1049        */
1050        if (localOptarg[0] == '0' && (localOptarg[1] == 'x' || localOptarg[1] == 'X')) {
1051            char          * endptr      = NULL;
1052            OSKextLogSpec   parsedFlags = (unsigned)strtoul(localOptarg, &endptr, 16);
1053
1054            if (endptr[0]) {
1055                OSKextLog(/* kext */ NULL,
1056                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
1057                    "Can't parse verbose argument %s.", localOptarg);
1058                goto finish;
1059            }
1060            logFilter = parsedFlags;
1061
1062            if (!optarg) {
1063                optind++;
1064            }
1065
1066       /* Now a 0-6 style verbose arg.
1067        */
1068        } else if (((localOptarg[0] >= '0') || (localOptarg[0] <= '6')) &&
1069            (localOptarg[1] == '\0')) {
1070
1071            logFilter = _sLogSpecsForVerboseLevels[localOptarg[0] - '0'];
1072            if (!optarg) {
1073                optind++;
1074            }
1075
1076       /* Must be a -v with command args following; just use the extra flag.
1077        */
1078        } else {
1079            logFilter = _sLogSpecsForVerboseLevels[1];
1080        }
1081    }
1082
1083    logFilter = logFilter | forceOnFlags;
1084
1085    OSKextSetLogFilter(logFilter, /* kernel? */ false);
1086    OSKextSetLogFilter(logFilter, /* kernel? */ true);
1087
1088    result = EX_OK;
1089
1090finish:
1091    return result;
1092}
1093
1094/*******************************************************************************
1095*******************************************************************************/
1096void beQuiet(void)
1097{
1098    fclose(stdout);
1099    fclose(stderr);
1100    close(1);
1101    close(2);
1102    OSKextSetLogFilter(kOSKextLogSilentFilter, /* kernel? */ false);
1103    OSKextSetLogFilter(kOSKextLogSilentFilter, /* kernel? */ true);
1104    return;
1105}
1106
1107/*******************************************************************************
1108*******************************************************************************/
1109FILE * g_log_stream = NULL;
1110aslclient gASLClientHandle = NULL;
1111aslmsg    gASLMessage      = NULL;  // reused
1112// xxx - need to aslclose()
1113
1114void tool_openlog(const char * name)
1115{
1116    // xxx - do we want separate name & facility?
1117    gASLClientHandle = asl_open(/* ident */ name, /* facility*/ name,
1118        /* options */ 0);
1119    gASLMessage     = asl_new(ASL_TYPE_MSG);
1120    return;
1121}
1122
1123#if !TARGET_OS_EMBEDDED
1124/*******************************************************************************
1125 * Check to see if this is an Apple internal build.  If apple internel then
1126 * use development kernel if it exists.
1127 * /System/Library/Kernels/kernel.development
1128 *******************************************************************************/
1129Boolean useDevelopmentKernel(const char * theKernelPath)
1130{
1131    struct stat     statBuf;
1132    char *          tempPath = NULL;
1133    size_t          length = 0;
1134    Boolean         myResult = FALSE;
1135
1136    if (statPath(kAppleInternalPath, &statBuf) != EX_OK) {
1137        return(myResult);
1138    }
1139    tempPath = malloc(PATH_MAX);
1140
1141    while (tempPath) {
1142        length = strlcpy(tempPath, theKernelPath, PATH_MAX);
1143        if (length >= PATH_MAX)   break;
1144        length = strlcat(tempPath,
1145                         kDefaultKernelSuffix,
1146                         PATH_MAX);
1147        if (length >= PATH_MAX)   break;
1148        if (statPath(tempPath, &statBuf) == EX_OK) {
1149            // use kernel.development
1150            myResult = TRUE;
1151        }
1152        break;
1153    } // while...
1154
1155    if (tempPath)   free(tempPath);
1156
1157    return(myResult);
1158}
1159#endif  // !TARGET_OS_EMBEDDED
1160
1161/*******************************************************************************
1162* Basic log function. If any log flags are set, log the message
1163* to syslog/stderr.  Note: exported as SPI in bootroot.h.
1164*******************************************************************************/
1165
1166void tool_log(
1167    OSKextRef       aKext __unused,
1168    OSKextLogSpec   msgLogSpec,
1169    const char    * format, ...)
1170{
1171    va_list ap;
1172
1173    if (gASLClientHandle) {
1174        int            aslLevel = ASL_LEVEL_ERR;
1175        OSKextLogSpec  kextLogLevel = msgLogSpec & kOSKextLogLevelMask;
1176        char           messageLogSpec[16];
1177
1178        if (kextLogLevel == kOSKextLogErrorLevel) {
1179            aslLevel = ASL_LEVEL_ERR;
1180        } else if (kextLogLevel == kOSKextLogWarningLevel) {
1181            aslLevel = ASL_LEVEL_WARNING;
1182        } else if (kextLogLevel == kOSKextLogBasicLevel) {
1183            aslLevel = ASL_LEVEL_NOTICE;
1184        } else if (kextLogLevel < kOSKextLogDebugLevel) {
1185            aslLevel = ASL_LEVEL_INFO;
1186        } else {
1187            aslLevel = ASL_LEVEL_DEBUG;
1188        }
1189
1190        snprintf(messageLogSpec, sizeof(messageLogSpec), "0x%x", msgLogSpec);
1191        asl_set(gASLMessage, "OSKextLogSpec", messageLogSpec);
1192
1193        va_start(ap, format);
1194        asl_vlog(gASLClientHandle, gASLMessage, aslLevel, format, ap);
1195        va_end(ap);
1196
1197    } else {
1198        // xxx - change to pick log stream based on log level
1199        // xxx - (0 == stdout, all others stderr)
1200
1201        if (!g_log_stream) {
1202            g_log_stream = stderr;
1203        }
1204
1205        va_start(ap, format);
1206        vfprintf(g_log_stream, format, ap);
1207        va_end(ap);
1208
1209        fprintf(g_log_stream, "\n");
1210        fflush(g_log_stream);
1211    }
1212
1213    return;
1214}
1215
1216/*******************************************************************************
1217*******************************************************************************/
1218void log_CFError(
1219    OSKextRef     aKext __unused,
1220    OSKextLogSpec msgLogSpec,
1221    CFErrorRef    error)
1222{
1223    CFStringRef   errorString = NULL;  // must release
1224    char        * cstring     = NULL;  // must release
1225
1226    if (!error) {
1227        return;
1228    }
1229    errorString = CFErrorCopyDescription(error);
1230    if (errorString) {
1231        cstring = createUTF8CStringForCFString(errorString);
1232        OSKextLog(/* kext */ NULL, msgLogSpec,
1233            "CFError descripton: %s.", cstring);
1234        SAFE_RELEASE_NULL(errorString);
1235        SAFE_FREE_NULL(cstring);
1236    }
1237
1238    errorString = CFErrorCopyFailureReason(error);
1239    if (errorString) {
1240        cstring = createUTF8CStringForCFString(errorString);
1241        OSKextLog(/* kext */ NULL, msgLogSpec,
1242            "CFError reason: %s.", cstring);
1243        SAFE_RELEASE_NULL(errorString);
1244        SAFE_FREE_NULL(cstring);
1245    }
1246
1247    return;
1248}
1249
1250#if !TARGET_OS_EMBEDDED
1251// log helper for libbless, exported as SPI via bootroot.h
1252int32_t
1253BRBLLogFunc(void *refcon __unused, int32_t level, const char *string)
1254{
1255    OSKextLogSpec logSpec = kOSKextLogGeneralFlag;
1256    switch (level) {
1257    case kBLLogLevelVerbose:
1258        logSpec |= kOSKextLogDebugLevel;
1259        break;
1260    case kBLLogLevelError:
1261        logSpec |= kOSKextLogErrorLevel;
1262        break;
1263    default:
1264        logSpec |= kOSKextLogWarningLevel;
1265    }
1266    OSKextLog(NULL, logSpec, "%s", string);
1267    return 0;
1268}
1269
1270/*******************************************************************************
1271 * returns TRUE and fills the Buffer with path to kernel extracted from
1272 * Kernelcache dictionary from /usr/standalone/bootcaches.plist on the
1273 * given volume.  If we can't find the path or if it does not exist or if
1274 * theBuffer is too small we return FALSE.
1275 *
1276 * If we find the kernel path in bootcaches.plist, but the file does not exist
1277 * we will copy the path into the buffer.  In that case the result will be FALSE
1278 * and strlen on buffer will be > 0.
1279 *
1280 * theVolRootURL == NULL means we want root volume.
1281 *******************************************************************************/
1282Boolean getKernelPathForURL(CFURLRef    theVolRootURL,
1283                            char *      theBuffer,
1284                            int         theBufferSize)
1285{
1286    CFDictionaryRef myDict              = NULL;     // must release
1287    CFDictionaryRef postBootPathsDict   = NULL;     // do not release
1288    CFDictionaryRef kernelCacheDict     = NULL;     // do not release
1289    Boolean			myResult            = FALSE;
1290
1291    if (theBuffer) {
1292        *theBuffer = 0x00;
1293
1294        myDict = copyBootCachesDictForURL(theVolRootURL);
1295        if (myDict != NULL) {
1296            postBootPathsDict = (CFDictionaryRef)
1297                CFDictionaryGetValue(myDict, kBCPostBootKey);
1298
1299            if (postBootPathsDict &&
1300                CFGetTypeID(postBootPathsDict) == CFDictionaryGetTypeID()) {
1301
1302                kernelCacheDict = (CFDictionaryRef)
1303                    CFDictionaryGetValue(postBootPathsDict, kBCKernelcacheV3Key);
1304            }
1305        }
1306    } // theBuffer
1307
1308    if (kernelCacheDict &&
1309        CFGetTypeID(kernelCacheDict) == CFDictionaryGetTypeID()) {
1310        CFStringRef     myTempStr;      // do not release
1311
1312        myTempStr = (CFStringRef) CFDictionaryGetValue(kernelCacheDict,
1313                                                       kBCKernelPathKey);
1314        if (myTempStr != NULL &&
1315            CFGetTypeID(myTempStr) == CFStringGetTypeID()) {
1316
1317            if (CFStringGetFileSystemRepresentation(myTempStr,
1318                                                    theBuffer,
1319                                                    theBufferSize)) {
1320                struct stat     statBuf;
1321                if (statPath(theBuffer, &statBuf) == EX_OK) {
1322                    myResult = TRUE;
1323                }
1324            }
1325        }
1326    } // kernelCacheDict
1327
1328    SAFE_RELEASE(myDict);
1329
1330    return(myResult);
1331}
1332
1333/*******************************************************************************
1334 * returns copy of /usr/standalone/bootcaches.plist (as CFDictionary) from
1335 * given volume.
1336 *
1337 * theVolRootURL == NULL means we want root volume.
1338 *
1339 * Caller must release returned CFDictionaryRef.
1340 *******************************************************************************/
1341CFDictionaryRef copyBootCachesDictForURL(CFURLRef theVolRootURL)
1342{
1343    CFStringRef             myVolRoot = NULL;           // must release
1344    CFStringRef             myPath = NULL;              // must release
1345    CFURLRef                myURL = NULL;               // must release
1346    CFDictionaryRef         myBootCachesPlist = NULL;   // do not release
1347
1348    if (theVolRootURL) {
1349        myVolRoot = CFURLCopyFileSystemPath(theVolRootURL,
1350                                            kCFURLPOSIXPathStyle);
1351        if (myVolRoot == NULL) {
1352            goto finish;
1353        }
1354        myPath = CFStringCreateWithFormat(
1355                                          kCFAllocatorDefault,
1356                                          /* formatOptions */ NULL,
1357                                          CFSTR("%@%s"),
1358                                          myVolRoot,
1359                                          "/usr/standalone/bootcaches.plist" );
1360    }
1361    else {
1362        myPath = CFStringCreateWithCString(
1363                                           kCFAllocatorDefault,
1364                                           "/usr/standalone/bootcaches.plist",
1365                                           kCFStringEncodingUTF8 );
1366    }
1367    if (myPath == NULL) {
1368        goto finish;
1369    }
1370
1371    myURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
1372                                          myPath,
1373                                          kCFURLPOSIXPathStyle,
1374                                          false );
1375    if (myURL && CFURLResourceIsReachable(myURL, NULL)) {
1376        CFReadStreamRef         readStream      = NULL;  // must release
1377        struct stat             myStatBuf;
1378        ExitStatus              myExitStatus;
1379
1380        myExitStatus = statURL(myURL, &myStatBuf);
1381        if (myExitStatus != EX_OK) {
1382            goto finish;
1383        }
1384        if (myStatBuf.st_uid != 0) {
1385            goto finish;
1386        }
1387        if (myStatBuf.st_mode & S_IWGRP || myStatBuf.st_mode & S_IWOTH) {
1388            goto finish;
1389        }
1390
1391        readStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, myURL);
1392        if (readStream) {
1393            if (CFReadStreamOpen(readStream)) {
1394                /* read in contents of bootcaches.plist */
1395                myBootCachesPlist = CFPropertyListCreateWithStream(
1396                                                                   kCFAllocatorDefault,
1397                                                                   readStream,
1398                                                                   0,
1399                                                                   kCFPropertyListMutableContainersAndLeaves,
1400                                                                   NULL, NULL);
1401                CFReadStreamClose(readStream);
1402            }
1403            SAFE_RELEASE(readStream);
1404        }
1405    }
1406
1407finish:
1408    SAFE_RELEASE(myURL);
1409    SAFE_RELEASE(myPath);
1410    SAFE_RELEASE(myVolRoot);
1411
1412    return(myBootCachesPlist);
1413}
1414#endif   // !TARGET_OS_EMBEDDED
1415
1416/*******************************************************************************
1417* safe_mach_error_string()
1418*******************************************************************************/
1419const char * safe_mach_error_string(mach_error_t error_code)
1420{
1421    const char * result = mach_error_string(error_code);
1422    if (!result) {
1423        result = "(unknown)";
1424    }
1425    return result;
1426}
1427
1428#if PRAGMA_MARK
1429#pragma mark User Input
1430#endif /* PRAGMA_MARK */
1431/*******************************************************************************
1432* user_approve()
1433*
1434* Ask the user a question and wait for a yes/no answer.
1435*******************************************************************************/
1436int user_approve(Boolean ask_all, int default_answer, const char * format, ...)
1437{
1438    int     result = REPLY_YES;
1439    va_list ap;
1440    char    fake_buffer[2];
1441    int     output_length;
1442    char  * output_string;
1443    int     c, x;
1444
1445    va_start(ap, format);
1446    output_length = vsnprintf(fake_buffer, 1, format, ap);
1447    va_end(ap);
1448
1449    output_string = (char *)malloc(output_length + 1);
1450    if (!output_string) {
1451        result = REPLY_ERROR;
1452        goto finish;
1453    }
1454
1455    va_start(ap, format);
1456    vsnprintf(output_string, output_length + 1, format, ap);
1457    va_end(ap);
1458
1459    while ( 1 ) {
1460        fprintf(stderr, "%s [%s/%s", output_string,
1461            (default_answer == REPLY_YES) ? "Y" : "y",
1462            (default_answer == REPLY_NO)  ? "N" : "n");
1463        if (ask_all) {
1464            fprintf(stderr, "/%s",
1465                (default_answer == REPLY_ALL) ? "A" : "a");
1466        }
1467        fprintf(stderr, "]? ");
1468        fflush(stderr);
1469
1470        c = fgetc(stdin);
1471
1472        if (c == EOF) {
1473            result = REPLY_ERROR;
1474            goto finish;
1475        }
1476
1477       /* Make sure we get a newline.
1478        */
1479        if ( c != '\n' ) {
1480            do {
1481                x = fgetc(stdin);
1482            } while (x != '\n' && x != EOF);
1483
1484            if (x == EOF) {
1485                result = REPLY_ERROR;
1486                goto finish;
1487            }
1488        }
1489
1490        if (c == '\n') {
1491            result = default_answer;
1492            goto finish;
1493        } else if (tolower(c) == 'y') {
1494            result = REPLY_YES;
1495            goto finish;
1496        } else if (tolower(c) == 'n') {
1497            result = REPLY_NO;
1498            goto finish;
1499        } else if (ask_all && tolower(c) == 'a') {
1500            result = REPLY_ALL;
1501            goto finish;
1502        } else {
1503            fprintf(stderr, "Please answer 'y' or 'n'%s.\n",
1504                ask_all ? " or 'a'" : "");
1505        }
1506    }
1507
1508finish:
1509    if (output_string) free(output_string);
1510
1511    return result;
1512}
1513
1514/*******************************************************************************
1515* user_input()
1516*
1517* Ask the user for input.
1518*******************************************************************************/
1519const char * user_input(Boolean * eof, const char * format, ...)
1520{
1521    char * result = NULL;  // return value
1522    va_list ap;
1523    char fake_buffer[2];
1524    int output_length;
1525    char * output_string = NULL;
1526    unsigned index;
1527    size_t size = 80;  // more than enough to input a hex address
1528    int c;
1529
1530    if (eof) {
1531        *eof = false;
1532    }
1533
1534    result = (char *)malloc(size);
1535    if (!result) {
1536        goto finish;
1537    }
1538    index = 0;
1539
1540    va_start(ap, format);
1541    output_length = vsnprintf(fake_buffer, 1, format, ap);
1542    va_end(ap);
1543
1544    output_string = (char *)malloc(output_length + 1);
1545    if (!output_string) {
1546        if (result) free(result);
1547        result = NULL;
1548        goto finish;
1549    }
1550
1551    va_start(ap, format);
1552    vsnprintf(output_string, output_length + 1, format, ap);
1553    va_end(ap);
1554
1555    fprintf(stderr, "%s ", output_string);
1556    fflush(stderr);
1557
1558    c = fgetc(stdin);
1559    while (c != '\n' && c != EOF) {
1560        if (index >= (size - 1)) {
1561            fprintf(stderr, "input line too long\n");
1562            if (result) free(result);
1563            result = NULL;
1564            goto finish;
1565        }
1566        result[index++] = (char)c;
1567        c = fgetc(stdin);
1568    }
1569
1570    result[index] = '\0';
1571
1572    if (c == EOF) {
1573        if (result) free(result);
1574        result = NULL;
1575        if (eof) {
1576            *eof = true;
1577        }
1578        goto finish;
1579    }
1580
1581finish:
1582    if (output_string) free(output_string);
1583
1584    return result;
1585}
1586
1587#if PRAGMA_MARK
1588#pragma mark Caches
1589#endif /* PRAGMA_MARK */
1590/*******************************************************************************
1591*******************************************************************************/
1592Boolean readSystemKextPropertyValues(
1593    CFStringRef        propertyKey,
1594    const NXArchInfo * arch,
1595    Boolean            forceUpdateFlag,
1596    CFArrayRef       * valuesOut)
1597{
1598    Boolean                result                  = false;
1599    CFArrayRef             sysExtensionsFolderURLs = OSKextGetSystemExtensionsFolderURLs();
1600    CFMutableArrayRef      values                  = NULL;  // must release
1601    CFStringRef            cacheBasename           = NULL;  // must release
1602    CFArrayRef             kexts                   = NULL;  // must release
1603    CFMutableDictionaryRef newDict                 = NULL;  // must release
1604    CFStringRef            kextPath                = NULL;  // must release
1605    CFTypeRef              value                   = NULL;  // do not release
1606    CFStringRef            kextVersion             = NULL;  // do not release
1607    CFIndex                count, i;
1608
1609    cacheBasename = CFStringCreateWithFormat(kCFAllocatorDefault,
1610        /* formatOptions */ NULL, CFSTR("%s%@"),
1611        _kKextPropertyValuesCacheBasename,
1612        propertyKey);
1613    if (!cacheBasename) {
1614        OSKextLogMemError();
1615        goto finish;
1616    }
1617
1618    if (OSKextGetUsesCaches() && !forceUpdateFlag) {
1619
1620       /* See if we have an up-to-date cache containing an array, and return
1621        * that if we have one.
1622        */
1623        if (_OSKextReadCache(sysExtensionsFolderURLs, cacheBasename,
1624            arch, _kOSKextCacheFormatCFXML, /* parseXML? */ true,
1625            (CFPropertyListRef *)&values)) {
1626
1627            if (values && CFGetTypeID(values) == CFArrayGetTypeID()) {
1628                result = true;
1629                goto finish;
1630            }
1631        }
1632    }
1633
1634    values = CFArrayCreateMutable(kCFAllocatorDefault, /* capacity */ 0,
1635        &kCFTypeArrayCallBacks);
1636    if (!values) {
1637        OSKextLogMemError();
1638        goto finish;
1639    }
1640
1641    kexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault,
1642    sysExtensionsFolderURLs);
1643
1644    if (!kexts) {
1645        // Create function should log error
1646        goto finish;
1647    }
1648
1649    count = CFArrayGetCount(kexts);
1650
1651    for (i = 0; i < count; i++) {
1652        OSKextRef aKext = (OSKextRef)CFArrayGetValueAtIndex(kexts, i);
1653
1654        SAFE_RELEASE_NULL(newDict);
1655        SAFE_RELEASE_NULL(kextPath);
1656        // do not release kextVersion
1657        kextVersion = NULL;
1658
1659        if ((OSKextGetSimulatedSafeBoot() || OSKextGetActualSafeBoot()) &&
1660            !OSKextIsLoadableInSafeBoot(aKext)) {
1661
1662            continue;
1663        }
1664        //??? if (OSKextGetLoadFailed(aKext)) continue;  -- don't have in OSKext
1665
1666        value = OSKextGetValueForInfoDictionaryKey(aKext, propertyKey);
1667        if (!value) {
1668            continue;
1669        }
1670
1671        newDict = CFDictionaryCreateMutable(
1672            kCFAllocatorDefault, 0,
1673            &kCFTypeDictionaryKeyCallBacks,
1674            &kCFTypeDictionaryValueCallBacks);
1675        if (!newDict) {
1676            goto finish;
1677        }
1678
1679        CFDictionarySetValue(newDict, CFSTR("Data"), value);
1680
1681        CFDictionarySetValue(newDict, CFSTR("CFBundleIdentifier"),
1682            OSKextGetIdentifier(aKext));
1683
1684        kextPath = copyKextPath(aKext);
1685        if (!kextPath) {
1686            goto finish;
1687        }
1688        CFDictionarySetValue(newDict, CFSTR("OSBundlePath"), kextPath);
1689
1690        kextVersion = OSKextGetValueForInfoDictionaryKey(aKext,
1691            CFSTR("CFBundleVersion"));
1692        if (!kextVersion) {
1693            goto finish;
1694        }
1695        CFDictionarySetValue(newDict, CFSTR("CFBundleVersion"),
1696            kextVersion);
1697
1698        CFArrayAppendValue(values, newDict);
1699    }
1700
1701    if (OSKextGetUsesCaches() || forceUpdateFlag) {
1702        _OSKextWriteCache(sysExtensionsFolderURLs, cacheBasename,
1703            arch, _kOSKextCacheFormatCFXML, values);
1704    }
1705
1706    result = true;
1707
1708finish:
1709    if (result && valuesOut && values) {
1710        *valuesOut = (CFArrayRef)CFRetain(values);
1711    }
1712
1713    SAFE_RELEASE(values);
1714    SAFE_RELEASE(cacheBasename);
1715    SAFE_RELEASE(kexts);
1716    SAFE_RELEASE(newDict);
1717    SAFE_RELEASE(kextPath);
1718
1719    return result;
1720}
1721
1722