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