1/*
2 * Copyright (c) 2006-2012 Apple Inc. All rights reserved.
3 *
4 *
5 * @APPLE_LICENSE_HEADER_START@
6 *
7 * This file contains Original Code and/or Modifications of Original Code
8 * as defined in and that are subject to the Apple Public Source License
9 * Version 2.0 (the 'License'). You may not use this file except in
10 * compliance with the License. Please obtain a copy of the License at
11 * http://www.opensource.apple.com/apsl/ and read it before using this
12 * file.
13 *
14 * The Original Code and all software distributed under the License are
15 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
19 * Please see the License for the specific language governing rights and
20 * limitations under the License.
21 *
22 * @APPLE_LICENSE_HEADER_END@
23 */
24/*
25 * FILE: bootcaches.c
26 * AUTH: Soren Spies (sspies)
27 * DATE: "spring" 2006
28 * DESC: routines for bootcache data
29 *
30 */
31
32#include <bless.h>
33#include <bootfiles.h>
34#include <IOKit/IOKitLib.h>
35
36#include <fcntl.h>
37#include <libgen.h>
38#include <notify.h>
39#include <paths.h>
40#include <mach/mach.h>
41#include <mach/kmod.h>
42#include <sys/attr.h>
43#include <sys/stat.h>
44#include <sys/sysctl.h>
45#include <sys/time.h>
46#include <sys/types.h>
47#include <unistd.h>
48
49#include <EFILogin/EFILogin.h>
50#include <System/libkern/mkext.h>
51#include <System/libkern/OSKextLibPrivate.h>
52#include <DiskArbitration/DiskArbitration.h>        // for UUID fetching
53#include <IOKit/kext/fat_util.h>
54#include <IOKit/kext/macho_util.h>
55#include <IOKit/storage/CoreStorage/CoreStorageUserLib.h>
56#include <IOKit/storage/CoreStorage/CoreStorageCryptoIDs.h>
57#include <IOKit/storage/CoreStorage/CSFullDiskEncryption.h>
58
59// Kext Management pieces from IOKitUser
60#include <IOKit/kext/OSKext.h>
61#include <IOKit/kext/OSKextPrivate.h>
62
63#include "bootcaches.h"         // includes CF
64#include "bootroot_internal.h"  // kBRUpdateOpts_t
65#include "fork_program.h"
66#include "kext_tools_util.h"
67#include "safecalls.h"
68
69// only used here
70#define kBRDiskArbMaxRetries   (10)
71
72static void removeTrailingSlashes(char * path);
73#if DEV_KERNEL_SUPPORT
74static int  getExtraKernelCachePaths(struct bootCaches *caches);
75#endif
76
77// XX These are used often together, rely on 'finish', and should be all-caps.
78// 'dst must be char[PATH_MAX]'
79// COMPILE_TIME_ASSERT fails for get_locres_info() due to char[size] ~ char*
80#define pathcpy(dst, src) do { \
81        /* COMPILE_TIME_ASSERT(sizeof(dst) == PATH_MAX); */ \
82        if (strlcpy(dst, src, PATH_MAX) >= PATH_MAX)  goto finish; \
83    } while(0)
84#define pathcat(dst, src) do { \
85        /* COMPILE_TIME_ASSERT(sizeof(dst) == PATH_MAX); */ \
86        if (strlcat(dst, src, PATH_MAX) >= PATH_MAX)  goto finish; \
87    } while(0)
88
89
90// http://lists.freebsd.org/pipermail/freebsd-hackers/2004-February/005627.html
91#define LOGERRxlate(ctx1, ctx2, errval) do { \
92        char *c2cpy = ctx2, ctx[256]; \
93        if (ctx2 != NULL) { \
94            snprintf(ctx, sizeof(ctx), "%s: %s", ctx1, c2cpy); \
95        } else { \
96            snprintf(ctx, sizeof(ctx), "%s", ctx1); \
97        } \
98        /* if necessary, modify passed-in argument so errno is returned */  \
99        if (errval == -1)       errval = errno;  \
100        OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, \
101                  "%s: %s", ctx, strerror(errval)); \
102    } while(0)
103
104/******************************************************************************
105* destroyCaches cleans up a bootCaches structure
106******************************************************************************/
107void
108destroyCaches(struct bootCaches *caches)
109{
110    if (caches) {
111        if (caches->cachefd != -1)  close(caches->cachefd);
112        if (caches->cacheinfo)      CFRelease(caches->cacheinfo);
113        if (caches->miscpaths)      free(caches->miscpaths);  // free strings
114        if (caches->rpspaths)       free(caches->rpspaths);
115        if (caches->exts)           free(caches->exts);
116        if (caches->csfde_uuid)     CFRelease(caches->csfde_uuid);
117#if DEV_KERNEL_SUPPORT
118        if (caches->extraKernelCachePaths) free(caches->extraKernelCachePaths);
119#endif
120        free(caches);
121    }
122}
123
124/******************************************************************************
125* readCaches checks for and reads bootcaches.plist
126* it will also create directories for the caches in question if needed
127******************************************************************************/
128// used for turning /foo/bar into :foo:bar for kTSCacheDir entries (see awk(1))
129static void gsub(char old, char new, char *s)
130{
131    char *p;
132
133    while((p = s++) && *p)
134        if (*p == old)
135            *p = new;
136}
137
138static int
139MAKE_CACHEDPATH(cachedPath *cpath, struct bootCaches *caches,
140                CFStringRef relstr)
141{
142    int rval;
143    size_t fullLen;
144    char tsname[NAME_MAX];
145
146    // check params
147    rval = EINVAL;
148    if (!(relstr))     goto finish;
149    if (CFGetTypeID(relstr) != CFStringGetTypeID())     goto finish;
150
151    // extract rpath
152    rval = EOVERFLOW;
153    if (!CFStringGetFileSystemRepresentation(relstr, cpath->rpath,
154                                             sizeof(cpath->rpath))) {
155        goto finish;
156    }
157
158    // tspath: rpath with '/' -> ':'
159    if (strlcpy(tsname, cpath->rpath, sizeof(tsname)) >= sizeof(tsname))
160        goto finish;
161    gsub('/', ':', tsname);
162    fullLen = snprintf(cpath->tspath, sizeof(cpath->tspath), "%s/%s/%s",
163                       kTSCacheDir, caches->fsys_uuid, tsname);
164    if (fullLen >= sizeof(cpath->tspath))
165        goto finish;
166
167    rval = 0;
168
169finish:
170    return rval;
171}
172
173// validate bootcaches.plist dict; data -> struct
174// caller properly frees the structure if we fail
175static int
176extractProps(struct bootCaches *caches, CFDictionaryRef bcDict)
177{
178    int rval = ENODEV;
179    CFDictionaryRef dict;   // don't release
180    CFIndex keyCount;       // track whether we've handled all keys
181    CFIndex rpsindex = 0;   // index into rps; compared to caches->nrps @ end
182    CFStringRef str;        // used to point to objects owned by others
183    CFStringRef createdStr = NULL;
184
185    rval = EFTYPE;
186    keyCount = CFDictionaryGetCount(bcDict);        // start with the top
187    caches->exts = NULL;
188    caches->nexts = 0;
189#if DEV_KERNEL_SUPPORT
190    caches->kernelsCount = 0;
191#endif
192
193    // process keys for paths read "before the booter"
194    dict = (CFDictionaryRef)CFDictionaryGetValue(bcDict, kBCPreBootKey);
195    if (dict) {
196        CFArrayRef apaths;
197        CFIndex miscindex = 0;
198
199        if (CFGetTypeID(dict) != CFDictionaryGetTypeID())  goto finish;
200        // only "Additional Paths" can contain > 1 path
201        caches->nmisc = (int)CFDictionaryGetCount(dict);  // init w/1 path/key
202        keyCount += CFDictionaryGetCount(dict);
203
204        // look at variable-sized member first -> right size for miscpaths
205        apaths = (CFArrayRef)CFDictionaryGetValue(dict, kBCAdditionalPathsKey);
206        if (apaths) {
207            CFIndex acount;
208
209            if (CFArrayGetTypeID() != CFGetTypeID(apaths))  goto finish;
210            acount = CFArrayGetCount(apaths);
211            // total "misc" paths = # of keyed paths + # additional paths
212            caches->nmisc += acount - 1;   // kBCAdditionalPathsKey not a path
213
214            if ((unsigned int)caches->nmisc > INT_MAX/sizeof(*caches->miscpaths)) goto finish;
215            caches->miscpaths = (cachedPath*)calloc(caches->nmisc,
216                sizeof(*caches->miscpaths));
217            if (!caches->miscpaths)  goto finish;
218
219            for (/*miscindex = 0 (above)*/; miscindex < acount; miscindex++) {
220                str = CFArrayGetValueAtIndex(apaths, miscindex);
221                // MAKE_CACHEDPATH checks str != NULL && str's type
222                MAKE_CACHEDPATH(&caches->miscpaths[miscindex], caches, str);
223            }
224            keyCount--; // AdditionalPaths sub-key
225        } else {
226            // allocate enough for the top-level keys (nothing variable-sized)
227            if ((unsigned int)caches->nmisc > INT_MAX/sizeof(*caches->miscpaths)) goto finish;
228            caches->miscpaths = calloc(caches->nmisc, sizeof(cachedPath));
229            if (!caches->miscpaths)     goto finish;
230        }
231
232        str = (CFStringRef)CFDictionaryGetValue(dict, kBCLabelKey);
233        if (str) {
234            MAKE_CACHEDPATH(&caches->miscpaths[miscindex], caches, str);
235            caches->label = &caches->miscpaths[miscindex];
236
237            miscindex++;    // get ready for the next guy
238#pragma unused(miscindex)
239            keyCount--;     // DiskLabel is dealt with
240        }
241
242        // add new keys here
243        keyCount--;     // preboot dict
244    }
245
246
247    // process booter keys
248    dict = (CFDictionaryRef)CFDictionaryGetValue(bcDict, kBCBootersKey);
249    if (dict) {
250        if (CFGetTypeID(dict) != CFDictionaryGetTypeID())  goto finish;
251        keyCount += CFDictionaryGetCount(dict);
252
253        str = (CFStringRef)CFDictionaryGetValue(dict, kBCEFIBooterKey);
254        if (str) {
255            MAKE_CACHEDPATH(&caches->efibooter, caches, str);
256
257            keyCount--;     // EFIBooter is dealt with
258        }
259
260        str = (CFStringRef)CFDictionaryGetValue(dict, kBCOFBooterKey);
261        if (str) {
262            MAKE_CACHEDPATH(&caches->ofbooter, caches, str);
263
264            keyCount--;     // BootX, check
265        }
266
267        // add new booters here
268        keyCount--;     // booters dict
269    }
270
271    // process keys for paths read "after the booter [is loaded]"
272    // these are read by the booter proper, which determines which
273    // of the Rock, Paper, Scissors directories is most current
274    dict = (CFDictionaryRef)CFDictionaryGetValue(bcDict, kBCPostBootKey);
275    if (dict) {
276        CFDictionaryRef mkDict, erDict;
277        CFArrayRef apaths;
278        CFIndex acount;
279        Boolean isKernelcache = false;
280        int kcacheKeys = 0;
281
282        if (CFGetTypeID(dict) != CFDictionaryGetTypeID())  goto finish;
283
284        // we must deal with all sub-keys
285        keyCount += CFDictionaryGetCount(dict);
286
287        // Figure out how many files will be watched/copied via rpspaths
288        // start by assuming each key provides one path to watch/copy
289        caches->nrps = (int)CFDictionaryGetCount(dict);
290
291        // AdditionalPaths: 1 key -> extra RPS entries
292        apaths = (CFArrayRef)CFDictionaryGetValue(dict, kBCAdditionalPathsKey);
293        if (apaths) {
294            if (CFArrayGetTypeID() != CFGetTypeID(apaths))  goto finish;
295            acount = CFArrayGetCount(apaths);
296
297            // add in extra keys
298            // "additional paths" array is not itself a path -> add one less
299            caches->nrps += (acount - 1);
300        }
301
302
303        // EncryptedRoot has 5 subkeys
304        erDict=(CFDictionaryRef)CFDictionaryGetValue(dict,kBCEncryptedRootKey);
305        if (erDict) {
306            if (CFGetTypeID(erDict)!=CFDictionaryGetTypeID())   goto finish;
307            // erDict has one slot, but two required & copied sub-properties
308            caches->nrps++;
309
310            // the other three keys lead to a single localized resources cache
311            if (CFDictionaryGetValue(erDict,kBCCSFDELocalizationSrcKey)) {
312                caches->nrps++;
313            }
314        }
315
316        // finally allocate correctly-sized rpspaths
317        if ((unsigned int)caches->nrps > INT_MAX/sizeof(*caches->rpspaths))
318            goto finish;
319        caches->rpspaths = (cachedPath*)calloc(caches->nrps,
320                            sizeof(*caches->rpspaths));
321        if (!caches->rpspaths)  goto finish;
322
323
324        // Load up rpspaths
325
326        // populate rpspaths with AdditionalPaths; leave rpsindex -> avail
327        // (above: apaths type-checked, rpsindex initialized to zero)
328        if (apaths) {
329            for (; rpsindex < acount; rpsindex++) {
330                str = CFArrayGetValueAtIndex(apaths, rpsindex);
331                MAKE_CACHEDPATH(&caches->rpspaths[rpsindex], caches, str);
332            }
333            keyCount--;     // handled AdditionalPaths
334        }
335
336        // com.apple.Boot.plist
337        str = (CFStringRef)CFDictionaryGetValue(dict, kBCBootConfigKey);
338        if (str) {
339            MAKE_CACHEDPATH(&caches->rpspaths[rpsindex], caches, str);
340            caches->bootconfig = &caches->rpspaths[rpsindex++];
341            keyCount--;     // handled BootConfig
342        }
343
344        // EncryptedRoot items
345        // two sub-keys required; one optional; one set of three optional
346        if (erDict) {
347            CFNumberRef boolRef;
348            keyCount += CFDictionaryGetCount(erDict);
349
350            str = CFDictionaryGetValue(erDict, kBCCSFDEPropertyCacheKey);
351            if (str) {
352                MAKE_CACHEDPATH(&caches->rpspaths[rpsindex], caches, str);
353                caches->erpropcache = &caches->rpspaths[rpsindex++];
354                keyCount--;
355            }
356
357            // !RootVolumePropertyCache => enable "timestamp only" optimization
358            boolRef = CFDictionaryGetValue(erDict,kBCCSFDERootVolPropCacheKey);
359            if (boolRef) {
360                if (CFGetTypeID(boolRef) == CFBooleanGetTypeID()) {
361                    caches->erpropTSOnly = CFEqual(boolRef, kCFBooleanFalse);
362                    keyCount--;
363                } else {
364                    goto finish;
365                }
366            }
367
368            // 8163405: non-localized resources
369            str = CFDictionaryGetValue(erDict, kBCCSFDEDefResourcesDirKey);
370            // XX check old key name for now
371            if (!str) str=CFDictionaryGetValue(erDict,CFSTR("ResourcesDir"));
372            if (str) {
373                MAKE_CACHEDPATH(&caches->rpspaths[rpsindex], caches, str);
374                caches->efidefrsrcs = &caches->rpspaths[rpsindex++];
375                keyCount--;
376            }
377
378            // localized resource cache
379            str = CFDictionaryGetValue(erDict,kBCCSFDELocRsrcsCacheKey);
380            if (str) {
381                MAKE_CACHEDPATH(&caches->rpspaths[rpsindex], caches, str);
382                caches->efiloccache = &caches->rpspaths[rpsindex++];
383                keyCount--;
384
385                // localization source material (required)
386                str = CFDictionaryGetValue(erDict, kBCCSFDELocalizationSrcKey);
387                if (str && CFGetTypeID(str) == CFStringGetTypeID() &&
388                        CFStringGetFileSystemRepresentation(str,
389                            caches->locSource, sizeof(caches->locSource))) {
390                    keyCount--;
391                } else {
392                    goto finish;
393                }
394
395                // localization prefs file (required)
396                str = CFDictionaryGetValue(erDict, kBCCSFDELanguagesPrefKey);
397                if (str && CFGetTypeID(str) == CFStringGetTypeID() &&
398                    CFStringGetFileSystemRepresentation(str, caches->locPref,
399                                                sizeof(caches->locPref))) {
400                    keyCount--;
401                } else {
402                    goto finish;
403                }
404
405                // background image (optional)
406                str = CFDictionaryGetValue(erDict, kBCCSFDEBackgroundImageKey);
407                if (str && CFGetTypeID(str) == CFStringGetTypeID() &&
408                    CFStringGetFileSystemRepresentation(str, caches->bgImage,
409                                                sizeof(caches->bgImage))) {
410                    keyCount--;
411                }
412            }
413
414            keyCount--;     // handled EncryptedRoot
415        }
416
417        // we support any one of three kext archival methods
418        kcacheKeys = 0;
419        if (CFDictionaryContainsKey(dict, kBCMKextKey)) kcacheKeys++;
420        if (CFDictionaryContainsKey(dict, kBCMKext2Key)) kcacheKeys++;
421        if (CFDictionaryContainsKey(dict, kBCKernelcacheV1Key)) kcacheKeys++;
422        if (CFDictionaryContainsKey(dict, kBCKernelcacheV2Key)) kcacheKeys++;
423        if (CFDictionaryContainsKey(dict, kBCKernelcacheV3Key)) kcacheKeys++;
424
425        if (kcacheKeys > 1) {
426            // don't support multiple types of kernel caching ...
427            goto finish;
428        }
429
430      /* Handle the "Kernelcache" key for prelinked kernels for Lion and
431       * later, the "MKext2 key" for format-2 mkext on Snow Leopard, and the
432       * original "MKext" key for format-1 mkexts prior to SnowLeopard.
433       */
434        do {
435            mkDict = (CFDictionaryRef)CFDictionaryGetValue(dict, kBCKernelcacheV1Key);
436            if (!mkDict) {
437                mkDict = (CFDictionaryRef)CFDictionaryGetValue(dict, kBCKernelcacheV2Key);
438            }
439            if (!mkDict) {
440                mkDict = (CFDictionaryRef)CFDictionaryGetValue(dict, kBCKernelcacheV3Key);
441            }
442
443            if (mkDict) {
444                isKernelcache = true;
445                break;
446            }
447
448            mkDict = (CFDictionaryRef)CFDictionaryGetValue(dict, kBCMKext2Key);
449            if (mkDict) break;
450
451            mkDict = (CFDictionaryRef)CFDictionaryGetValue(dict, kBCMKextKey);
452            if (mkDict) break;
453        } while (0);
454
455        if (mkDict) {
456            if (CFGetTypeID(mkDict) != CFDictionaryGetTypeID()) {
457                goto finish;
458            }
459
460            // path to the cache itself
461            // currently /System/Library/Caches/com.apple.kext.caches/Startup/kernelcache
462            str = (CFStringRef)CFDictionaryGetValue(mkDict, kBCPathKey);
463            MAKE_CACHEDPATH(&caches->rpspaths[rpsindex], caches, str);   // M
464            caches->kext_boot_cache_file = &caches->rpspaths[rpsindex++];
465#pragma unused(rpsindex)
466
467            // Starting with Kernelcache v1.3 kBCExtensionsDirKey is a key for
468            // an array of paths to extensions directory. Pre v1.3 it is just
469            // a string equal to "/System/Library/Extensions"
470            size_t  bufsize = 0;
471            apaths = (CFArrayRef)CFDictionaryGetValue(mkDict, kBCExtensionsDirKey);
472            if (apaths && CFArrayGetTypeID() == CFGetTypeID(apaths)) {
473                int     i;
474                char    *bufptr;
475                char    tempbuf[PATH_MAX];
476
477                caches->nexts = (int) CFArrayGetCount(apaths);
478                if (caches->nexts == 0)    goto finish;
479
480                caches->exts = malloc(caches->nexts * PATH_MAX);
481                if (caches->exts == NULL) {
482                    OSKextLogMemError();
483                    goto finish;
484                }
485                bufptr = caches->exts;
486
487                for (i = 0; i < caches->nexts; i++) {
488                    str = CFArrayGetValueAtIndex(apaths, i);
489                    if (!str || CFGetTypeID(str) != CFStringGetTypeID()) {
490                        goto finish;
491                    }
492                    if (!CFStringGetFileSystemRepresentation(str, tempbuf,
493                                                             sizeof(tempbuf))) {
494                        goto finish;
495                    }
496                    pathcpy(bufptr, tempbuf);
497                    bufsize += (strlen(tempbuf) + 1);
498                    bufptr += (strlen(tempbuf) + 1);
499                }
500            }
501            else {
502                // Pre v1.3 so we're dealing with just 1 path
503                caches->exts = malloc(PATH_MAX);
504                if (caches->exts == NULL) {
505                    OSKextLogMemError();
506                    goto finish;
507                }
508                caches->nexts = 1;
509                str = (CFStringRef)CFDictionaryGetValue(mkDict, kBCExtensionsDirKey);
510                if (!str || CFGetTypeID(str) != CFStringGetTypeID()) {
511                    goto finish;
512                }
513                if (!CFStringGetFileSystemRepresentation(str, caches->exts,
514                                                     PATH_MAX)) {
515                    goto finish;
516                }
517                bufsize = (strlen(caches->exts) + 1);
518            }
519            // trim if possible
520            if (bufsize) {
521                caches->exts = reallocf(caches->exts, bufsize);
522                if (caches->exts == NULL) {
523                    OSKextLogMemError();
524                    goto finish;
525                }
526            }
527
528            // kernelcaches have a kernel path key, which we set up by hand
529            if (isKernelcache) {
530                // <= 10.9 - /Volumes/foo/mach_kernel
531                // > 10.9 - /Volumes/foo/System/Library/Kernels/kernel
532                str = (CFStringRef)CFDictionaryGetValue(mkDict,
533                                                        kBCKernelPathKey);
534                if (!str || CFGetTypeID(str) != CFStringGetTypeID()) {
535                    goto finish;
536                }
537
538                if (!CFStringGetFileSystemRepresentation(str, caches->kernelpath,
539                                                         sizeof(caches->kernelpath))) {
540                    goto finish;
541                }
542#if DEV_KERNEL_SUPPORT
543                getExtraKernelCachePaths(caches);
544#endif
545
546            }
547
548            // Archs are fetched from the cacheinfo dictionary when needed
549            keyCount--;     // mkext, mkext2, or kernelcache key handled
550        }
551
552        keyCount--;     // postBootPaths handled
553    }
554
555    if (keyCount == 0 && (unsigned)rpsindex == caches->nrps) {
556        rval = 0;
557        caches->cacheinfo = CFRetain(bcDict);   // for archs, etc
558    }
559
560finish:
561    if (createdStr)     CFRelease(createdStr);
562    if (rval != 0 && caches->exts != NULL) {
563        free(caches->exts);
564        caches->exts = NULL;
565        caches->nexts = 0;
566    }
567
568    return rval;
569}
570
571#if DEV_KERNEL_SUPPORT
572
573/* getExtraKernelCachePaths:
574 * creates and populates extraKernelCachePaths.  Also sets kernelsCount.
575 * Looks for any kernel files in the format of "kernel.SUFFIX" in
576 * /System/Library/Kernels then uses SUFFIX to create the corresponding
577 * kernelcache.SUFFIX path.
578 *
579 * NOTE, we already have the kernelcache path for the "kernel" file as part of
580 * rpspaths so we do not add it to extraKernelCachePaths (thus "extra").  And
581 * that is why nekcp is one less than bootCaches.kernelsCount.
582 */
583static int
584getExtraKernelCachePaths(struct bootCaches *caches)
585{
586    int                 result              = -1;
587    int                 maxCacheCount       = 0;
588    int                 cacheIndex          = 0;
589    CFURLRef            kernURL             = NULL; // must release
590    CFURLRef            kernParentURL       = NULL; // must release
591    CFURLEnumeratorRef  myEnumerator        = NULL; // must release
592    CFURLRef            enumURL             = NULL; // do not release
593    CFStringRef         tmpCFString         = NULL; // must release
594    CFArrayRef          resultArray         = NULL; // must release
595    char *              tmpKernelPath       = NULL; // must free
596    char *              tmpCachePath        = NULL; // must free
597    char *              suffixPtr           = NULL; // must free
598
599    caches->kernelsCount = 0;
600    caches->nekcp = 0;
601
602    tmpKernelPath = malloc(PATH_MAX);
603    tmpCachePath = malloc(PATH_MAX);
604
605    if (tmpKernelPath == NULL || tmpCachePath == NULL)  goto finish;
606
607    if (strlcpy(tmpKernelPath, caches->root, PATH_MAX) >= PATH_MAX)
608        goto finish;
609    if (strlcat(tmpKernelPath, caches->kernelpath, PATH_MAX) >= PATH_MAX)
610        goto finish;
611
612    kernURL = CFURLCreateFromFileSystemRepresentation(
613                                                      NULL,
614                                                      (const UInt8 *)tmpKernelPath,
615                                                      strlen(tmpKernelPath),
616                                                      true );
617    if (kernURL == NULL) {
618        goto finish;
619    }
620
621    kernParentURL = CFURLCreateCopyDeletingLastPathComponent(NULL,
622                                                             kernURL );
623    if (kernParentURL == NULL) {
624        goto finish;
625    }
626
627    // bail out if the parent is not "Kernels"
628    tmpCFString = CFURLCopyLastPathComponent(kernParentURL);
629    if (tmpCFString == NULL ||
630        CFStringCompare(tmpCFString, CFSTR("Kernels"), 0) != kCFCompareEqualTo) {
631        goto finish;
632    }
633
634    myEnumerator = CFURLEnumeratorCreateForDirectoryURL(
635                                                        NULL,
636                                                        kernParentURL,
637                                                        kCFURLEnumeratorDefaultBehavior,
638                                                        NULL );
639    if (myEnumerator == NULL) {
640        goto finish;
641    }
642
643    while (CFURLEnumeratorGetNextURL(myEnumerator,
644                                     &enumURL,
645                                     NULL) == kCFURLEnumeratorSuccess) {
646        SAFE_RELEASE_NULL(tmpCFString);
647        SAFE_FREE_NULL(suffixPtr);
648
649        // valid kernel name must be in the form of:
650        // "kernel" or
651        // "kernel.SUFFIX"
652        tmpCFString = CFURLCopyLastPathComponent(enumURL);
653        if (tmpCFString == NULL)       continue;
654
655        if (kCFCompareEqualTo == CFStringCompare(tmpCFString,
656                                                 CFSTR("kernel"),
657                                                 kCFCompareAnchored)) {
658            // this is "kernel" so count it and move on
659            caches->kernelsCount++;
660            continue;
661        }
662
663        // only want kernel.SUFFIX from here on
664        if (CFStringHasPrefix(tmpCFString,
665                              CFSTR("kernel.")) == false) {
666            continue;
667        }
668
669        // skip any kernels with more than one '.' character.
670        // For example: kernel.foo.bar is not a valid kernel name
671        SAFE_RELEASE_NULL(resultArray);
672        resultArray = CFStringCreateArrayWithFindResults(
673                                                         NULL,
674                                                         tmpCFString,
675                                                         CFSTR("."),
676                                                         CFRangeMake(0, CFStringGetLength(tmpCFString)),
677                                                         0);
678        if (resultArray && CFArrayGetCount(resultArray) > 1) {
679            continue;
680        }
681        caches->kernelsCount++;
682
683        SAFE_RELEASE(tmpCFString);
684        tmpCFString =  CFURLCopyPathExtension(enumURL);
685        if (tmpCFString == NULL)   continue;
686
687        // build kernelcache file for each valid kernel suffix we find.
688        suffixPtr = createUTF8CStringForCFString(tmpCFString);
689        if (suffixPtr == NULL)    continue;
690
691        if (strlcpy(tmpCachePath, caches->kext_boot_cache_file->rpath, PATH_MAX) >= PATH_MAX)
692            continue;
693        if (strlcat(tmpCachePath, ".", PATH_MAX) >= PATH_MAX)
694            continue;
695        if (strlcat(tmpCachePath, suffixPtr, PATH_MAX) >= PATH_MAX)
696            continue;
697
698        SAFE_RELEASE_NULL(tmpCFString);
699        tmpCFString = CFStringCreateWithCString(kCFAllocatorDefault,
700                                                tmpCachePath,
701                                                kCFStringEncodingUTF8);
702        if (tmpCFString == NULL)   continue;
703        if (cacheIndex >= maxCacheCount) {
704            cachedPath *    tempCPPtr;
705
706            maxCacheCount += 2;
707            tempCPPtr = (cachedPath *) calloc(maxCacheCount, sizeof(cachedPath));
708            if (tempCPPtr == NULL)  goto finish;
709
710            if (caches->extraKernelCachePaths) {
711                // copy existing cache paths into new buffer
712                bcopy(caches->extraKernelCachePaths,
713                      tempCPPtr,
714                      sizeof(*caches->extraKernelCachePaths) * caches->nekcp);
715                SAFE_FREE(caches->extraKernelCachePaths);
716            }
717            caches->extraKernelCachePaths = tempCPPtr;
718        }
719
720        MAKE_CACHEDPATH(&caches->extraKernelCachePaths[cacheIndex],
721                        caches,
722                        tmpCFString);
723        cacheIndex++;
724        caches->nekcp++;
725   } // while loop
726
727    result = 0;
728
729finish:
730    if (result != 0) {
731        SAFE_FREE_NULL(caches->extraKernelCachePaths);
732        caches->nekcp = 0;
733    }
734    SAFE_RELEASE(kernURL);
735    SAFE_RELEASE(kernParentURL);
736    SAFE_RELEASE(myEnumerator);
737    SAFE_RELEASE(tmpCFString);
738    SAFE_RELEASE(resultArray);
739    SAFE_FREE(tmpKernelPath);
740    SAFE_FREE(tmpCachePath);
741    SAFE_FREE(suffixPtr);
742
743#if 0
744    OSKextLogCFString(NULL,
745                      kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
746                      CFSTR("%s: kernelsCount %d nekcp %d"),
747                      __func__,
748                      caches->kernelsCount,
749                      caches->nekcp);
750    if (result == 0) {
751        int     j;
752        for (j = 0; j < cacheIndex; j++) {
753            OSKextLogCFString(NULL,
754                              kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
755                              CFSTR("%s: extraKernelCachePaths at %d \"%s\""),
756                              __func__,
757                              j,
758                              caches->extraKernelCachePaths[j].rpath);
759        }
760    }
761#endif
762
763    return result;
764}
765
766#endif
767
768// helper to create cache dirs as needed
769static int
770ensureCacheDirs(struct bootCaches *caches)
771{
772    int errnum, result = ELAST + 1;
773    struct statfs sfs;
774    char *errname;
775    struct stat sb;
776    char cachedir[PATH_MAX], uuiddir[PATH_MAX];      // bootstamps, csfde
777    Boolean checkOnly = false;
778
779    // don't create new cache directories if owners are disabled
780    errname = caches->root;
781    if (statfs(caches->root, &sfs) == 0) {
782        if (sfs.f_flags & MNT_IGNORE_OWNERSHIP) {
783            checkOnly = true;
784        }
785    } else {
786        result = errno; goto finish;
787    }
788
789    // bootstamps directory
790    // (always made because it's used by libbless on non-BootRoot for ESP)
791    errname = kTSCacheDir;
792    pathcpy(cachedir, caches->root);
793    pathcat(cachedir, kTSCacheDir);
794    pathcpy(uuiddir, cachedir);
795    pathcat(uuiddir, "/");
796    pathcat(uuiddir, caches->fsys_uuid);
797    if ((errnum = stat(uuiddir, &sb))) {
798        if (errno == ENOENT) {
799            if (checkOnly) {
800                result = ENOTSUP; goto finish;
801            }
802            // attempt to clean up cache dir to eliminate stale UUID dirs
803            if (stat(cachedir, &sb) == 0) {
804                (void)sdeepunlink(caches->cachefd, cachedir);
805            }
806            // s..mkdir ensures the cache directory is on the same volume
807            if ((errnum = sdeepmkdir(caches->cachefd,uuiddir,kCacheDirMode))){
808                result = errnum; goto finish;
809            }
810        } else {
811            result = errnum; goto finish;
812        }
813    }
814
815    // create /S/L/Caches/com.apple.corestorage as necessary
816    if (caches->erpropcache) {
817        errname = caches->erpropcache->rpath;
818        pathcpy(cachedir, caches->root);
819        pathcat(cachedir, dirname(caches->erpropcache->rpath));
820        errname = cachedir;
821        if ((-1 == stat(cachedir, &sb))) {
822            if (errno == ENOENT) {
823                if (checkOnly) {
824                    result = ENOTSUP; goto finish;
825                }
826                // s..mkdir ensures cachedir is on the same volume
827                errnum=sdeepmkdir(caches->cachefd,cachedir,kCacheDirMode);
828                if (errnum) {
829                    result = errnum; goto finish;
830                }
831            } else {
832                result = errno; goto finish;
833            }
834        }
835    }
836
837    // success
838    errname = NULL;
839    result = 0;
840
841// XX need to centralize this sort of error decoding (w/9217695?)
842finish:
843    if (result) {
844        LOGERRxlate(errname, NULL, result);
845
846        // so kextcache -u doesn't claim bootcaches.plist didn't exist, etc
847        errno = 0;
848    }
849
850    return result;
851}
852
853static CFDictionaryRef
854copy_dict_from_fd(int fd, struct stat *sb)
855{
856    CFDictionaryRef rval = NULL;
857    void *buf = NULL;
858    CFDataRef data = NULL;
859    CFDictionaryRef dict = NULL;
860
861    // read the plist
862    if (sb->st_size > UINT_MAX || sb->st_size > LONG_MAX)   goto finish;
863    if (!(buf = malloc((size_t)sb->st_size)))               goto finish;
864    if (read(fd, buf, (size_t)sb->st_size) != sb->st_size)
865        goto finish;
866    if (!(data = CFDataCreate(nil, buf, (long)sb->st_size)))
867        goto finish;
868
869    // Sec: see 4623105 & related for an assessment of our XML parsers
870    dict = (CFDictionaryRef)
871            CFPropertyListCreateWithData(nil,
872                                         data,
873                                         kCFPropertyListImmutable,
874                                         NULL,
875                                         NULL);
876    if (!dict || CFGetTypeID(dict)!=CFDictionaryGetTypeID()) {
877        goto finish;
878    }
879
880    rval = CFRetain(dict);
881
882finish:
883    if (dict)   CFRelease(dict);      // CFRetain()'d on success
884    if (data)   CFRelease(data);
885    if (buf)    free(buf);
886
887    return rval;
888}
889
890/*
891 * readBootCaches() reads a volumes bootcaches.plist file and returns
892 * the contents in a new struct bootCaches.  Because it returns a pointer,
893 * it stores a more precise error code in errno.
894 */
895struct bootCaches*
896readBootCaches(char *volRoot, BRUpdateOpts_t opts)
897{
898    struct bootCaches *rval = NULL, *caches = NULL;
899    int errnum = ELAST + 1;
900    char *errmsg;
901    struct statfs rootsfs;
902    struct stat sb;
903    char bcpath[PATH_MAX];
904    CFDictionaryRef bcDict = NULL;
905    uuid_t vol_uuid;
906
907    errmsg = "allocation failure";
908    caches = calloc(1, sizeof(*caches));
909    if (!caches)            goto finish;
910    caches->cachefd = -1;       // set cardinal (fd 0 valid)
911    pathcpy(caches->root, volRoot);
912
913    errmsg = "error opening " kBootCachesPath;
914    pathcpy(bcpath, caches->root);
915    pathcat(bcpath, kBootCachesPath);
916    // Sec: cachefd lets us validate data, operations
917    caches->cachefd = (errnum = open(bcpath, O_RDONLY|O_EVTONLY));
918    if (errnum == -1) {
919        if (errno == ENOENT) {
920            // let kextcache -u log this special case
921            errmsg = NULL;
922        }
923        goto finish;
924    }
925
926    // check the owner and mode (fstat() to insure it's the same file)
927    // w/Leopard, root can see all the way to the disk; 99 -> truly unknown
928    // note: 'sudo cp mach_kernel /Volumes/disrespected/' should -> error
929    if (fstatfs(caches->cachefd, &rootsfs)) {
930        goto finish;
931    }
932    if (fstat(caches->cachefd, &sb)) {
933        goto finish;
934    }
935    // stash the timestamps for later reference (detect bc.plist changes)
936    caches->bcTime = sb.st_mtimespec;      // stash so we can detect changes
937    if (rootsfs.f_flags & MNT_QUARANTINE) {
938        errmsg = kBootCachesPath " quarantined";
939        goto finish;
940    }
941    if (sb.st_uid != 0) {
942        errmsg = kBootCachesPath " not owned by root; no rebuilds";
943        goto finish;
944    }
945    if (sb.st_mode & S_IWGRP || sb.st_mode & S_IWOTH) {
946        errmsg = kBootCachesPath " writable by non-root";
947        goto finish;
948    }
949
950    // get UUIDs & other info
951    errmsg = "error obtaining storage information";
952    if ((errnum = copyVolumeInfo(volRoot, &vol_uuid, &caches->csfde_uuid,
953                                 caches->bsdname, caches->defLabel))){
954        errno = errnum; goto finish;
955    }
956    if ((opts & kBRAnyBootStamps) == 0) {
957        uuid_unparse_upper(vol_uuid, caches->fsys_uuid);
958    }
959
960
961    // plist -> dictionary
962    errmsg = "error reading " kBootCachesPath;
963    bcDict = copy_dict_from_fd(caches->cachefd, &sb);
964    if (!bcDict)        goto finish;
965
966
967    // error returned via errno now all that matters
968    errmsg = NULL;
969
970    // extractProps returns EFTYPE if the contents were whack
971    // this function returns NULL on failure -> sends err# via errno :P
972    if ((errnum = extractProps(caches, bcDict))) {
973        errno = errnum; goto finish;
974    }
975
976
977    // success!
978    rval = caches;
979
980finish:
981    // report any error message
982    if (errmsg) {
983        if (errnum == -1) {
984            OSKextLog(/* kext */ NULL,
985                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
986                 "%s: %s: %s.", caches->root, errmsg, strerror(errno));
987        } else {
988            OSKextLog(/* kext */ NULL,
989                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
990                "%s: %s.", caches->root, errmsg);
991        }
992    }
993
994    // clean up (unwind in reverse order of allocation)
995    if (bcDict)     CFRelease(bcDict);  // extractProps() retains for struct
996
997    // if things went awry, free anything associated with 'caches'
998    if (!rval) {
999        destroyCaches(caches);      // closes cachefd if needed
1000    }
1001
1002    return rval;
1003}
1004
1005struct bootCaches*
1006readBootCachesForDADisk(DADiskRef dadisk)
1007{
1008    struct bootCaches *rval = NULL;
1009    CFDictionaryRef ddesc = NULL;
1010    CFURLRef volURL;        // owned by dict; don't release
1011    char volRoot[PATH_MAX];
1012    int ntries = 0;
1013
1014    // Work around inability to know if diskarb is up (4243227) by retrying
1015    // until data is available.  5454260 tracks removal of the workaround.
1016    // kexd's vol_appeared() also filters volumes w/o mount points.
1017    do {
1018        ddesc = DADiskCopyDescription(dadisk);
1019        if (!ddesc)     goto finish;
1020        volURL = CFDictionaryGetValue(ddesc,kDADiskDescriptionVolumePathKey);
1021        if (volURL) {
1022            break;
1023        } else {
1024            sleep(1);
1025            CFRelease(ddesc);
1026            ddesc = NULL;
1027        }
1028    } while (++ntries < kBRDiskArbMaxRetries);
1029
1030    if (!volURL) {
1031        OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1032            "Disk description missing mount point for %d tries", ntries);
1033        goto finish;
1034    }
1035
1036    if (ntries) {
1037        OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
1038            "Warning: readCaches got mount point after %d tries.", ntries);
1039    }
1040
1041    if (!CFURLGetFileSystemRepresentation(volURL, /* resolveToBase */ true,
1042                             (UInt8 *)volRoot, sizeof(volRoot))){
1043        OSKextLogStringError(NULL);
1044        goto finish;
1045    }
1046
1047    rval = readBootCaches(volRoot, kBROptsNone);
1048
1049finish:
1050    if (ddesc)      CFRelease(ddesc);
1051
1052    return rval;
1053}
1054
1055/*******************************************************************************
1056* needsUpdate checks a single path and timestamp; populates path->tstamp
1057* compares/stores *ctime* of the source file vs. the *mtime* of the bootstamp.
1058* returns false on error: if we can't tell, we probably can't update
1059*******************************************************************************/
1060Boolean
1061needsUpdate(char *root, cachedPath* cpath)
1062{
1063    Boolean outofdate = false;
1064    Boolean rfpresent, tsvalid;
1065    struct stat rsb, tsb;
1066    char fullrp[PATH_MAX], fulltsp[PATH_MAX];
1067
1068    // create full paths
1069    pathcpy(fullrp, root);
1070    pathcat(fullrp, cpath->rpath);
1071    pathcpy(fulltsp, root);
1072    pathcat(fulltsp, cpath->tspath);
1073
1074    // check the source file in the root volume
1075    if (stat(fullrp, &rsb) == 0) {
1076        rfpresent = true;
1077    } else if (errno == ENOENT) {
1078        rfpresent = false;
1079    } else {
1080        // non-ENOENT errars => fail with log message
1081        OSKextLog(/* kext */ NULL,
1082            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1083            "Cached file %s: %s.", fullrp, strerror(errno));
1084        goto finish;
1085    }
1086
1087    // The timestamp file's mtime tracks the source file's ctime.
1088    // If present, store the root path's timestamps to apply later.
1089    if (rfpresent) {
1090        TIMESPEC_TO_TIMEVAL(&cpath->tstamps[0], &rsb.st_atimespec);
1091        TIMESPEC_TO_TIMEVAL(&cpath->tstamps[1], &rsb.st_ctimespec);
1092    } else {
1093        // "no [corresponding] root file" is represented by a timestamp
1094        // file ("bootstamp") with a/mtime == 0.
1095        bzero(cpath->tstamps, sizeof(cpath->tstamps));
1096    }
1097
1098    // check on the timestamp file itself
1099    // it's invalid if it tracks a non-existant root file
1100    if (stat(fulltsp, &tsb) == 0) {
1101        if (tsb.st_mtimespec.tv_sec != 0) {
1102            tsvalid = true;
1103        } else {
1104            tsvalid = false;
1105        }
1106    } else if (errno == ENOENT) {
1107        tsvalid = false;
1108    } else {
1109        // non-ENOENT errors => fail w/log message
1110        OSKextLog(/* kext */ NULL,
1111            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1112            "timestamp cache %s: %s!", fulltsp, strerror(errno));
1113        goto finish;
1114    }
1115
1116
1117    // Depnding on root file vs. timestamp data, figure out what, if
1118    // anything, needs to be done.
1119    if (rfpresent && tsvalid) {
1120        outofdate = (tsb.st_mtimespec.tv_sec != rsb.st_ctimespec.tv_sec ||
1121               tsb.st_mtimespec.tv_nsec != rsb.st_ctimespec.tv_nsec);
1122    } else if (!rfpresent && tsvalid) {
1123        // need to propagate the fact that the file no longer exists
1124        outofdate = true;
1125    } else if (rfpresent && !tsvalid) {
1126        // need to make the timestamp valid
1127        outofdate = true;
1128    } else {
1129        // !rfpresent && !tsvalid
1130        outofdate = false;
1131    }
1132
1133finish:
1134    return outofdate;
1135}
1136
1137/*******************************************************************************
1138* needUpdates() checks all cached paths to see what looks out of date
1139*
1140* needUpdates() compares the cached timestamps from the helper partition
1141* (stored in /S/L/Caches/c.a.bootstamps) to the corresponding source files
1142* in the root volume.  Any caches built first into the root volume
1143* (kernel/kext, EFI Login locs, etc) should be checked and rebuilt prior
1144* to calling this function.
1145*
1146* needsUpdate() also populates the timestamp structs for updateStamps().
1147*******************************************************************************/
1148
1149#define kBRTaintFile ".notBootRootDefault"
1150#define MAKETAINTPATH(taintpath, mount, subdir) do { \
1151    unsigned fullLen;   \
1152    fullLen = snprintf(taintpath, sizeof(taintpath), "%s/%s/%s",    \
1153                       mount, subdir ? subdir : "", kBRTaintFile);  \
1154    if (fullLen >= sizeof(taintpath))           goto finish;        \
1155} while(0)
1156Boolean
1157notBRDefault(const char *mount, const char *subdir)
1158{
1159    Boolean ismarked = false;
1160    char taintf[PATH_MAX];
1161    struct stat sb;
1162
1163    MAKETAINTPATH(taintf, mount, subdir);
1164    ismarked = stat(taintf, &sb) == 0;
1165
1166finish:
1167    return ismarked;
1168}
1169
1170#define OODMSG "not cached."
1171Boolean
1172needUpdates(struct bootCaches *caches, BRUpdateOpts_t opts,
1173            Boolean *rps, Boolean *booters, Boolean *misc,
1174            OSKextLogSpec oodLogSpec)
1175{
1176    Boolean rpsOOD, bootersOOD, miscOOD, anyOOD;
1177    cachedPath *cp;
1178
1179    // assume nothing needs updating (can't tell -> don't update)
1180    rpsOOD = bootersOOD = miscOOD = anyOOD = false;
1181
1182    // If attempting to make default-bootable, check for non-default content
1183    if ((opts & kBRUCachesAnyRoot) == false &&
1184            notBRDefault(caches->root,kTSCacheDir)){
1185        rpsOOD = bootersOOD = miscOOD = anyOOD = true;
1186        // not done yet, need to populate the tstamps!
1187    }
1188
1189    // first check RPS paths
1190    for (cp = caches->rpspaths; cp < &caches->rpspaths[caches->nrps]; cp++) {
1191        if (needsUpdate(caches->root, cp)) {
1192            OSKextLog(NULL, oodLogSpec, "%s " OODMSG, cp->rpath);
1193            anyOOD = rpsOOD = true;
1194        }
1195    }
1196#if DEV_KERNEL_SUPPORT
1197    if (caches->extraKernelCachePaths) {
1198        for (cp = caches->extraKernelCachePaths;
1199             cp < &caches->extraKernelCachePaths[caches->nekcp];
1200             cp++) {
1201            if (needsUpdate(caches->root, cp)) {
1202                OSKextLog(NULL, oodLogSpec, "%s " OODMSG, cp->rpath);
1203                anyOOD = rpsOOD = true;
1204            }
1205        }
1206    }
1207#endif
1208
1209    // then booters
1210    if ((cp = &(caches->efibooter)), cp->rpath[0]) {
1211        if (needsUpdate(caches->root, cp)) {
1212            OSKextLog(NULL, oodLogSpec, "%s " OODMSG, cp->rpath);
1213            anyOOD = bootersOOD = true;
1214        }
1215    }
1216    if ((cp = &(caches->ofbooter)), cp->rpath[0]) {
1217        if (needsUpdate(caches->root, cp)) {
1218            OSKextLog(NULL, oodLogSpec, "%s " OODMSG, cp->rpath);
1219            anyOOD = bootersOOD = true;
1220       }
1221    }
1222
1223    // and finally misc paths (non-booter files read by EFI)
1224    // kextcache -U -Boot -> misc = NULL
1225    for (cp=caches->miscpaths; cp<&caches->miscpaths[caches->nmisc]; cp++){
1226        if (needsUpdate(caches->root, cp)) {
1227            OSKextLog(NULL, oodLogSpec, "%s " OODMSG, cp->rpath);
1228            anyOOD = miscOOD = true;
1229        }
1230    }
1231
1232    if (rps)        *rps = rpsOOD;
1233    if (booters)    *booters = bootersOOD;
1234    if (misc)       *misc = miscOOD;
1235
1236    return anyOOD;
1237}
1238
1239/*******************************************************************************
1240* updateStamps runs through all of the cached paths in a struct bootCaches
1241* and applies the timestamps captured before the update
1242* not going to bother with a re-stat() of the sources for now
1243*******************************************************************************/
1244// could/should use schdirparent, move to safecalls.[ch]
1245static int
1246_sutimes(int fdvol, char *path, int oflags, struct timeval times[2])
1247{
1248    int bsderr;
1249    int fd = -1;
1250
1251    // X O_RDONLY is the only way to open directories ... and the
1252    // descriptor allows timestamp updates
1253    if (-1 == (fd = sopen(fdvol, path, oflags, kCacheFileMode))) {
1254        bsderr = fd;
1255        // XX sopen() should log on its own after we get errors correct
1256        OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1257                  "%s: %s", path, strerror(errno));
1258        goto finish;
1259    }
1260    if ((bsderr = futimes(fd, times))) {
1261        OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1262                  "futimes(<%s>): %s", path, strerror(errno));
1263    }
1264
1265finish:
1266    if (fd != -1)   close(fd);
1267
1268    return bsderr;
1269}
1270
1271// Sec review: no need to drop privs thanks to safecalls.[ch]
1272static int
1273updateStamp(char *root, cachedPath *cpath, int fdvol, int command)
1274{
1275    int bsderr = -1;
1276    char fulltspath[PATH_MAX];
1277
1278    pathcpy(fulltspath, root);
1279    pathcat(fulltspath, cpath->tspath);
1280
1281    // we must unlink even for ApplyTimes b/c sopen() passes O_EXCL
1282    bsderr = sunlink(fdvol, fulltspath);
1283    if (bsderr == -1 && errno == ENOENT) {
1284        bsderr = 0;
1285    }
1286
1287    if (command == kBCStampsApplyTimes) {
1288        bsderr = _sutimes(fdvol, fulltspath, O_CREAT, cpath->tstamps);
1289    }
1290
1291finish:
1292    return bsderr;
1293}
1294
1295#define BRDBG_DISABLE_EXTSYNC_F "/var/db/.BRDisableExtraSync"
1296int
1297updateStamps(struct bootCaches *caches, int command)
1298{
1299    int anyErr = 0;         // accumulates errors
1300    struct statfs sfs;
1301    cachedPath *cp;
1302    struct stat sb;
1303
1304    // don't try to apply bootstamps to a read-only volume
1305    if (statfs(caches->root, &sfs) == 0) {
1306        if ((sfs.f_flags & MNT_RDONLY)) {
1307            OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
1308                      "Warning: %s read-only: no bootstamp updates",
1309                      caches->root);
1310            return 0;   // success
1311        }
1312    }
1313
1314    // allow known commands through
1315    switch (command) {
1316        case kBCStampsApplyTimes:
1317        case kBCStampsUnlinkOnly:
1318            break;
1319
1320        default:
1321            return EINVAL;
1322    }
1323
1324    // if writing stamps, make sure cache directory exists
1325    if (command == kBCStampsApplyTimes &&
1326            (anyErr = ensureCacheDirs(caches))) {
1327        return anyErr;
1328    }
1329
1330    // run through all of the cached paths apply bootstamp
1331    for (cp = caches->rpspaths; cp < &caches->rpspaths[caches->nrps]; cp++) {
1332        anyErr |= updateStamp(caches->root, cp, caches->cachefd, command);
1333    }
1334#if DEV_KERNEL_SUPPORT
1335    if (caches->extraKernelCachePaths) {
1336        for (cp = caches->extraKernelCachePaths;
1337             cp < &caches->extraKernelCachePaths[caches->nekcp];
1338             cp++) {
1339            anyErr |= updateStamp(caches->root, cp, caches->cachefd, command);
1340        }
1341    }
1342#endif
1343    if ((cp = &(caches->efibooter)), cp->rpath[0]) {
1344        anyErr |= updateStamp(caches->root, cp, caches->cachefd, command);
1345    }
1346    if ((cp = &(caches->ofbooter)), cp->rpath[0]) {
1347        anyErr |= updateStamp(caches->root, cp, caches->cachefd, command);
1348    }
1349    for (cp = caches->miscpaths; cp < &caches->miscpaths[caches->nmisc]; cp++){
1350        anyErr |= updateStamp(caches->root, cp, caches->cachefd, command);
1351    }
1352
1353    // make sure stamps updates are on disk
1354    if (stat(BRDBG_DISABLE_EXTSYNC_F, &sb) == -1) {
1355        anyErr |= fcntl(caches->cachefd, F_FULLFSYNC);
1356    }
1357
1358    // bootstamps imply default content, so remove any taint
1359    anyErr |= markNotBRDefault(caches->cachefd,caches->root,kTSCacheDir,false);
1360
1361    return anyErr;
1362}
1363
1364
1365/*******************************************************************************
1366* taintDefaultStamps() adds .notBootRootDefault to com.apple.bootstamps.
1367* It takes only a helper identifier, from which it searches for any online
1368* volume that stores Boot!=Root content in the specified helper.
1369*
1370* markNotBRDefault() is an exported helper that toggles the taint on/off
1371*******************************************************************************/
1372int
1373markNotBRDefault(int scopefd, const char *mount, const char* subdir,
1374                 Boolean marking)
1375{
1376    int rval = ELAST + 1;
1377    char taintf[PATH_MAX];
1378
1379    if (!mount) {
1380        rval = EINVAL; goto finish;
1381    }
1382
1383    // create cookie file (unlink so sopen() O_EXCL is clean)
1384    MAKETAINTPATH(taintf, mount, subdir);
1385    (void)sunlink(scopefd, taintf);
1386    if (marking) {
1387        int fd = sopen(scopefd, taintf, O_CREAT, kCacheFileMode);
1388        if (fd == -1) {
1389            rval = errno;
1390            OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1391                      "Creating %s: %s", taintf, strerror(rval));
1392            goto finish;
1393        }
1394        close(fd);
1395    }
1396
1397    // success
1398    rval = 0;
1399
1400finish:
1401    return rval;
1402}
1403
1404// search all volumes to see whether any use this helper, if found, taint
1405int
1406taintDefaultStamps(CFStringRef targetBSD)
1407{
1408    int rval = ELAST + 1;
1409    struct bootCaches *defRootCaches = NULL;
1410    char *errmsg = NULL;
1411    int nfsys, i;
1412    size_t bufsz;
1413    struct statfs *mounts = NULL;
1414
1415    errmsg = "Error getting mount list.";
1416    if (-1 == (nfsys = getfsstat(NULL, 0, MNT_NOWAIT))) {
1417        rval = errno; goto finish;
1418    }
1419    bufsz = nfsys * sizeof(struct statfs);
1420    if (!(mounts = malloc(bufsz))) {
1421        rval = errno; goto finish;
1422    }
1423    if (-1 == getfsstat(mounts, (int)bufsz, MNT_NOWAIT)) {
1424        rval = errno; goto finish;
1425    }
1426
1427    errmsg = "error examining helper partitions";
1428    for (i = 0; i < nfsys; i++) {
1429        struct statfs *sfs = &mounts[i];
1430        CFArrayRef helpers;
1431        CFStringRef helper;
1432        if (sfs->f_flags & MNT_LOCAL && strcmp(sfs->f_fstypename, "devfs")) {
1433            CFURLRef volURL;
1434            volURL = CFURLCreateFromFileSystemRepresentation(nil,
1435                                                    (UInt8*)sfs->f_mntonname,
1436                                                    strlen(sfs->f_mntonname),
1437                                                    1 /* isDirectory */);
1438            if (!volURL) {
1439                rval = ENOMEM; goto finish;
1440            }
1441            if ((helpers = BRCopyActiveBootPartitions(volURL))) {
1442                CFIndex hidx = CFArrayGetCount(helpers);
1443                while (hidx--) {
1444                    helper = CFArrayGetValueAtIndex(helpers, hidx);
1445                    if (helper && CFEqual(helper, targetBSD)) {
1446                        defRootCaches = readBootCaches(sfs->f_mntonname, 0);
1447                    }
1448                }
1449                CFRelease(helpers);
1450            }
1451            CFRelease(volURL);
1452        }
1453        if (defRootCaches)      break;
1454    }
1455
1456    // If we found a volume, leave the "helper contents non-standard" hint
1457    errmsg = NULL;
1458    if (defRootCaches) {
1459        rval = markNotBRDefault(defRootCaches->cachefd, defRootCaches->root,
1460                                kTSCacheDir, true);         // logs
1461        destroyCaches(defRootCaches);
1462    } else {
1463        // success
1464        rval = 0;
1465    }
1466
1467finish:
1468    if (errmsg) {
1469        OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1470                  "%s", errmsg);
1471    }
1472    if (mounts)     free(mounts);
1473
1474    return rval;
1475}
1476
1477/*******************************************************************************
1478* rebuild_kext_boot_cache_file fires off kextcache on the given volume
1479* XX there is a bug here that can mask a stale mkext in the Apple_Boot (4764605)
1480*******************************************************************************/
1481int
1482rebuild_kext_boot_cache_file(
1483    struct bootCaches *caches,
1484    Boolean wait,
1485    const char * kext_boot_cache_file,
1486    const char * kernel_file)
1487{
1488    int             rval                    = ELAST + 1;
1489    int             pid                     = -1;
1490    CFIndex i, argi = 0, argc = 0, narchs = 0;
1491    CFDictionaryRef pbDict, mkDict;
1492    CFArrayRef      archArray;
1493    char **kcargs = NULL, **archstrs = NULL;    // no [ARCH_MAX] anywhere?
1494    char          * lastslash               = NULL;
1495    char            rcpath[PATH_MAX]        = "";
1496    struct stat     sb;
1497    char            full_cache_file_path[PATH_MAX]        = "";
1498    char            full_cache_file_dir_path[PATH_MAX]    = "";
1499    char          * fullextsp               = NULL;
1500    char            fullkernelp[PATH_MAX] = "";
1501    Boolean         generateKernelcache     = false;
1502    int             mkextVersion            = 0;
1503
1504    // bootcaches.plist might not request mkext/kernelcache rebuilds
1505    if (!caches->kext_boot_cache_file
1506    ) {
1507       goto finish;
1508    }
1509
1510    fullextsp = malloc(caches->nexts * PATH_MAX);
1511    if (!fullextsp)  goto finish;
1512    *fullextsp = 0x00;
1513
1514    pbDict = CFDictionaryGetValue(caches->cacheinfo, kBCPostBootKey);
1515    if (!pbDict || CFGetTypeID(pbDict) != CFDictionaryGetTypeID())  goto finish;
1516
1517   /* Try for a Kernelcache key, and if there isn't one, look for an "MKext" key.
1518    */
1519    do {
1520        mkDict = CFDictionaryGetValue(pbDict, kBCKernelcacheV1Key);
1521        if (!mkDict)
1522            mkDict = CFDictionaryGetValue(pbDict, kBCKernelcacheV2Key);
1523        if (!mkDict) {
1524            mkDict = CFDictionaryGetValue(pbDict, kBCKernelcacheV3Key);
1525        }
1526
1527        if (mkDict) {
1528            generateKernelcache = true;
1529            break;
1530        }
1531
1532        mkDict = CFDictionaryGetValue(pbDict, kBCMKext2Key);
1533        if (mkDict) {
1534            mkextVersion = 2;
1535            break;
1536        }
1537
1538        mkDict = CFDictionaryGetValue(pbDict, kBCMKextKey);
1539        if (mkDict) {
1540            mkextVersion = 1;
1541            break;
1542        }
1543
1544    } while (0);
1545
1546    if (!mkDict || CFGetTypeID(mkDict) != CFDictionaryGetTypeID())  goto finish;
1547
1548        archArray = CFDictionaryGetValue(mkDict, kBCArchsKey);
1549    if (archArray) {
1550        narchs = CFArrayGetCount(archArray);
1551        archstrs = calloc(narchs, sizeof(char*));
1552        if (!archstrs)  goto finish;
1553    }
1554
1555    //      argv[0]   -a x -a y   -l [-n] [-r] [-K <kernel>] -c <kcache> -volume-root <vol> <exts>  NULL
1556    argc =  1       + (narchs*2) + 1 + 1  + 1  + 1     + 1  + 1    + 1           + 1  + 1  + caches->nexts + 1;
1557    kcargs = malloc(argc * sizeof(char*));
1558    if (!kcargs)  goto finish;
1559    kcargs[argi++] = "kextcache";
1560
1561    // convert each -arch argument into a char* and add to the vector
1562    for(i = 0; i < narchs; i++) {
1563        CFStringRef archStr;
1564        size_t archSize;
1565
1566        // get  arch
1567        archStr = CFArrayGetValueAtIndex(archArray, i);
1568        if (!archStr || CFGetTypeID(archStr)!=CFStringGetTypeID()) goto finish;
1569        // XX an arch is not a pathname; EncodingASCII might be more appropriate
1570        archSize = CFStringGetMaximumSizeOfFileSystemRepresentation(archStr);
1571        if (!archSize)  goto finish;
1572        // X marks the spot: over 800 lines written before I realized that
1573        // there were some serious security implications
1574        archstrs[i] = malloc(archSize);
1575        if (!archstrs[i])  goto finish;
1576        if (!CFStringGetFileSystemRepresentation(archStr,archstrs[i],archSize))
1577            goto finish;
1578
1579        kcargs[argi++] = "-arch";
1580        kcargs[argi++] = archstrs[i];
1581    }
1582
1583    // BootRoot always includes local kexts
1584    kcargs[argi++] = "-local-root";
1585
1586    // 6413843 check if it's installation media (-> add -n)
1587    pathcpy(rcpath, caches->root);
1588    removeTrailingSlashes(rcpath);       // X caches->root trailing '/'?
1589    pathcat(rcpath, "/etc/rc.cdrom");
1590    if (stat(rcpath, &sb) == 0) {
1591        kcargs[argi++] = "-network-root";
1592    }
1593
1594    // determine proper argument to precede kext_boot_cache_file
1595    if (generateKernelcache) {
1596        // for '/' only, include all kexts loaded since boot (9130863)
1597        // TO DO: can we optimize for the install->first boot case?
1598        if (0 == strcmp(caches->root, "/")) {
1599            kcargs[argi++] = "-all-loaded";
1600        }
1601        pathcpy(fullkernelp, caches->root);
1602        removeTrailingSlashes(fullkernelp);
1603        pathcat(fullkernelp, kernel_file);
1604        kcargs[argi++] = "-kernel";
1605        kcargs[argi++] = fullkernelp;
1606        // prelinked kernel path below
1607        kcargs[argi++] = "-prelinked-kernel";
1608    } else if (mkextVersion == 2) {
1609        kcargs[argi++] = "-mkext2";
1610    } else if (mkextVersion == 1) {
1611        kcargs[argi++] = "-mkext1";
1612    } else {
1613        // internal error!
1614        goto finish;
1615    }
1616
1617    pathcpy(full_cache_file_path, caches->root);
1618    removeTrailingSlashes(full_cache_file_path);
1619    pathcat(full_cache_file_path, kext_boot_cache_file);
1620    kcargs[argi++] = full_cache_file_path;
1621
1622    kcargs[argi++] = "-volume-root";
1623    kcargs[argi++] = caches->root;
1624
1625    // we now support multiple extensions directories
1626    char    *extsDirPtr = caches->exts;
1627    char    *tempExtsDirPtr = fullextsp;
1628
1629    for (i = 0; i < caches->nexts; i++) {
1630        pathcpy(tempExtsDirPtr, caches->root);
1631        removeTrailingSlashes(tempExtsDirPtr);
1632        pathcat(tempExtsDirPtr, extsDirPtr);
1633
1634        kcargs[argi++] = tempExtsDirPtr;
1635
1636        extsDirPtr += (strlen(extsDirPtr) + 1);
1637        tempExtsDirPtr += (strlen(tempExtsDirPtr) + 1);
1638    }
1639    kcargs[argi] = NULL;
1640
1641    pathcpy(full_cache_file_dir_path, full_cache_file_path);
1642    lastslash = rindex(full_cache_file_dir_path, '/');
1643    if (lastslash) {
1644        *lastslash = '\0';
1645
1646       /* Make sure we have a destination directory to write the new mkext
1647        * file into (people occasionally delete the caches folder).
1648        */
1649        if ((rval = sdeepmkdir(caches->cachefd, full_cache_file_dir_path,
1650                               kCacheDirMode))) {
1651            OSKextLog(/* kext */ NULL,
1652                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1653                "failed to create cache folder %s.", full_cache_file_dir_path);
1654            // can't make dest directory, kextcache will fail, so don't bother
1655            goto finish;
1656        }
1657
1658    }
1659    rval = 0;
1660
1661   /* wait:false means the return value is <0 for fork/exec failures and
1662    * the pid of the forked process if >0.
1663    *
1664    * wait:true means the return value is <0 for fork/exec failures and
1665    * the exit status of the forked process (>=0) otherwise.
1666    */
1667    pid = fork_program("/usr/sbin/kextcache", kcargs, wait);  // logs errors
1668
1669finish:
1670    if (rval) {
1671        OSKextLog(/* kext */ NULL,
1672            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
1673            "Data error before mkext rebuild.");
1674    }
1675    if (wait || pid < 0) {
1676        rval = pid;
1677    }
1678
1679    if (archstrs) {
1680        for (i = 0; i < narchs; i++) {
1681            if (archstrs[i])    free(archstrs[i]);
1682        }
1683        free(archstrs);
1684    }
1685    if (fullextsp)  free(fullextsp);
1686    if (kcargs)     free(kcargs);
1687
1688    return rval;
1689}
1690
1691/*******************************************************************************
1692* Check our various plist caches, for the current kernel arch only, to see if
1693* they need to be rebuilt:
1694*
1695*    id -> url index (per directory)
1696*    logindwindow prop/value cache for OSBundleHelper (global)
1697*
1698* This should only be called for the root volume!
1699*******************************************************************************/
1700Boolean
1701plistCachesNeedRebuild(const NXArchInfo * kernelArchInfo)
1702{
1703    Boolean     result                     = true;
1704    CFArrayRef  systemExtensionsFolderURLs = NULL;  // need not release
1705    CFStringRef cacheBasename              = NULL;  // must release
1706    CFIndex     count, i;
1707
1708    systemExtensionsFolderURLs = OSKextGetSystemExtensionsFolderURLs();
1709    if (!systemExtensionsFolderURLs ||
1710        !CFArrayGetCount(systemExtensionsFolderURLs)) {
1711
1712        result = false;
1713        goto finish;
1714    }
1715
1716    count = CFArrayGetCount(systemExtensionsFolderURLs);
1717    for (i = 0; i < count; i++) {
1718        CFURLRef directoryURL = CFArrayGetValueAtIndex(
1719            systemExtensionsFolderURLs, i);
1720
1721       /* Check the KextIdentifiers index.
1722        */
1723        if (!_OSKextReadCache(directoryURL, CFSTR(_kOSKextIdentifierCacheBasename),
1724            /* arch */ NULL, _kOSKextCacheFormatCFBinary, /* parseXML? */ false,
1725            /* valuesOut*/ NULL)) {
1726
1727            goto finish;
1728        }
1729    }
1730
1731   /* Check the KextPropertyValues_OSBundleHelper cache for the current kernel arch.
1732    */
1733    cacheBasename = CFStringCreateWithFormat(kCFAllocatorDefault,
1734        /* formatOptions */ NULL, CFSTR("%s%s"),
1735        _kKextPropertyValuesCacheBasename,
1736        "OSBundleHelper");
1737    if (!cacheBasename) {
1738        OSKextLogMemError();
1739        result = false; // cause we don't be able to update
1740        goto finish;
1741    }
1742
1743    if (!_OSKextReadCache(systemExtensionsFolderURLs, cacheBasename,
1744        kernelArchInfo, _kOSKextCacheFormatCFXML, /* parseXML? */ false,
1745        /* valuesOut*/ NULL)) {
1746
1747        goto finish;
1748    }
1749
1750    result = false;
1751
1752finish:
1753    SAFE_RELEASE(cacheBasename);
1754    return result;
1755}
1756
1757Boolean
1758check_kext_boot_cache_file(
1759    struct bootCaches * caches,
1760    const char * cache_path,
1761    const char * kernel_path)
1762{
1763    Boolean      needsrebuild                       = false;
1764    char         fullPath[PATH_MAX]                 = "";
1765    struct stat  statbuffer;
1766    time_t       validModtime                       = 0;
1767    time_t       kernelcacheModtime                 = 0;
1768
1769   /* Do we have a cache file (mkext or kernelcache)?
1770    * Note: cache_path is a pointer field, not a static array.
1771    */
1772    if (cache_path == NULL)
1773        goto finish;
1774
1775   /* If so, check the mod time of the cache file vs. the extensions folder.
1776    */
1777    // we support multiple extensions directories, use latest mod time
1778    char    *bufptr;
1779    int     i;
1780    bufptr = caches->exts;
1781
1782    for (i = 0; i < caches->nexts; i++) {
1783        pathcpy(fullPath, caches->root);
1784        removeTrailingSlashes(fullPath);
1785        pathcat(fullPath, bufptr);
1786
1787        if (stat(fullPath, &statbuffer) == 0) {
1788#if 0
1789            OSKextLogCFString(NULL,
1790                              kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
1791                              CFSTR("%s - %ld <- mod time of %s "),
1792                              __func__, statbuffer.st_mtime, fullPath);
1793#endif
1794            if (statbuffer.st_mtime + 1 > validModtime) {
1795               validModtime = statbuffer.st_mtime + 1;
1796            }
1797        }
1798        else {
1799        OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
1800                  "Warning: %s: %s", fullPath, strerror(errno));
1801        }
1802        bufptr += (strlen(bufptr) + 1);
1803        fullPath[0] = 0x00;
1804    }
1805
1806   /* Check the mod time of the appropriate kernel too, if applicable.
1807    * A kernel path in bootcaches.plist means we should have a kernelcache.
1808    * Note: kernel_path is a static array, not a pointer field.
1809    */
1810    if (kernel_path[0]) {
1811        pathcpy(fullPath, caches->root);
1812        removeTrailingSlashes(fullPath);
1813        pathcat(fullPath, kernel_path);
1814
1815        if (stat(fullPath, &statbuffer) == -1) {
1816            OSKextLog(/* kext */ NULL,
1817                      kOSKextLogBasicLevel | kOSKextLogFileAccessFlag,
1818                      "Note: %s: %s", fullPath, strerror(errno));
1819            // assert(needsrebuild == false);   // we can't build w/o kernel
1820            goto finish;
1821        }
1822#if 0
1823        OSKextLogCFString(NULL,
1824                          kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
1825                          CFSTR("%s - %ld <- mod time of %s "),
1826                          __func__, statbuffer.st_mtime, fullPath);
1827#endif
1828
1829        /* The cache file should be 1 second newer than the newer of the
1830         * Extensions folder(s) or the kernel.
1831         */
1832        if (statbuffer.st_mtime > validModtime) {
1833            validModtime = statbuffer.st_mtime + 1;
1834        }
1835    }
1836
1837    // The cache file itself
1838    needsrebuild = true;  // since this stat() will fail if cache file is gone
1839    pathcpy(fullPath, caches->root);
1840    removeTrailingSlashes(fullPath);
1841    pathcat(fullPath, cache_path);
1842    if (stat(fullPath, &statbuffer) == -1) {
1843        goto finish;
1844    }
1845#if 0
1846    OSKextLogCFString(NULL,
1847                      kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
1848                      CFSTR("%s - %ld <- mod time of %s "),
1849                      __func__, statbuffer.st_mtime, fullPath);
1850#endif
1851
1852    kernelcacheModtime = statbuffer.st_mtime;
1853    needsrebuild = (kernelcacheModtime != validModtime);
1854
1855finish:
1856    return needsrebuild;
1857}
1858
1859/*******************************************************************************
1860* createDiskForMount creates a DADisk object given a mount point
1861* session is optional; one is created and released if the caller can't supply
1862*******************************************************************************/
1863DADiskRef
1864createDiskForMount(DASessionRef session, const char *mount)
1865{
1866    DADiskRef rval = NULL;
1867    DASessionRef dasession = NULL;
1868    CFURLRef volURL = NULL;
1869
1870    if (session) {
1871        dasession = session;
1872    } else {
1873        dasession = DASessionCreate(nil);
1874        if (!dasession)     goto finish;
1875    }
1876
1877    volURL = CFURLCreateFromFileSystemRepresentation(nil, (UInt8*)mount,
1878            strlen(mount), 1 /*isDirectory*/);
1879    if (!volURL)        goto finish;
1880
1881    rval = DADiskCreateFromVolumePath(nil, dasession, volURL);
1882
1883finish:
1884    if (volURL)     CFRelease(volURL);
1885    if (!session && dasession)
1886        CFRelease(dasession);
1887
1888    return rval;
1889}
1890
1891
1892/*****************************************************************************
1893* CoreStorage FDE check & update routines
1894* kextd calls check_csfde; kextcache calls check_, rebuild_csfde_cache()
1895*****************************************************************************/
1896// on success, caller is responsible for releasing econtext
1897int
1898copyCSFDEInfo(CFStringRef uuidStr, CFDictionaryRef *econtext,
1899               time_t *timeStamp)
1900{
1901    int             rval = ELAST+1;
1902    CFDictionaryRef lvfprops = NULL;
1903    CFDictionaryRef ectx;
1904    CFNumberRef     psRef;
1905    CFArrayRef      eusers;     // owned by lvfprops
1906    Boolean         encrypted;
1907
1908    if (!uuidStr) {
1909        rval = EINVAL; goto finish;
1910    }
1911
1912    // 04/25/11 - gab: <rdar://problem/9168337>
1913    // can't operate without libCoreStorage func
1914    if (CoreStorageCopyFamilyProperties == NULL) {
1915        rval = ESHLIBVERS; goto finish;
1916    }
1917
1918    lvfprops = CoreStorageCopyFamilyProperties(uuidStr);
1919    if (!lvfprops) {
1920        rval = EFTYPE; goto finish;
1921    }
1922
1923    ectx = (CFMutableDictionaryRef)CFDictionaryGetValue(lvfprops,
1924                        CFSTR(kCoreStorageFamilyEncryptionContextKey));
1925    if (!ectx || CFGetTypeID(ectx) != CFDictionaryGetTypeID()) {
1926        rval = EFTYPE; goto finish;
1927    }
1928
1929    // does it have encrypted users?
1930    eusers = (CFArrayRef)CFDictionaryGetValue(ectx, CFSTR(kCSFDECryptoUsersID));
1931    encrypted = (eusers && CFArrayGetCount(eusers));
1932
1933    if (encrypted) {
1934        if (econtext) {
1935            *econtext = CFRetain(ectx);
1936        }
1937        if (timeStamp) {
1938            psRef = CFDictionaryGetValue(ectx, CFSTR(kCSFDELastUpdateTime));
1939            if (psRef) {
1940                if (CFGetTypeID(psRef) != CFNumberGetTypeID() ||
1941                    !CFNumberGetValue(psRef,kCFNumberSInt64Type,timeStamp)){
1942                    rval = EFTYPE; goto finish;
1943                }
1944            } else {    // no timestamp (odd, but maybe okay)
1945                *timeStamp = 0LL;
1946            }
1947        }
1948    } else {        // not encrypted
1949        if (econtext)       *econtext = NULL;
1950        if (timeStamp)      *timeStamp = 0LL;
1951    }
1952
1953    rval = 0;
1954
1955finish:
1956    if (lvfprops)   CFRelease(lvfprops);
1957
1958    if (rval) {
1959        OSKextLogCFString(NULL, kOSKextLogErrorLevel|kOSKextLogFileAccessFlag,
1960                          CFSTR("could not copy LVF props for %@: %s"),
1961                          uuidStr, strerror(rval));
1962    }
1963
1964    return rval;
1965}
1966
1967Boolean
1968check_csfde(struct bootCaches *caches)
1969{
1970    Boolean         needsupdate = false;
1971    time_t          propStamp, erStamp;
1972    char            erpath[PATH_MAX];
1973    struct stat     ersb;
1974
1975    if (!caches->csfde_uuid || !caches->erpropcache)
1976        goto finish;
1977
1978    if (copyCSFDEInfo(caches->csfde_uuid, NULL, &propStamp))
1979        goto finish;
1980
1981    // get property cache file's timestamp
1982    pathcpy(erpath, caches->root);
1983    pathcat(erpath, caches->erpropcache->rpath);
1984    if (stat(erpath, &ersb) == 0) {
1985        erStamp = ersb.st_mtimespec.tv_sec;
1986    } else {
1987        if (errno == ENOENT) {
1988            erStamp = 0LL;
1989        } else {
1990            goto finish;
1991        }
1992    }
1993
1994    // generally the timestamp advances, but != means out of date
1995    needsupdate = erStamp != propStamp;
1996
1997finish:
1998    return needsupdate;
1999}
2000
2001/*
2002 * _writeCSFDENoFD()
2003 * If possible, use CSFDEInitPropertyCache() to write
2004 * EncryptedRoot.plist.wipekey to the requested path.
2005 *
2006 * CSFDEInitPropertyCache() writes to <path>/S/L/Caches/com.apple.corestorage
2007 * so the basic algorithm is
2008 * 0) provided dstpath = /path/to/S/L/Caches/com.apple.corestorage
2009 * 1) find substring S/L/Caches/c.a.corestorage/EncryptedRoot.plist.wipekey
2010 * 2) create terminated parentpath = path/to/\0ystem/L/Caches...
2011 * 3) create /path/to/.../SystemVersion.plist if it doesn't exist
2012 * 4) call CSFDEInitPropertyCache(/path/to)!
2013 */
2014// CSFDEInitPropertyCache() uses /S/L/E in 10.7.2+, but _writeCSFDENoFD()
2015// is only for 10.7.[01] where InitPropertyCache() uses SystemVersion.plist.
2016#define kOrigInitCookieDir "/System/Library/CoreServices"
2017#define kOrigInitCookieFile "/SystemVersion.plist"
2018#define kFDECacheFile kCSFDEPropertyCacheDir"/"kCSFDEPropertyCacheFileEncrypted
2019static int
2020_writeCSFDENoFD(int scopefd, CFDictionaryRef ectx,
2021                CFStringRef wipeKeyUUID, char *dstpath)
2022{
2023    int bsderr, rval = ELAST + 1;       // all but path*() should set
2024    int fd = -1;
2025    Boolean createdCookie = false;
2026    char parentpath[PATH_MAX], cookiepath[PATH_MAX];
2027    char *relpath;
2028    struct stat sb;
2029
2030    // detect expected relative path to EncryptedRoot.plist.wipekey
2031    // and create terminated parentpath
2032    pathcpy(parentpath, dstpath);
2033    if (!(relpath = strstr(parentpath, kFDECacheFile))) {
2034        // path doesn't contain expected substring
2035        rval = EINVAL; LOGERRxlate(dstpath, "missing" kFDECacheFile, rval);
2036        goto finish;
2037    }
2038    relpath[0] = '\0';      // terminate parentpath[] at common parent
2039
2040    // if necessary, create sibling SystemVersion.plist
2041    pathcpy(cookiepath, parentpath);
2042    pathcat(cookiepath, kOrigInitCookieDir);
2043    if ((bsderr = sdeepmkdir(scopefd, cookiepath, kCacheDirMode))) {
2044        rval = bsderr; LOGERRxlate(cookiepath, NULL, rval); goto finish;
2045    }
2046    pathcat(cookiepath, kOrigInitCookieFile);
2047    if (0 != stat(cookiepath, &sb)) {
2048        if ((fd = sopen(scopefd, cookiepath, O_CREAT, kCacheFileMode)) < 0) {
2049            rval = errno; LOGERRxlate(cookiepath, NULL, rval); goto finish;
2050        }
2051        close(fd);
2052        createdCookie = true;
2053    }
2054
2055    // write via the 10.7.[01] function (scopefd ignored!)
2056    errno = 0;
2057    OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
2058              "WARNING: no CSFDEWritePropertyCacheToFD(); "
2059              "trying CSFDEInitPropertyCache()");
2060    if (false == CSFDEInitPropertyCache(ectx, parentpath, wipeKeyUUID)) {
2061        rval = ELAST + 1;   // "internal error" :P
2062        LOGERRxlate("CSFDEInitPropertyCache", parentpath, rval);
2063        goto finish;
2064    }
2065    // make sure it did the deed
2066    if (-1 == stat(dstpath, &sb)) {
2067        rval = errno; LOGERRxlate(dstpath, NULL, rval); goto finish;
2068    }
2069
2070    // success!
2071    rval = 0;
2072
2073finish:
2074    if (createdCookie) {
2075        (void)sunlink(scopefd, cookiepath);   // empty boot.?/S/L/CS okay
2076    }
2077
2078    return rval;
2079}
2080
2081// NOTE: weak-linking depends on -weak-l/-weak_framemwork *and* the
2082// function declaration being marked correctly in the header file!
2083int
2084writeCSFDEProps(int scopefd, CFDictionaryRef ectx,
2085                char *cspvbsd, char *dstpath)
2086{
2087    int             errnum, rval = ELAST + 1;
2088    CFStringRef     wipeKeyUUID = NULL;
2089    char            dstparent[PATH_MAX];
2090    int             erfd = -1;
2091
2092    // 9168337 didn't quite do it, see 10831618
2093    // check for required weak-linked symbol
2094    if (CoreStorageCopyPVWipeKeyUUID==NULL) {
2095        rval = ESHLIBVERS;
2096        LOGERRxlate("no CoreStorageCopyPVWipeKeyUUID()", NULL, rval);
2097        goto finish;
2098    }
2099    wipeKeyUUID = CoreStorageCopyPVWipeKeyUUID(cspvbsd);
2100    if (!wipeKeyUUID) {
2101        OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
2102                  "CoreStorageCopyPVWipeKeyUUID(%s) failed", cspvbsd);
2103        rval = ENODEV; goto finish;
2104    }
2105
2106    // prep (ENOENT ignored by szerofile())
2107    if ((errnum = szerofile(scopefd, dstpath)) ||
2108            ((errnum = sunlink(scopefd, dstpath)) && errno != ENOENT)) {
2109        OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
2110                  "WARNING: %s: %s", dstpath, strerror(errno));
2111    }
2112
2113    // recursively create the parent directory
2114    if (strlcpy(dstparent,dirname(dstpath),PATH_MAX) >= PATH_MAX) {
2115        rval = EOVERFLOW; goto finish;
2116    }
2117    if ((errnum = sdeepmkdir(scopefd, dstparent, kCacheDirMode))) {
2118        rval = errnum; LOGERRxlate(dstparent, NULL, rval); goto finish;
2119    }
2120
2121    // use modern function if available
2122    if (CSFDEWritePropertyCacheToFD!=NULL) {
2123        // open and write to FD
2124        erfd = sopen(scopefd, dstpath, O_CREAT|O_RDWR, kCacheFileMode);
2125        if (-1 == erfd) {
2126            rval = errno; LOGERRxlate(dstpath, NULL, rval); goto finish;
2127        }
2128        if (!CSFDEWritePropertyCacheToFD(ectx, erfd, wipeKeyUUID)) {
2129            rval = ELAST + 1;   // "internal error" :P
2130            LOGERRxlate("CSFDEWritePropertyCacheToFD", dstpath, rval);
2131            goto finish;
2132        }
2133    } else {
2134        // try to trick the old function into writing the cache
2135        if ((errnum = _writeCSFDENoFD(scopefd,ectx,wipeKeyUUID,dstpath))) {
2136            rval = errnum; goto finish;     // error logged by function
2137        }
2138    }
2139
2140    // success
2141    rval = 0;
2142
2143finish:
2144    if (wipeKeyUUID)    CFRelease(wipeKeyUUID);
2145    if (erfd != -1)     close(erfd);
2146
2147    return rval;
2148}
2149
2150// write out a populated EncryptedRoot.plist.wipekey to the root volume
2151static int
2152_writeLegacyCSFDECache(struct bootCaches *caches)
2153{
2154    int             errnum, rval = ELAST + 1;
2155    CFArrayRef      dataVolumes = NULL;
2156    CFStringRef     bsdStr;     // belongs to dataVolumes
2157    char            bsdname[DEVMAXPATHSIZE];
2158    CFDictionaryRef ectx = NULL;
2159    char           *errmsg;
2160    char            erpath[PATH_MAX];
2161    int             erfd = -1;
2162
2163    errmsg = "invalid argument";
2164    if (!caches->csfde_uuid || !caches->erpropcache) {
2165        rval = EINVAL; goto finish;
2166    }
2167
2168    // hasBRBs() cares about Apple_Boot's; FDE, data partitions
2169    (void)hasBootRootBoots(caches, NULL, &dataVolumes, NULL);
2170    if (!dataVolumes || CFArrayGetCount(dataVolumes) == 0) {
2171        errmsg = "no data partition! (for wipe key)";
2172        rval = ENODEV; goto finish;
2173    }
2174
2175    // legacy => encrypt with the first Apple_CoreStorage wipe key
2176    errmsg = "error getting volume wipe key";
2177    bsdStr = CFArrayGetValueAtIndex(dataVolumes, 0);
2178    if (!bsdStr) {
2179        rval = ENODEV; goto finish;
2180    }
2181    if (!CFStringGetFileSystemRepresentation(bsdStr,bsdname,sizeof(bsdname))){
2182        rval = EINVAL; goto finish;
2183    }
2184
2185    errmsg = "error getting encryption context data";
2186    if ((errnum = copyCSFDEInfo(caches->csfde_uuid, &ectx, NULL))) {
2187        rval = errnum; goto finish;
2188    }
2189
2190    // build /<vol>/S/L/Caches/..corestorage/EncryptedRoot.plist.wipekey
2191    errmsg = "error building encryption context cache file path";
2192    pathcpy(erpath, caches->root);
2193    pathcat(erpath, caches->erpropcache->rpath);
2194    errmsg = NULL;
2195
2196    // if not encrypted, just nuke :)
2197    if (!ectx) {
2198        (void)sunlink(caches->cachefd, erpath);
2199        rval = 0; goto finish;
2200    }
2201
2202    errmsg = NULL;      // writeCSFDEProps() logs errors
2203    if ((errnum = writeCSFDEProps(caches->cachefd, ectx, bsdname, erpath))) {
2204        rval = errnum; goto finish;
2205    }
2206
2207    // success
2208    rval = 0;
2209
2210finish:
2211    if (erfd != -1)     close (erfd);
2212    if (dataVolumes)    CFRelease(dataVolumes);
2213    if (ectx)           CFRelease(ectx);
2214
2215    if (rval && errmsg) {
2216        LOGERRxlate(caches->root, errmsg, rval);
2217    }
2218
2219    return rval;
2220}
2221
2222int
2223rebuild_csfde_cache(struct bootCaches *caches)
2224{
2225    int             errnum, rval = ELAST + 1;
2226    time_t          timeStamp;
2227    char            erpath[PATH_MAX] = "<unknown>";
2228    struct timeval  times[2] = {{ 0, 0 }, { 0, 0 }};
2229
2230    if (!caches->csfde_uuid || !caches->erpropcache) {
2231        rval = EINVAL; goto finish;
2232    }
2233
2234    if ((errnum = ensureCacheDirs(caches))) {
2235        rval = errnum; goto finish;
2236    }
2237
2238    // OSes that only support single-PV CSFDE need content in erpropcache
2239    if (caches->erpropTSOnly == false) {
2240        return _writeLegacyCSFDECache(caches);    // takes care of everything
2241    }
2242
2243    // otherwise, just grab the timestamp so update_boot.c knows to re-fetch
2244    if ((errnum = copyCSFDEInfo(caches->csfde_uuid, NULL, &timeStamp))) {
2245        rval = errnum; goto finish;
2246    }
2247    times[0].tv_sec = (__darwin_time_t)timeStamp;
2248    times[1].tv_sec = (__darwin_time_t)timeStamp;    // mdworker -> atime
2249
2250    // build path and recreate proper timestamp
2251    pathcpy(erpath, caches->root);
2252    pathcat(erpath, caches->erpropcache->rpath);
2253    (void)sunlink(caches->cachefd, erpath);
2254
2255    if (timeStamp != 0LL) {
2256        if ((errnum = _sutimes(caches->cachefd, erpath, O_CREAT, times))) {
2257            rval = errnum; goto finish;
2258        }
2259    }
2260
2261    // success
2262    rval = 0;
2263
2264finish:
2265    // no logging above
2266    if (rval)       LOGERRxlate(erpath, NULL, rval);
2267
2268    return rval;
2269}
2270
2271
2272/*******************************************************************************
2273* check_loccache() checks caches that depend on the system localization
2274* XX could use getFilePathModTimePlusOne() -- currently in kextcache_main.c
2275*******************************************************************************/
2276// [PATH_MAX] is essentially a comment; char[] are char* after being passed
2277static int
2278get_locres_info(struct bootCaches *caches, char locRsrcDir[PATH_MAX],
2279                char prefPath[PATH_MAX], struct stat *prefsb,
2280                char locCacheDir[PATH_MAX], time_t *validModTime)
2281{
2282    int rval = EOVERFLOW;       // all other paths set rval
2283    time_t newestTime;
2284    struct stat sb;
2285    char bgImagePath[PATH_MAX];
2286
2287    if (!validModTime) {
2288        rval = EINVAL; LOGERRxlate("get_locres_info", NULL, rval); goto finish;
2289    }
2290
2291    // build localization sources directory path
2292    pathcpy(locRsrcDir, caches->root);
2293    pathcat(locRsrcDir, caches->locSource);
2294    // get localization sources directory timestamp
2295    if (stat(locRsrcDir, &sb)) {
2296        rval = errno; LOGERRxlate(locRsrcDir, NULL, rval); goto finish;
2297    }
2298    newestTime = sb.st_mtime;
2299
2300    // prefs file path & timestamp (if it exists)
2301    pathcpy(prefPath, caches->root);
2302    pathcat(prefPath, caches->locPref);
2303    if (stat(prefPath, prefsb) == 0) {
2304        if (prefsb->st_mtime > newestTime) {
2305            newestTime = prefsb->st_mtime;
2306        }
2307    } else {
2308        if (errno != ENOENT) {
2309            rval = errno; LOGERRxlate(prefPath, NULL, rval); goto finish;
2310        }
2311    }
2312
2313    // background image file (optional)
2314    if (caches->bgImage[0]) {
2315        pathcpy(bgImagePath, caches->root);
2316        pathcat(bgImagePath, caches->bgImage);
2317        if (stat(bgImagePath, &sb) == 0 &&
2318                sb.st_mtime > newestTime) {
2319            newestTime = sb.st_mtime;
2320        }
2321    }
2322
2323    // the cache directory must be one second newer than the
2324    // later of the prefs file and the source directory.
2325    *validModTime = newestTime + 1;
2326
2327    // build localized resources cache directory path
2328    pathcpy(locCacheDir, caches->root);
2329    pathcat(locCacheDir, caches->efiloccache->rpath);
2330
2331    rval = 0;
2332
2333finish:
2334    return rval;
2335}
2336
2337Boolean
2338check_loccache(struct bootCaches *caches)
2339{
2340    Boolean     needsupdate = false;   // needsupdate defaults to "nope"
2341    struct stat prefsb, cachesb;
2342    char        erpath[PATH_MAX];
2343    char        locRsrcDir[PATH_MAX], prefPath[PATH_MAX];
2344    char        locCacheDir[PATH_MAX];
2345    time_t      validModTime = 0;
2346
2347    if (!caches->efiloccache)       goto finish;
2348
2349    // 9516786: loccache only needed if EFI Login plist is active
2350    pathcpy(erpath, caches->root);
2351    pathcat(erpath, caches->erpropcache->rpath);
2352    if (stat(erpath, &cachesb) == -1 && errno == ENOENT) {
2353        // not an error, there is no cache file on non-encrypted volumes
2354        goto finish;
2355    }
2356
2357    if (get_locres_info(caches, locRsrcDir, prefPath, &prefsb,
2358                        locCacheDir, &validModTime)) {
2359        goto finish;    // error logged by function
2360    }
2361
2362    if (stat(locCacheDir, &cachesb) == 0) {
2363        needsupdate = (cachesb.st_mtime != validModTime);
2364    } else if (errno == ENOENT) {
2365        needsupdate = true;
2366    }
2367
2368finish:
2369    return needsupdate;
2370}
2371
2372/*****************************************************************************
2373* rebuild_loccache() rebuilds the localized resources for EFI Login
2374*****************************************************************************/
2375struct writeRsrcCtx {
2376    struct bootCaches *caches;
2377    char *locCacheDir;
2378    int *result;
2379};
2380void
2381_writeResource(const void *value, void *ctxp)
2382{
2383    int bsderr;
2384    CFDictionaryRef rsrc = (CFDictionaryRef)value;
2385    struct writeRsrcCtx *ctx = (struct writeRsrcCtx*)ctxp;
2386    struct bootCaches *caches = ctx->caches;
2387
2388    CFDataRef data;
2389    void *buf;
2390    ssize_t bufsz;
2391    CFStringRef nameStr;
2392    int fflags, fd = -1;
2393    char fname[PATH_MAX], fullp[PATH_MAX];
2394
2395
2396    // extract data, filename & prepare for BSD syscalls
2397    bsderr = EFTYPE;
2398    if (!(data = CFDictionaryGetValue(rsrc, kEFILoginDataKey)))
2399        goto finish;
2400    if (!(buf = (void*)CFDataGetBytePtr(data)))
2401        goto finish;
2402    bufsz = (ssize_t)CFDataGetLength(data);
2403    if (bufsz < 0)      goto finish;
2404
2405    if (!(nameStr = CFDictionaryGetValue(rsrc, kEFILoginFileNameKey)))
2406        goto finish;
2407    if(!CFStringGetFileSystemRepresentation(nameStr, fname, PATH_MAX))
2408        goto finish;
2409    bsderr = EOVERFLOW;
2410    pathcpy(fullp, ctx->locCacheDir);
2411    pathcat(fullp, "/");
2412    pathcat(fullp, fname);
2413
2414    // open & write!
2415    fflags = O_WRONLY | O_CREAT | O_TRUNC;   // sopen() adds EXCL/NOFOL
2416    if (-1 == (fd = sopen(caches->cachefd, fullp, fflags, kCacheFileMode))) {
2417        bsderr = -1;
2418        goto finish;
2419    }
2420    if (write(fd, buf, bufsz) != bufsz) {
2421        bsderr = -1;
2422        goto finish;
2423    }
2424
2425    // success
2426    bsderr = 0;
2427
2428finish:
2429    if (bsderr) {
2430        *(ctx->result) = bsderr;
2431    }
2432
2433    if (fd != -1)   close(fd);
2434
2435    return;
2436}
2437
2438// ahh, ye olde SysLang.h :]
2439// #define GLOBALPREFSFILE "/Library/Preferences/.GlobalPreferences.plist"
2440#define LANGSKEY    CFSTR("AppleLanguages")   // key in .GlobalPreferences
2441#define ENGLISHKEY  CFSTR("en")
2442static int
2443_writeEFILoginResources(struct bootCaches *caches,
2444                        char prefPath[PATH_MAX], struct stat *prefsb,
2445                        char locCacheDir[PATH_MAX])
2446{
2447    int result;         // all paths set an explicit result
2448    int gpfd = -1;
2449    CFDictionaryRef gprefs = NULL;
2450    CFMutableArrayRef locsList = NULL;      // retained & released
2451    CFStringRef volStr = NULL;
2452    CFArrayRef blobList = NULL;
2453
2454    CFRange allEntries;
2455    struct writeRsrcCtx applyCtx = { caches, locCacheDir, &result };
2456
2457    // can't operate without EFILogin.framework function
2458    // (XX as of Zin12A190, this function is not properly decorated ...)
2459    if (EFILoginCopyInterfaceGraphics == NULL) {
2460        result = ESHLIBVERS;
2461        goto finish;
2462    }
2463
2464    // attempt to get AppleLanguages out of .GlobalPreferences
2465    if ((gpfd = sopen(caches->cachefd, prefPath, O_RDONLY, 0)) >= 0 &&
2466        (gprefs = copy_dict_from_fd(gpfd, prefsb)) &&
2467        (locsList=(CFMutableArrayRef)CFDictionaryGetValue(gprefs,LANGSKEY)) &&
2468        CFGetTypeID(locsList) == CFArrayGetTypeID()) {
2469            CFRetain(locsList);
2470    } else {
2471        // create a new array containing the default "en" (locsList !retained)
2472        CFRange range = { 0, 1 };
2473        locsList = CFArrayCreateMutable(nil, 1, &kCFTypeArrayCallBacks);
2474        if (!locsList) {
2475            result = ENOMEM;
2476            goto finish;
2477        }
2478        CFArrayAppendValue(locsList, ENGLISHKEY);
2479        if (!CFArrayContainsValue(locsList, range, ENGLISHKEY)) {
2480            result = ENOMEM;    // ECFFAILED :P
2481            goto finish;
2482        }
2483    }
2484
2485    // generate all resources
2486    volStr = CFStringCreateWithFileSystemRepresentation(nil, caches->root);
2487    if (!volStr ||
2488            !(blobList = EFILoginCopyInterfaceGraphics(locsList, volStr))) {
2489        result = ENOMEM;
2490        goto finish;
2491    }
2492
2493    // write everything out
2494    result = 0;         // applier only modifies on error
2495    allEntries = CFRangeMake(0, CFArrayGetCount(blobList));
2496    CFArrayApplyFunction(blobList, allEntries, _writeResource, &applyCtx);
2497    if (result)     goto finish;
2498
2499    // success!
2500    result = 0;
2501
2502finish:
2503    if (blobList)       CFRelease(blobList);
2504    if (volStr)         CFRelease(volStr);
2505    if (locsList)       CFRelease(locsList);
2506    if (gprefs)         CFRelease(gprefs);
2507    if (gpfd != -1)     close(gpfd);
2508
2509    return result;
2510}
2511
2512int
2513rebuild_loccache(struct bootCaches *caches)
2514{
2515    int errnum, result = ELAST + 1;
2516    struct stat cachesb, prefsb;
2517    char        locRsrcDir[PATH_MAX], prefPath[PATH_MAX];
2518    char        locCacheDir[PATH_MAX];
2519    time_t      validModTime = 0;
2520    int         fd = -1;
2521    struct timeval times[2];
2522
2523    // prefsb.st_size = 0;  // Analyzer doesn't check get_locres_info(&prefsb)
2524    bzero(&prefsb, sizeof(prefsb)); // and doesn't know bzero sets st_size = 0
2525    if ((errnum = get_locres_info(caches, locRsrcDir, prefPath, &prefsb,
2526                                  locCacheDir, &validModTime))) {
2527        result = errnum; goto finish;   // error logged by function
2528    }
2529
2530    // empty out locCacheDir ...
2531    /* This cache is an optional part of RPS, thus it is okay to
2532       destroy on failure (leaving it empty risks "right" timestamps). */
2533    if (sdeepunlink(caches->cachefd, locCacheDir) == -1 && errno == EROFS) {
2534        result = errno; LOGERRxlate(locCacheDir, NULL, result); goto finish;
2535    }
2536    if ((errnum = sdeepmkdir(caches->cachefd,locCacheDir,kCacheDirMode))) {
2537        result = errnum; LOGERRxlate(locCacheDir, NULL, result); goto finish;
2538    }
2539
2540    // actually write resources!
2541    errnum = _writeEFILoginResources(caches, prefPath, &prefsb, locCacheDir);
2542    if (errnum) {
2543        (void)sdeepunlink(caches->cachefd, locCacheDir);
2544        result = errnum;
2545        LOGERRxlate("_writeEFILoginResources", NULL, result);
2546        goto finish;
2547    }
2548
2549    // get current times (keeping access, overwriting mod)
2550    if ((errnum = stat(locCacheDir, &cachesb))) {
2551        result = errnum; LOGERRxlate(locCacheDir, NULL, result); goto finish;
2552    }
2553    cachesb.st_mtime = validModTime;
2554    TIMESPEC_TO_TIMEVAL(&times[0], &cachesb.st_atimespec);
2555    TIMESPEC_TO_TIMEVAL(&times[1], &cachesb.st_mtimespec);
2556    if ((errnum = _sutimes(caches->cachefd, locCacheDir, O_RDONLY, times))) {
2557        result = errnum; LOGERRxlate(locCacheDir, NULL, result); goto finish;
2558    }
2559
2560    // success
2561    result = 0;
2562
2563finish:
2564    if (fd != -1)       close(fd);
2565
2566    return result;
2567}
2568
2569
2570
2571/*****************************************************************************
2572* hasBRBoots lets you know if a volume has boot partitions and if it's on GPT
2573* no error reporting except residual errno
2574*****************************************************************************/
2575Boolean
2576hasBootRootBoots(struct bootCaches *caches, CFArrayRef *auxPartsCopy,
2577                         CFArrayRef *dataPartsCopy, Boolean *isAPM)
2578{
2579    CFDictionaryRef binfo = NULL;
2580    Boolean rval = false, apm = false;
2581    CFArrayRef dparts = NULL, bparts = NULL;
2582    char stack_bsdname[DEVMAXPATHSIZE];
2583    char * lookup_bsdname = caches->bsdname;
2584    CFArrayRef dataPartitions = NULL; // do not release;
2585    size_t fullLen;
2586    char fulldev[DEVMAXPATHSIZE];
2587#if DEBUG_REGISTRY
2588    char parentdevname[DEVMAXPATHSIZE];
2589    uint32_t partitionNum;
2590    BLPartitionType partitionType;
2591#endif
2592
2593   /* Get the BL info about partitions & such.
2594    */
2595    if (BLCreateBooterInformationDictionary(NULL, lookup_bsdname, &binfo))
2596        goto finish;
2597    bparts = CFDictionaryGetValue(binfo, kBLAuxiliaryPartitionsKey);
2598    dparts = CFDictionaryGetValue(binfo, kBLDataPartitionsKey);
2599    if (!bparts || !dparts)     goto finish;
2600
2601   /*****
2602    * Now, for a GPT check, use one of the data partitions given by the above
2603    * call to BLCreateBooterInformationDictionary().
2604    */
2605    dataPartitions = CFDictionaryGetValue(binfo, kBLDataPartitionsKey);
2606    if (dataPartitions && CFArrayGetCount(dataPartitions)) {
2607        CFStringRef dpBsdName = CFArrayGetValueAtIndex(dataPartitions, 0);
2608
2609        if (dpBsdName) {
2610            if (!CFStringGetFileSystemRepresentation(dpBsdName, stack_bsdname,
2611                    sizeof(stack_bsdname)))
2612                goto finish;
2613            lookup_bsdname = stack_bsdname;
2614        }
2615    }
2616
2617   /* Get the BL info about the partition type (that's all we use, but
2618    * we have to pass in valid buffer pointers for all the rest).
2619    */
2620    fullLen = snprintf(fulldev, sizeof(fulldev), "/dev/%s", lookup_bsdname);
2621    if (fullLen >= sizeof(fulldev)) {
2622        goto finish;
2623    }
2624
2625#if DEBUG_REGISTRY
2626    // doesn't work on watson w/USB disk??
2627    if (BLGetParentDeviceAndPartitionType(NULL /* context */,
2628        fulldev, parentdevname, &partitionNum, &partitionType))
2629    {
2630        goto finish;
2631    }
2632    if (partitionType == kBLPartitionType_APM) {
2633        apm = true;
2634    }
2635#endif
2636
2637    // 5158091 / 6413843: 10.4.x APM Apple_Boot's aren't BootRoot
2638    // Boot!=Root was introduced in 10.4.7 for *Intel only*.
2639    // BootX didn't learn about Boot!=Root until 10.5 (mkext2 era).
2640    // XX 10740646 tracks reviewing / dropping ppc support
2641    // The check is APM-only because ppc only booted APM.
2642    if (apm) {
2643        CFDictionaryRef pbDict, mk2Dict, kcDict;
2644
2645        // i.e. Leopard had BootX; SnowLeopard has mkext2
2646        pbDict = CFDictionaryGetValue(caches->cacheinfo, kBCPostBootKey);
2647        if (!pbDict || CFGetTypeID(pbDict) != CFDictionaryGetTypeID())  goto finish;
2648
2649        kcDict = CFDictionaryGetValue(pbDict, kBCKernelcacheV1Key);
2650        if (!kcDict)
2651            kcDict = CFDictionaryGetValue(pbDict, kBCKernelcacheV2Key);
2652        mk2Dict = CFDictionaryGetValue(pbDict, kBCMKext2Key);
2653
2654        // if none of these indicates a more modern OS, we skip
2655        // XX should the ofbooter path check be != '\0' ?
2656        // (then we could drop the kcDict check?)
2657        if (!kcDict && !mk2Dict && caches->ofbooter.rpath[0] == '\0')
2658            goto finish;
2659    }
2660
2661    // check for helper partitions
2662    rval = (CFArrayGetCount(bparts) > 0);
2663
2664finish:
2665    // out parameters set if provided
2666    if (auxPartsCopy) {
2667        if (bparts)     CFRetain(bparts);
2668        *auxPartsCopy = bparts;
2669    }
2670    if (dataPartsCopy) {
2671        if (dparts)     CFRetain(dparts);
2672        *dataPartsCopy = dparts;
2673    }
2674    if (isAPM)      *isAPM = apm;
2675
2676    // cleanup
2677    if (binfo)      CFRelease(binfo);
2678
2679    return rval;
2680}
2681
2682CFArrayRef
2683BRCopyActiveBootPartitions(CFURLRef volRoot)
2684{
2685    CFArrayRef bparts, rval = NULL;
2686    char path[PATH_MAX], *bsdname;
2687    struct statfs sfs;
2688    CFDictionaryRef binfo = NULL;
2689
2690    if (!volRoot)        goto finish;
2691
2692    // get BSD Name of volRoot
2693    if (!CFURLGetFileSystemRepresentation(
2694            volRoot, false, (UInt8*)path, sizeof(path))) {
2695        goto finish;
2696    }
2697    if (-1 == statfs(path, &sfs))       goto finish;
2698    if (strlen(sfs.f_mntfromname) < sizeof(_PATH_DEV)) {
2699        goto finish;
2700    }
2701    bsdname = sfs.f_mntfromname + (sizeof(_PATH_DEV)-1);
2702
2703    // have libbless provide the helper partitions
2704    // (doesn't vet them as much as hasBootRootBoots())
2705    if (BLCreateBooterInformationDictionary(NULL, bsdname, &binfo))
2706        goto finish;
2707    bparts = CFDictionaryGetValue(binfo, kBLAuxiliaryPartitionsKey);
2708
2709    // success -> retain sub-dictionary for caller
2710    if (bparts && CFArrayGetCount(bparts)) {
2711        rval = CFRetain(bparts);
2712    }
2713
2714finish:
2715    if (binfo)      CFRelease(binfo);
2716
2717    return rval;
2718}
2719
2720/*******************************************************************************
2721*******************************************************************************/
2722void
2723_daDone(DADiskRef disk __unused, DADissenterRef dissenter, void *ctx)
2724{
2725    if (dissenter)
2726        CFRetain(dissenter);
2727    *(DADissenterRef*)ctx = dissenter;
2728    CFRunLoopStop(CFRunLoopGetCurrent());   // assumed okay even if not running
2729}
2730
2731/*******************************************************************************
2732* We don't want to wind up invoking kextcache using assembled paths that have
2733* repeating slashes. Note that paths in bootcaches.plist are absolute so
2734* appending them should always put a slash in as expected.
2735*******************************************************************************/
2736static void removeTrailingSlashes(char * path)
2737{
2738    size_t pathLength = strlen(path);
2739    size_t scanIndex = pathLength - 1;
2740
2741    if (!pathLength) return;
2742
2743    while (path[scanIndex] == '/') {
2744        path[scanIndex] = '\0';
2745        if (scanIndex == 0)   break;
2746        scanIndex--;
2747    }
2748
2749    return;
2750}
2751
2752
2753
2754/******************************************************************************
2755 * updateMount() remounts the volume with the requested flags!
2756 *****************************************************************************/
2757int
2758updateMount(mountpoint_t mount, uint32_t mntgoal)
2759{
2760    int result = ELAST + 1;         // 3/22/12: all paths set result
2761    DASessionRef session = NULL;
2762    CFStringRef toggleMode = CFSTR("updateMountMode");
2763    CFURLRef volURL = NULL;
2764    DADiskRef disk = NULL;
2765    DADissenterRef dis = (void*)kCFNull;
2766    CFStringRef mountargs[] = {
2767            CFSTR("update"),
2768       ( mntgoal & MNT_NODEV          ) ? CFSTR("nodev")    : CFSTR("dev"),
2769       ( mntgoal & MNT_NOEXEC         ) ? CFSTR("noexec")   : CFSTR("exec"),
2770       ( mntgoal & MNT_NOSUID         ) ? CFSTR("nosuid")   : CFSTR("suid"),
2771       ( mntgoal & MNT_RDONLY         ) ? CFSTR("rdonly")   : CFSTR("rw"),
2772       ( mntgoal & MNT_DONTBROWSE     ) ? CFSTR("nobrowse") : CFSTR("browse"),
2773       (mntgoal & MNT_IGNORE_OWNERSHIP) ? CFSTR("noowners") : CFSTR("owners"),
2774       NULL };
2775
2776    // same 'dis' logic as mountBoot in update_boot.c
2777    if (!(session = DASessionCreate(nil))) {
2778        result = ENOMEM; goto finish;
2779    }
2780    DASessionScheduleWithRunLoop(session, CFRunLoopGetCurrent(), toggleMode);
2781    if (!(volURL=CFURLCreateFromFileSystemRepresentation(nil, (void*)mount,
2782                                                   strlen(mount), true))) {
2783        result = ENOMEM; goto finish;
2784    }
2785    if (!(disk = DADiskCreateFromVolumePath(nil, session, volURL))) {
2786        result = ENOMEM; goto finish;
2787    }
2788    DADiskMountWithArguments(disk, NULL, kDADiskMountOptionDefault, _daDone,
2789                             &dis, mountargs);
2790
2791    while (dis == (void*)kCFNull) {
2792        CFRunLoopRunInMode(toggleMode, 0, true);    // _daDone updates 'dis'
2793    }
2794    if (dis) {
2795        result = DADissenterGetStatus(dis);     // XX errno |= unix_err()
2796        if (result == 0)    result = ELAST + 1;
2797        goto finish;
2798    }
2799
2800    result = 0;
2801
2802finish:
2803    if (dis && dis != (void*)kCFNull)   CFRelease(dis);
2804    if (disk)                           CFRelease(disk);
2805    if (session)                        CFRelease(session);
2806    if (volURL)                         CFRelease(volURL);
2807
2808    if (result) {
2809        OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
2810            "Warning: couldn't update %s->f_flags to %#x: error %#x", mount,
2811            mntgoal, result);
2812    }
2813
2814    return result;
2815}
2816
2817/******************************************************************************
2818 * returns the result of fork/exec (negative on error; pid on success)
2819 * a (waited-for) helper exit status will also be returned (see fork_program.c)
2820 * - 'force' -> -f to ignore bootstamps (13784516 removed only use)
2821 *****************************************************************************/
2822// kextcache -u helper sets up argv
2823pid_t
2824launch_rebuild_all(char * rootPath, Boolean force, Boolean wait)
2825{
2826    pid_t rval = -1;
2827    int argc, argi = 0;
2828    char **kcargs = NULL;
2829
2830    //  argv[0] '-F'  '-u'  root          -f ?       NULL
2831    argc =  1  +  1  +  1  +  1  + (force == true) +  1;
2832    kcargs = malloc(argc * sizeof(char*));
2833    if (!kcargs)    goto finish;
2834
2835    kcargs[argi++] = "/usr/sbin/kextcache";
2836    // fork_program(wait=false) also sets IOPOL_THROTTLE while spawning
2837    kcargs[argi++] = "-F";      // lower priority within kextcache
2838    if (force) {
2839        kcargs[argi++] = "-f";
2840    }
2841    kcargs[argi++] = "-u";
2842    kcargs[argi++] = rootPath;
2843    // kextcache reads bc.plist so nothing more needed
2844
2845    kcargs[argi] = NULL;    // terminate the list
2846
2847   /* wait:false means the return value is <0 for fork/exec failures and
2848    * the pid of the forked process if >0.
2849    */
2850    rval = fork_program(kcargs[0], kcargs, wait);
2851
2852finish:
2853    if (kcargs)     free(kcargs);
2854
2855    if (rval < 0)
2856        OSKextLog(/* kext */ NULL,
2857            kOSKextLogErrorLevel | kOSKextLogIPCFlag,
2858            "Error launching kextcache -u.");
2859
2860    return rval;
2861}
2862
2863/*******************************************************************************
2864*******************************************************************************/
2865struct nameAndUUID {
2866    uint32_t nbytes;
2867    struct attrreference nameref;
2868    uuid_t uuid;
2869    char namedata[NAME_MAX+1];
2870};
2871int
2872copyVolumeInfo(const char *vol_path, uuid_t *vol_uuid, CFStringRef *cslvf_uuid,
2873               char vol_bsd[DEVMAXPATHSIZE], char vol_name[NAME_MAX])
2874{
2875    int bsderr, rval = ENODEV;
2876    struct nameAndUUID attrs;
2877    struct attrlist attrdesc = { ATTR_BIT_MAP_COUNT, 0, 0, ATTR_VOL_INFO |
2878                                 ATTR_VOL_NAME | ATTR_VOL_UUID, 0, 0, 0 };
2879    struct statfs sfs;
2880    char *bsdname;
2881    io_object_t ioObj = IO_OBJECT_NULL;
2882    CFTypeRef regEntry = NULL;
2883
2884    // get basic data
2885    // (don't worry about FSOPT_REPORT_FULLSIZE; NAME_MAX+1 is plenty :]
2886    if ((bsderr=getattrlist(vol_path, &attrdesc, &attrs, sizeof(attrs), 0))
2887            || attrs.nbytes >= sizeof(attrs)) {
2888        rval = errno; goto finish;
2889    }
2890    if (vol_bsd || cslvf_uuid) {
2891        if ((bsderr = statfs(vol_path, &sfs))) {
2892            rval = errno; goto finish;
2893        }
2894        bsdname = sfs.f_mntfromname;
2895        if (strncmp(bsdname, _PATH_DEV, strlen(_PATH_DEV)) == 0) {
2896            bsdname += strlen(_PATH_DEV);
2897        }
2898    }
2899
2900
2901    // handle UUID if requested
2902    if (vol_uuid) {
2903        memcpy(*vol_uuid, attrs.uuid, sizeof(uuid_t));
2904    }
2905
2906    // CoreStorage UUID if requested
2907    if (cslvf_uuid) {
2908        CFDictionaryRef matching;   // IOServiceGetMatchingServices() releases
2909        matching = IOBSDNameMatching(kIOMasterPortDefault, 0, bsdname);
2910        if (!matching) {
2911            rval = ENOMEM; goto finish;
2912        }
2913        ioObj = IOServiceGetMatchingService(kIOMasterPortDefault, matching);
2914        matching = NULL;        // IOServiceGetMatchingService() released
2915        if (ioObj == IO_OBJECT_NULL) {
2916            rval = ENODEV; goto finish;
2917        }
2918        regEntry = IORegistryEntryCreateCFProperty(ioObj,
2919                                    CFSTR(kCoreStorageLVFUUIDKey), nil, 0);
2920        if (regEntry && CFGetTypeID(regEntry) == CFStringGetTypeID()) {
2921            // retain the result (regEntry released below)
2922            *cslvf_uuid = (CFStringRef)CFRetain(regEntry);
2923        } else {
2924            *cslvf_uuid = NULL;
2925        }
2926    }
2927
2928    // BSD Name
2929    if (vol_bsd) {
2930        if (strlcpy(vol_bsd, bsdname, DEVMAXPATHSIZE) >= DEVMAXPATHSIZE) {
2931            rval = EOVERFLOW; goto finish;
2932        }
2933    }
2934
2935    // volume name
2936    if (vol_name) {
2937        char *volname = (char*)&attrs.nameref + attrs.nameref.attr_dataoffset;
2938        (void)strlcpy(vol_name, volname, NAME_MAX);
2939    }
2940
2941    rval = 0;
2942
2943finish:
2944    if (rval) {
2945        OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
2946                  "%s: %s", vol_path, strerror(rval));
2947    }
2948
2949    if (regEntry)                   CFRelease(regEntry);
2950    if (ioObj != IO_OBJECT_NULL)    IOObjectRelease(ioObj);
2951    // matching consumed by IOServiceGetMatchingService()
2952
2953    return rval;
2954}
2955
2956