1/*
2 * Copyright (c) 2006-2012 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23/*
24 * FILE: update_boot.c
25 * AUTH: Soren Spies (sspies)
26 * DATE: 8 June 2006
27 * DESC: implement 'kextcache -u' (copying to Apple_Boot partitions)
28 *
29 */
30
31#include <bless.h>
32#include <miscfs/devfs/devfs.h>     // UID_ROOT, GID_WHEEL
33#include <fcntl.h>
34#include <hfs/hfs_mount.h>          // hfs_mount_args
35#include <libgen.h>
36#include <mach/mach_error.h>
37#include <mach/mach_port.h>         // mach_port_allocate()
38#include <servers/bootstrap.h>
39#include <sysexits.h>
40#include <sys/errno.h>
41#include <sys/param.h>
42#include <sys/mount.h>
43#include <sys/xattr.h>
44#include <unistd.h>
45
46#include <IOKit/kext/kextmanager_types.h>
47#include <IOKit/kext/OSKextPrivate.h>
48#include <IOKit/kext/kextmanager_types.h>
49#include <IOKit/kext/kextmanager_mig.h>
50#include <IOKit/IOKitLib.h>
51#include <IOKit/IOBSD.h>
52#include <IOKit/storage/IOMedia.h>
53#include <IOKit/storage/IOPartitionScheme.h>
54#include <MediaKit/GPTTypes.h>
55#include <bootfiles.h>
56#include <CoreFoundation/CoreFoundation.h>
57#include <DiskArbitration/DiskArbitration.h>
58#include <DiskArbitration/DiskArbitrationPrivate.h>
59
60#include "bootcaches.h"
61#include "bootroot_internal.h"      // includes bootroot.h
62#include "fork_program.h"
63#include "safecalls.h"
64#include "kext_tools_util.h"
65
66
67/******************************************************************************
68* File-Globals
69******************************************************************************/
70static mach_port_t sBRUptLock = MACH_PORT_NULL;
71static uuid_t      s_vol_uuid;      // XX not threadsafe (10561671)
72static mach_port_t sKextdPort = MACH_PORT_NULL;
73
74
75/******************************************************************************
76* Types
77******************************************************************************/
78enum bootReversions {
79    nothingSerious = 0,
80    noLabel,                // 1
81    copyingOFBooter,        // 2
82    copyingEFIBooter,       // 3
83    copiedBooters,          // 4
84    activatingOFBooter,     // 5
85    activatingEFIBooter,    // 6
86    activatedBooters,       // 7
87};
88
89enum blessIndices {
90    kSystemFolderIdx = 0,
91    kEFIBooterIdx = 1
92    // sBLSetBootFinderInfo() preserves other values
93};
94
95const char * bootReversionsStrings[] = {
96    NULL,           // unused
97    "Label deleted",
98    "Unlinking and copying BootX booter",
99    "Unlinking and copying EFI booter",
100    "Booters copied",
101    "Activating BootX",
102    "Activating EFI booter",
103    "Booters activated"
104};
105
106
107// for Apple_Boot update
108struct updatingVol {
109    struct bootCaches *caches;          // parsed bootcaches.plist data
110    char srcRoot[PATH_MAX];             // src for boot caches as char[]
111    uuid_string_t host_uuid;            // initialRoot's UUID
112    CFDictionaryRef bpoverrides;        // provided Boot.plist overrides
113    CFDictionaryRef csfdeprops;         // CSFDE property cache data (!encr)
114    char flatTarget[PATH_MAX];          // indy <helper>/<target>, min-RPS
115    OSKextLogSpec warnLogSpec;          // flags for file access warnings
116    OSKextLogSpec errLogSpec;           // flags for file access errors
117    CFArrayRef boots;                   // BSD Names of Apple_Boot partitions
118    DASessionRef dasession;             // diskarb handle
119    BRBlessStyle blessSpec;             // support non-default BR..ToDir()
120    BRUpdateOpts_t opts;                // "how hard to try" & other flags
121
122    // default to false for the common kextcache -u case
123    Boolean earlyBoot;                  // detect early boot check
124    Boolean doRPS, doMisc, doBooters;   // what needs updating
125    Boolean doSanitize, cleanOnceDir;   // how to cleanse each helper
126    Boolean useOnceDir;                 // copy to com.apple.boot.once
127
128    // updated as each Apple_Boot is updated
129    int bootIdx;                        // which helper are we updating
130    enum bootReversions changestate;    // track changes to roll back
131    char bsdname[DEVMAXPATHSIZE];       // bsdname of Apple_Boot
132    DADiskRef curBoot;                  // and matching diskarb ref
133    char curMount[MNAMELEN];            // path to current boot mountpt
134    int curbootfd;                      // Sec: handle to curMount
135    char dstdir[PATH_MAX];              // full path to main dest.
136    char efidst[PATH_MAX], ofdst[PATH_MAX];
137    Boolean onAPM;                      // tweak support based on pmap
138    Boolean detectedRecovery;           // seen com.apple.recovery.boot?
139};
140
141
142/******************************************************************************
143* Definitions
144******************************************************************************/
145#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}
146
147// for non-RPS content, including booters
148#define OLDEXT ".old"
149#define NEWEXT ".new"
150#define SCALE_2xEXT "_2x"
151#define CONTENTEXT ".contentDetails"
152#define kBRRootUUIDFile ".root_uuid"
153#define kBRBootOnceDir "/com.apple.boot.once"
154
155// NOTE: These strings must be the same length, or code in ucopyRPS will break!
156// There is a compile time assert in the function to this effect.
157#define BOOTPLIST_NAME "com.apple.Boot.plist"
158#define BOOTPLIST_APM_NAME "com.apple.boot.plist"
159
160
161/******************************************************************************
162* Helpers
163******************************************************************************/
164
165// diskarb
166static int mountBoot(struct updatingVol *up);
167static void unmountBoot(struct updatingVol *up);
168
169// ucopy = unlink & copy
170// no race for RPS, so install it first
171static int ucopyRPS(struct updatingVol *s);  // nuke/copy to inactive
172// the label files (for example) have no fallback, .new is harmless
173// XX ucopy"Preboot/Firmware"
174static int ucopyMisc(struct updatingVol *s);        // use/overwrite .new names
175// booters have fallback paths, but originals might be broken
176static int ucopyBooters(struct updatingVol *s);     // nuke/copy booters (inact)
177// no label -> hint of indeterminate state (label key in plist?)
178static int moveLabels(struct updatingVol *s);       // move aside
179static int nukeBRLabels(struct updatingVol *s);     // byebye (all?)
180// booters have worst critical:fragile ratio (point of departure)
181static int activateBooters(struct updatingVol *s);  // bless new names
182// and the RPS data needed for booting
183static int activateRPS(struct updatingVol *s);      // leap-frog w/rename()
184// finally, the label (indicating a working system via this helper partition)
185// XX activate"FirmwarePaths/postboot"
186static int activateMisc(struct updatingVol *s);     // rename .new / label
187// and now that we're safe
188static int nukeFallbacks(struct updatingVol *s);
189static int eraseRPS(struct updatingVol *up, char *toErase);
190static int addHostVolInfo(struct updatingVol *up, CFURLRef hostVol,
191                          CFDictionaryRef bootPrefOverrides, CFURLRef targetStr,
192                          CFStringRef pickerLabel);
193
194// cleanup routines (RPS is the last step; activateMisc handles label)
195static int revertState(struct updatingVol *up);
196
197/* Chain of Trust
198 * Our goal is to do anything the bootcaches.plist says, but only to that vol.
199 * #1 we only pay attention to root-owned bootcaches.plist files
200 * #2 we get an fd to the bootcaches.plist              [trust is here]
201// * #3 we validate the bc.plist fd after getting an fd to the volume's root
202 * #4 we use stored bsdname for libbless
203 * #5 we validate cachefd after the call to bless       [trust -> bsdname]
204 * #6 we get curbootfd after each apple_boot mount
205 * #7 we validate cachefd after the call                [trust -> curfd]
206 * #8 operations take an fd limiting their scope to the mount
207 */
208
209// XX should probably rename to all-caps
210// seed errno since strlxxx routines do not set it. This will make
211// downstream error messages more meaningful (since we're often logging the
212// errno value and message).
213#define pathcpy(dst, src) do { \
214            Boolean useErrno = (errno == 0); \
215            if (useErrno)       errno = ENAMETOOLONG; \
216            if (strlcpy(dst, src, PATH_MAX) >= PATH_MAX)  goto finish; \
217            if (useErrno)       errno = 0; \
218    } while(0)
219#define pathcat(dst, src) do { \
220            Boolean useErrno = (errno == 0); \
221            if (useErrno)       errno = ENAMETOOLONG; \
222            if (strlcat(dst, src, PATH_MAX) >= PATH_MAX)  goto finish; \
223            if (useErrno)       errno = 0; \
224    } while(0)
225#define makebootpath(path, rpath) do { \
226                                    pathcpy(path, up->curMount); \
227                                    if (up->useOnceDir) { \
228                                        pathcat(path, kBRBootOnceDir); \
229                                    } \
230                                    if (up->useOnceDir || up->flatTarget[0]) { \
231                                        pathcat(path, up->flatTarget); \
232                                        /* XX 10561671: basename unsafe */ \
233                                        pathcat(path, "/"); \
234                                        pathcat(path, basename(rpath)); \
235                                    } else { \
236                                        pathcat(path, rpath); \
237                                    } \
238                                } while(0)
239
240// continue versions
241#define PATHCPYcont(dst, src) do { \
242            if (strlcpy(dst, src, PATH_MAX) >= PATH_MAX)  continue; \
243    } while(0)
244#define PATHCATcont(dst, src) do { \
245            if (strlcat(dst, src, PATH_MAX) >= PATH_MAX)  continue; \
246    } while(0)
247
248// break versions
249#define PATHCPYbreak(dst, src) do { \
250            if (strlcpy(dst, src, PATH_MAX) >= PATH_MAX)  break; \
251    } while(0)
252#define PATHCATbreak(dst, src) do { \
253            if (strlcat(dst, src, PATH_MAX) >= PATH_MAX)  break; \
254    } while(0)
255
256#define LOGERRxlate(up, ctx1, ctx2, errval) do {  \
257        char *c2cpy = ctx2, ctx[256];  \
258        if (ctx2 != NULL) {  \
259            snprintf(ctx, sizeof(ctx), "%s: %s", ctx1, c2cpy);  \
260        } else {  \
261            snprintf(ctx, sizeof(ctx), "%s", ctx1);  \
262        }  \
263        /* if necessary, modify passed-in argument so errno is returned */  \
264        if (errval == -1)       errval = errno;  \
265        OSKextLog(/* kext */ NULL, up->errLogSpec,  \
266                  "%s: %s", ctx, strerror(errval));  \
267    } while(0)
268
269
270// XX there is overlap between errno values and sysexits
271static int
272getExitValueFor(errval)
273{
274    int rval;
275
276    switch (errval) {
277        case ELAST + 1:
278            rval = EX_SOFTWARE;
279            break;
280        case EPERM:
281            rval = EX_NOPERM;
282            break;
283        case EAGAIN:
284        case ENOLCK:
285            rval = EX_OSERR;
286            break;
287        case -1:
288            switch (errno) {
289                case EIO:
290                    rval = EX_IOERR;
291                    break;
292                default:
293                    rval = EX_OSERR;
294                    break;
295            }
296            break;
297        default:
298            rval = errval;
299    }
300
301    return rval;
302}
303
304// TM should no longer add to Apple_Boot partitions (8992773)
305#define MOBILEBACKUPS_DIR "/.MobileBackups"
306#define MDS_BULWARK "/.metadata_never_index"
307#define MDS_DIR "/.Spotlight-V100"
308#define FSEVENTS_BULWARK "/.fseventsd/no_log"
309#define FSEVENTS_DIR "/.fseventsd"
310#define NETBOOT_SHADOW "/.com.apple.NetBootX/shadowfile"
311static int
312sanitizeBoot(struct updatingVol *up)
313{
314    int lastErrno = 0;              // best effort
315    int fd;
316    struct statfs sfs;
317    char bloatp[PATH_MAX], blockp[PATH_MAX];
318    Boolean blockMissing = true;
319    struct stat sb;
320
321    OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
322              "Removing unnecessary bloat.");
323
324    // X if the size similar to a user's data volume, don't scrub
325    if ((fstatfs(up->curbootfd, &sfs) == 0) &&
326            (sfs.f_blocks * sfs.f_bsize > 1ULL<<32)) {
327        goto finish;
328    }
329
330    // ensure root ownership of the helper root (opened in mountBoot())
331    if ((fstat(up->curbootfd, &sb) == 0) &&
332            (sb.st_uid != UID_ROOT || sb.st_gid != GID_WHEEL)) {
333        if (fchown(up->curbootfd, UID_ROOT, GID_WHEEL) == -1) {
334            lastErrno = errno;
335        }
336    }
337
338    // Time Machine
339    makebootpath(bloatp, MOBILEBACKUPS_DIR);
340    if (0 == (stat(bloatp, &sb))) {
341        if (sdeepunlink(up->curbootfd, bloatp) == -1) {
342            lastErrno = errno;
343        }
344    }
345
346    // NetBoot shadow file (see 11535905)
347    makebootpath(bloatp, NETBOOT_SHADOW);
348    if (0 == (stat(bloatp, &sb))) {
349        if (sdeepunlink(up->curbootfd, bloatp) == -1) {
350            lastErrno = errno;
351        }
352    }
353
354    // Spotlight
355    makebootpath(blockp, MDS_BULWARK);
356    if (-1 == stat(blockp, &sb) && errno == ENOENT) {
357        fd = sopen(up->curbootfd, blockp, O_CREAT, kCacheFileMode);
358        if (fd == -1) {
359            lastErrno = errno;
360        } else {
361            close(fd);
362        }
363    }
364    makebootpath(bloatp, MDS_DIR);
365    if (0 == (stat(bloatp, &sb))) {
366        if (sdeepunlink(up->curbootfd, bloatp) == -1) {
367            lastErrno = errno;
368        }
369    }
370
371    // FSEvents has its antithesis inside its directory :P
372    // we'll assume if no_log is present, that there's no cruft
373    makebootpath(bloatp, FSEVENTS_DIR);
374    makebootpath(blockp, FSEVENTS_BULWARK);
375    if (0 == (stat(bloatp, &sb))) {
376        if (-1 == stat(blockp, &sb) && errno == ENOENT) {
377            // no bulwark, so nuke the whole thing
378            if (sdeepunlink(up->curbootfd, bloatp) == -1) {
379                lastErrno = errno;
380            }
381        } else {
382            blockMissing = false;
383        }
384    }
385
386    if (blockMissing) {
387        // then recreate the directory and the "stay away" file
388        if (sdeepmkdir(up->curbootfd, bloatp, kCacheDirMode) == -1) {
389            lastErrno = errno;
390        }
391        fd = sopen(up->curbootfd, blockp, O_CREAT, kCacheFileMode);
392        if (fd == -1) {
393            lastErrno = errno;
394        } else {
395            close(fd);
396        }
397    }
398
399    // no accumulated errors -> success
400
401finish:
402    if (lastErrno) {
403        OSKextLog(NULL, up->warnLogSpec, "sanitizeBoot(): Warning: %s",
404                  strerror(lastErrno));
405    }
406
407    return lastErrno;
408}
409
410
411/******************************************************************************
412 * checkForMissingFiles
413 * Look for missing files in the relevant Apple_boot (helper) partition.  If
414 * any of the files we care about are missing then we force an update of those
415 * files.
416******************************************************************************/
417static void
418checkForMissingFiles(struct updatingVol *up)
419{
420    unsigned i;
421    char srcpath[PATH_MAX], dstpath[PATH_MAX];
422    struct stat sb;
423
424    OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
425              "Looking for missing files.");
426
427    // forced updates not allowed with -U (early boot)
428    if (up->opts & kBRUExpectUpToDate)  return;
429
430    /* looking for missing .VolumeIcon.icns, SystemVersion.plist,
431     * PlatformSupport.plist, .disk_label, etc
432     */
433    if (!up->doMisc) {
434        for (i = 0; i < up->caches->nmisc; i++) {
435            pathcpy(srcpath, up->caches->root);
436            pathcat(srcpath, up->caches->miscpaths[i].rpath);
437            makebootpath(dstpath, up->caches->miscpaths[i].rpath);
438
439            /* look to see if our source file exists (some may not) and if it does
440             * and it's clone is NOT in Apple_Boot then force an update
441             */
442            if (stat(srcpath, &sb) == 0) {
443                // source file exists, now check on Apple_Boot
444                if (stat(dstpath, &sb) != 0 && errno == ENOENT) {
445                    // missing file, force an update
446                    up->doMisc = true;
447                    OSKextLog(nil,kOSKextLogFileAccessFlag|kOSKextLogBasicLevel,
448                        "Helper partition missing misc files, forcing update");
449                    break;
450                }
451            }
452        }
453    }
454
455    // now look for boot.efi
456    if (!up->doBooters) {
457        if (up->caches->efibooter.rpath[0]) {
458            makebootpath(dstpath, up->caches->efibooter.rpath);
459            if (stat(dstpath, &sb) != 0 && errno == ENOENT) {
460                // missing file, force an update
461                up->doBooters = true;
462                OSKextLog(NULL, kOSKextLogFileAccessFlag|kOSKextLogBasicLevel,
463                         "Helper partition missing EFI booter, forcing update");
464                goto finish;
465            }
466        }
467        // OF booter deserves love too :)
468        if (up->caches->ofbooter.rpath[0]) {
469            makebootpath(dstpath, up->caches->ofbooter.rpath);
470            if (stat(dstpath, &sb) != 0 && errno == ENOENT) {
471                // missing file, force an update
472                up->doBooters = true;
473                OSKextLog(NULL, kOSKextLogFileAccessFlag|kOSKextLogBasicLevel,
474                         "Helper partition missing OF booter, forcing update");
475                goto finish;
476            }
477        }
478    }
479
480finish:
481    return;
482}
483
484/*******************************************************************************
485* updateBootHelpers() updates per the passed-in struct updatingVol.
486* Sec: must ensure each target is one of the source's Apple_Boot partitions
487* Logically, callers provide up->boots,caches but initContext() also
488* fills in up->dasession.  Callers must also releaseContext() afterwards.
489*
490* "expect up to date" -> just move the labels aside
491******************************************************************************/
492static int
493updateBootHelpers(struct updatingVol *up)
494{
495    int errnum, result = 0;
496    up->curbootfd = -1;
497    struct stat sb;
498    CFIndex bootcount, bootupdates = 0;
499
500    // if the plist has gone stale, punt
501    if ((result = fstat(up->caches->cachefd, &sb))) {
502        OSKextLog(NULL, up->errLogSpec, "fstat(cachefd): %s", strerror(errno));
503        goto finish;
504    }
505
506    bootcount = CFArrayGetCount(up->boots);
507    for (up->bootIdx = 0; up->bootIdx < bootcount; up->bootIdx++) {
508        char path[PATH_MAX];
509
510        up->changestate = nothingSerious;           // init state
511        if ((errnum = mountBoot(up))) {             // sets curMount
512            result = errnum; goto bootfail;
513        }
514
515        // if directed, nuke anything that doesn't belong
516        if (up->doSanitize) {
517            (void)sanitizeBoot(up);
518        }
519        if (up->cleanOnceDir &&
520                strlcpy(path, up->curMount, PATH_MAX) < PATH_MAX &&
521                strlcat(path, kBRBootOnceDir, PATH_MAX) < PATH_MAX &&
522                0 == stat(path, &sb)) {
523            (void)sdeepunlink(up->curbootfd, path);
524        }
525
526        // If files are missing, update up.do* to ensure we copy them
527        // (implicitly forcing their update in subsequent helpers).
528        checkForMissingFiles(up);
529
530        if (up->doRPS && (result = ucopyRPS(up))) {
531            goto bootfail;  // -> inactive
532        }
533        if (up->doMisc) {
534            (void) ucopyMisc(up);  // -> .new files
535        }
536
537        // get the label out of the way (should be optional?)
538        // expectUpToDate => early boot -> harder to generate label?
539        if (up->opts & kBRUExpectUpToDate) {
540            if ((result = moveLabels(up))) {
541                goto bootfail;
542            }
543        } else {
544            if ((result = nukeBRLabels(up))) {
545                goto bootfail;
546            }
547        }
548
549        if (up->doBooters && (result = ucopyBooters(up))) {
550            goto bootfail;      // .old still active
551        }
552        // If Recovery OS was available, we could swap these two and leave
553        // the Recovery OS blessed until RPS and new booters were activated.
554        if (up->doBooters && (result = activateBooters(up))) { // committed
555            goto bootfail;
556        }
557        // 10.x.n+1 booters remain compatible 10.x.n kernels?? (power outage!)
558        if (up->doRPS && (result = activateRPS(up))) {         // complete
559            goto bootfail;
560        }
561        if ((result = activateMisc(up))) {
562            goto bootfail;      // reverts label
563        }
564
565        up->changestate = nothingSerious;
566        bootupdates++;      // loop success
567        // -U -> updates are a warning
568        OSKextLog(NULL,kOSKextLogFileAccessFlag|((up->opts & kBRUExpectUpToDate)
569                  ? kOSKextLogWarningLevel : kOSKextLogBasicLevel),
570                  "Successfully updated %s%s.", up->bsdname, up->flatTarget);
571
572bootfail:
573        // clean up this helper only, no hard failures in the loop
574        if (up->changestate!=nothingSerious && !(up->opts&kBRUHelpersOptional)){
575            OSKextLog(NULL, up->errLogSpec,
576                      "Error updating helper partition %s, state %d: %s.",
577                      up->bsdname, up->changestate,
578                      bootReversionsStrings[up->changestate]);
579        }
580        // unroll any changes we may have made
581        (void)revertState(up);     // smart enough to do nothing
582
583        // clean up and unmount (flatTarget -> might not be a helper)
584        // X could check for MNT_DONTBROWSE as a hint it's okay to unmount
585        if (nukeFallbacks(up)) {
586            OSKextLog(NULL, up->errLogSpec, "Warning: %s%s may be untidy.",
587                      up->bsdname, up->flatTarget);
588        }
589        unmountBoot(up);       // smart, handles "when to unmount" policy
590    }
591
592    if (bootupdates != bootcount && !(up->opts&kBRUHelpersOptional)) {
593        OSKextLog(NULL, up->errLogSpec, "Failed to update helper partition%s.",
594                  bootcount - bootupdates == 1 ? "" : "s");
595        // bullet-proofing: make sure there is a generic error
596        if (result == 0) {
597            // should always be a non-zero result at this point
598            result = ELAST + 1;
599        }
600        goto finish;
601    }
602
603finish:
604    return result;
605}
606
607/******************************************************************************
608* entry points to update caches and copy files to helper partitions
609* these culminate in BRUpdateBootFiles() and BRCopyBootFiles().
610******************************************************************************/
611// XX move to bootcaches.[ch]?
612/* sBRUptLock is accessible here and could be used to conditionalize
613   the setting of ...skiplocks.  This function might be useful to
614   kextd, though it would be a significant change in that kextd
615   would now be calling the 'rebuild' functions as well as the
616   'check' functions (instead of calling kextcache -u).  For example,
617   it would preclude multiple stacked kextcache -u processes (good)
618   but change the nature of canceling in-progress updates (unknown).
619   kextd's memory footprint would likely grow (one way or another).
620*/
621int
622checkRebuildAllCaches(struct bootCaches *caches, int oodLogSpec)
623{
624    int opres, result = ELAST + 1;  // no pathc() [yet]
625    struct stat sb;
626
627    if (caches == NULL)  goto finish;
628    // if the caches data is no longer valid, abort immediately
629    if ((opres = fstat(caches->cachefd, &sb))) {
630        result = opres; goto finish;
631    }
632
633    OSKextLog(NULL, kOSKextLogProgressLevel | kOSKextLogArchiveFlag,
634              "Ensuring %s's caches are up to date.", caches->root);
635
636    /* XX Sec (re-review?): can't let an external volume insert a cache
637     * - mktmp/mkstmp used to create temp file at destination
638     * - final rename must be on whatever volume provided the kexts
639     * - if volume is /, then kexts owned by root can be trusted (4623559 fstat)
640     * - otherwise, rename from wrong volume will fail
641     */
642
643    // We have to rely on the system's kextcache + IOKit.framework to
644    // rebuild these caches.  If called on an older system via
645    // libBootRoot against newer cache files, the launched kextcache
646    // processes are unlikely to know how to update the caches.  Errors
647    // should be returned.
648
649    // Avoid deadlock with the kextcache processes which might launch below.
650    // This environment variable tells it *not* to take a lock since we
651    // should be holding it (caller should have called initContext() XX?).
652    setenv("_com_apple_kextd_skiplocks", "1", 1);
653
654
655    // update the various mach_kernel caches
656    if (check_kext_boot_cache_file(caches,
657                  caches->kext_boot_cache_file->rpath, caches->kernel)) {
658
659        // rebuild the mkext under our lock / lack thereof
660        // (-v forwarded via environment variable by kextcache & kextd)
661        OSKextLog(nil, oodLogSpec, "rebuilding %s",
662                caches->kext_boot_cache_file->rpath);
663        if ((opres = rebuild_kext_boot_cache_file(caches, true /*wait*/,
664                caches->kext_boot_cache_file->rpath, caches->kernel))) {
665            OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
666                      "Error %d rebuilding %s", result,
667                      caches->kext_boot_cache_file->rpath);
668                result = opres; goto finish;
669        }
670    } else {
671        OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogArchiveFlag,
672                  "Primary kext cache does not need update.");
673    }
674
675
676
677    // Check/rebuild the CSFDE property cache which goes into the Apple_Boot.
678    // It's less critical for booting, but more critical for security.
679    if (check_csfde(caches)) {
680        OSKextLog(NULL,oodLogSpec,"rebuilding %s",caches->erpropcache->rpath);
681        if ((opres = rebuild_csfde_cache(caches))) {
682            OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
683                      "Error %d rebuilding %s", result,
684                      caches->erpropcache->rpath);
685            result = opres; goto finish;
686        }
687    } else {
688        OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogArchiveFlag,
689                  "CSFDE property cache does not need update.");
690    }
691
692    // check on the (optional) localized resources used by EFI Login
693    if (check_loccache(caches)) {
694        OSKextLog(NULL,oodLogSpec,"rebuilding %s",caches->efiloccache->rpath);
695        if ((result = rebuild_loccache(caches))) {
696            OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogArchiveFlag,
697                      "Warning: Error %d rebuilding %s", result == -1
698                      ? errno : result, caches->efiloccache->rpath);
699        }
700        // efiloccache is not required when copying rpspaths
701        // so we can ignore failures to rebuild the cache.
702    } else {
703        OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogArchiveFlag,
704                  "Localized EFI Login resources do not need update.");
705    }
706
707    // success!
708    result = 0;
709
710finish:
711    return result;
712}
713
714
715/*****************************************************************************
716* initContext() sets up a struct updatingVol for use by other functions
717* - volRoot must contain a supported bootcaches.plist
718* - volRoot will be locked with kextd
719* - if available, diskarb will be configured up->dasession
720* - specifiying helperBSDName -> up->boots = [ helperBSDName ]
721* releaseContext() should be called when the context is no longer needed.
722*****************************************************************************/
723#define BOOTCOUNT 1
724static int
725initContext(struct updatingVol *up, CFURLRef srcVol, CFStringRef helperBSDName,
726            BRBlessStyle blessSpec, BRUpdateOpts_t opts)
727{
728    int opres, result = ELAST + 1;      // all paths should reset
729    const void  *values[BOOTCOUNT] = { helperBSDName };
730
731    // start fresh (all booleans to default false values)
732    bzero(up, sizeof(struct updatingVol));
733
734    // callers want to rely on these log spec values even after failure
735    up->warnLogSpec = kOSKextLogArchiveFlag | kOSKextLogWarningLevel;
736    up->errLogSpec = kOSKextLogArchiveFlag | kOSKextLogErrorLevel;
737
738    // stash opts for subroutines
739    up->blessSpec = blessSpec;
740    up->opts = opts;
741
742    // takeVolumeForPath() wants a char* ... comes before up->caches = ...
743    if (!CFURLGetFileSystemRepresentation(srcVol, /* resolveToBase */ true,
744                             (UInt8 *)up->srcRoot,sizeof(up->srcRoot))){
745        OSKextLogStringError(NULL);
746        result = ENOMEM; goto finish;
747    }
748
749    // Technically, we don't need to lock to read bootcaches.plist,
750    // but if there are multiple kextcache -u processes, it leaves
751    // a longer window during which files can be updated.  Also,
752    // being locked means owners are enabled so it's okay for
753    // read[SIC]BootCaches() to make the bootstamps directory.
754
755    // For now, kextcache -U in early boot doesn't lock.
756    // (part of why kextd delays auto-rebuild for 5 minutes)
757    if ((opts & kBRUExpectUpToDate) && getppid() == 2 /* launchctl */) {
758        up->earlyBoot = true;
759    } else {
760        if ((opres = takeVolumeForPath(up->srcRoot))) { // lock (logs errors)
761            result = opres; goto finish;
762        }
763    }
764
765    // initializing the context fails if there's no bootcaches.plist
766    if (!(up->caches = readBootCaches(up->srcRoot, opts))) {
767        result = errno ? errno : ELAST + 1;
768        goto finish;
769    }
770
771    // attempt to configure a disk arb session
772    if ((up->dasession = DASessionCreate(nil))) {
773        // mountBoot and unmountBoot will spin the runloop for this DA session
774        DASessionScheduleWithRunLoop(up->dasession, CFRunLoopGetCurrent(),
775                kCFRunLoopDefaultMode);
776    } else {
777        OSKextLog(NULL, up->warnLogSpec, "Warning: proceeding w/o DiskArb");
778    }
779
780    // if specified, this partition is the one to update
781    if (helperBSDName) {
782        up->boots = CFArrayCreate(nil,values,BOOTCOUNT,&kCFTypeArrayCallBacks);
783    }
784
785    result = 0;
786
787finish:
788    return result;
789}
790
791static void
792releaseContext(struct updatingVol *up, int status)
793{
794    // unmountBoot() not always called
795    if (up->curBoot)            CFRelease(up->curBoot);
796    if (up->curbootfd != -1)    close(up->curbootfd);
797
798    if (up->dasession) {
799        DASessionUnscheduleFromRunLoop(up->dasession, CFRunLoopGetCurrent(),
800                kCFRunLoopDefaultMode);
801        CFRelease(up->dasession);
802        up->dasession = NULL;
803    }
804
805    if (up->boots)          CFRelease(up->boots);
806    if (up->csfdeprops)     CFRelease(up->csfdeprops);
807    if (up->bpoverrides)    CFRelease(up->bpoverrides);
808    if (up->caches)         destroyCaches(up->caches);
809
810    // unlock
811    putVolumeForPath(up->srcRoot, status);
812}
813
814static void
815addDictOverride(const void *key, const void *value, void *ctx)
816{
817    CFMutableDictionaryRef tgtDict = (CFMutableDictionaryRef)ctx;
818
819    // AddValue is "add if absent;" we implement override by removing
820    if (CFDictionaryContainsKey(tgtDict, key))
821        CFDictionaryRemoveValue(tgtDict, key);
822
823    CFDictionaryAddValue(tgtDict, key, value);
824}
825
826static CFDataRef
827createBootPrefData(struct updatingVol *up, uuid_string_t root_uuid,
828                   CFDictionaryRef bootPrefOverrides)
829{
830    CFDataRef rval = NULL;
831    char srcpath[PATH_MAX];
832    int fd = -1;
833    void *buf = NULL;
834    CFDataRef data = NULL;
835    CFMutableDictionaryRef pldict = NULL;
836    CFStringRef UUIDStr = NULL;
837    CFStringRef kernPathStr = NULL;
838
839    OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
840              "creating com.apple.Boot.plist data with UUID %s.",
841              root_uuid);
842
843    // suck in any existing plist
844    do {
845        struct stat sb;
846        PATHCPYcont(srcpath, up->caches->root);
847        PATHCATcont(srcpath, up->caches->bootconfig->rpath);
848        if (-1 == (fd=sopen(up->caches->cachefd,srcpath,O_RDONLY,0)))
849            break;
850        if (fstat(fd, &sb))                                 break;
851        if (sb.st_size > UINT_MAX || sb.st_size > LONG_MAX) break;
852        if (!(buf = malloc((size_t)sb.st_size)))            break;
853        if (read(fd, buf, (size_t)sb.st_size) != sb.st_size)break;
854        if (!(data = CFDataCreate(nil, buf, (long)sb.st_size)))
855            break;
856
857        // make mutable dictionary from file data
858        pldict =(CFMutableDictionaryRef)CFPropertyListCreateFromXMLData(nil,
859                    data, kCFPropertyListMutableContainers, NULL/*errstr*/);
860    } while(0);
861
862    errno = 0;
863
864    // if we got a dictionary, just grab the file mode
865    if (!pldict || CFGetTypeID(pldict)!=CFDictionaryGetTypeID()) {
866        // otherwise, create a dictionary
867        if (pldict)      CFRelease(pldict);     // e.g. if it was a non-dict
868        pldict = CFDictionaryCreateMutable(nil, 1,
869                                        &kCFTypeDictionaryKeyCallBacks,
870                                        &kCFTypeDictionaryValueCallBacks);
871        if (!pldict)    goto finish;
872    }
873
874    // make a CFStr out of the UUID and insert
875    errno = 0;
876    UUIDStr = CFStringCreateWithCString(nil,root_uuid,kCFStringEncodingASCII);
877    if (!UUIDStr)   goto finish;
878    CFDictionarySetValue(pldict, CFSTR(kRootUUIDKey), UUIDStr);
879    if (!CFEqual(CFDictionaryGetValue(pldict,CFSTR(kRootUUIDKey)), UUIDStr))
880        goto finish;
881
882    // if necessary, tell the booter to load <flatTarget>/kernelcache
883    if (up->flatTarget[0] || up->useOnceDir) {
884        char kpath[PATH_MAX] = "";
885        /* XX 10561671: basename() unsafe */
886        if (up->useOnceDir) {
887            pathcat(kpath, kBRBootOnceDir);
888        }
889        pathcat(kpath, up->flatTarget);
890        pathcat(kpath, "/");
891        pathcat(kpath, basename(up->caches->kext_boot_cache_file->rpath));
892        kernPathStr = CFStringCreateWithFileSystemRepresentation(nil, kpath);
893        if (!kernPathStr)   goto finish;
894        CFDictionarySetValue(pldict, CFSTR(kKernelCacheKey), kernPathStr);
895    }
896
897    // add any additional override values
898    if (bootPrefOverrides) {
899        CFDictionaryApplyFunction(bootPrefOverrides,addDictOverride,pldict);
900    }
901
902    rval = CFPropertyListCreateXMLData(nil, pldict);
903
904finish:
905    if (kernPathStr)    CFRelease(kernPathStr);
906    if (UUIDStr)        CFRelease(UUIDStr);
907    if (pldict)         CFRelease(pldict);
908    if (data)           CFRelease(data);
909
910    if (buf)        free(buf);
911    if (fd != -1)   close(fd);
912
913    return rval;
914}
915
916
917/*
918 * needUpdatesNoUUID() checks the top-level bootstamps directory.
919 * 12369781: allow asr to change the fsys UUID w/o first boot rebooting
920 *
921 */
922static int
923needUpdatesNoUUID(CFURLRef volURL, Boolean *anyCritical)
924{
925    int rval = ELAST + 1;           // all paths should reset
926    Boolean doAnyNoUUID = false;
927    char volRoot[PATH_MAX];
928    struct bootCaches *caches = NULL;
929
930    if (!CFURLGetFileSystemRepresentation(volURL, /* resolve */ true,
931                                          (UInt8*)volRoot, sizeof(volRoot))) {
932        OSKextLogStringError(NULL);
933        rval = ENOMEM; goto finish;
934    }
935
936    caches = readBootCaches(volRoot, kBRAnyBootStamps);
937    if (!caches) {
938        rval = errno ? errno : ELAST + 1;
939        goto finish;
940    }
941
942    // needUpdates() has already been called once with higher verbosity
943    doAnyNoUUID = needUpdates(caches, NULL, NULL, NULL,
944                              kOSKextLogGeneralFlag | kOSKextLogDetailLevel);
945
946    if (anyCritical) {
947        *anyCritical = doAnyNoUUID;
948    }
949
950finish:
951    if (caches)     destroyCaches(caches);
952
953    return rval;
954}
955
956/******************************************************************************
957* checkUpdateCachesAndBoots() returns
958* - success (EX_OK / 0) if nothing needs updating
959* - success if updates were successfully made (and expectUTD = false)
960* - EX_OSFILE if updates were unexpectedly needed and successfully made
961******************************************************************************/
962// keeping these active for reliability testing of live builds
963#define BRDBG_OOD_HANG_BOOT_F "/var/db/.BRHangBootOnOODCaches"
964#define BRDBG_HANG_MSG PRODUCT_NAME ": " BRDBG_OOD_HANG_BOOT_F \
965                    "-> hanging on out of date caches"
966#define BRDBG_CONS_MSG "[via /dev/console] " BRDBG_HANG_MSG "\n"
967int
968checkUpdateCachesAndBoots(CFURLRef volumeURL, BRUpdateOpts_t opts)
969{
970    int opres, result = ELAST + 1;          // try to always set on error
971    OSKextLogSpec oodLogSpec = kOSKextLogGeneralFlag | kOSKextLogBasicLevel;
972    Boolean expectUpToDate = (opts & kBRUExpectUpToDate);
973    Boolean doAny = false, cachesUpToDate = false;
974    Boolean loggedOOD = false;
975    struct updatingVol up = { /*NULL...*/ };
976    up.curbootfd = -1;
977
978    // try to configure 'up'; treat missing data per opts
979    if ((opres = initContext(&up, volumeURL, NULL, kBRBlessFSDefault, opts))) {
980        char *bcmsg = NULL;
981        CFArrayRef helpers;
982        switch (opres) {        // describe known problems
983            case ENOENT: bcmsg = "no " kBootCachesPath; break;
984            case EFTYPE: bcmsg = "unrecognized " kBootCachesPath; break;
985            default:     break;
986        }
987        if ((opts & kBRUForceUpdateHelpers) &&
988                (helpers = BRCopyActiveBootPartitions(volumeURL))) {
989            // helper partitions + -f => we require bootcaches.plist
990            OSKextLog(NULL,up.errLogSpec,"%s: %s; aborting",up.srcRoot,bcmsg);
991            CFRelease(helpers);
992            result = opres; goto finish;
993        } else if (bcmsg) {
994            // politely pass on known limitations
995            OSKextLog(NULL, oodLogSpec, "%s: %s; skipping",up.srcRoot,bcmsg);
996            result = 0; goto finish;
997        } else {
998            // unknown error; fail
999            OSKextLog(NULL, up.errLogSpec, "%s: error %d reading "
1000                      kBootCachesPath, up.srcRoot, opres);
1001            result = opres; goto finish;
1002        }
1003    }
1004
1005    // -U logs what is out of date at a a more urgent level than -u
1006    if (expectUpToDate) {
1007        oodLogSpec = up.errLogSpec;
1008    }
1009
1010    // do some real work updating caches *in* the source volume
1011    if ((opres = checkRebuildAllCaches(up.caches, oodLogSpec))) {
1012        result = opres; goto finish;    // error logged by function
1013    }
1014
1015    // record partial success
1016    cachesUpToDate = true;
1017
1018    // 9455881: If requested, only update the caches
1019    if (opts & kBRUCachesOnly) {
1020        goto doneUpdatingHelpers;
1021    }
1022
1023    if (!hasBootRootBoots(up.caches, &up.boots, NULL, &up.onAPM)) {
1024        OSKextLog(NULL, kOSKextLogBasicLevel | kOSKextLogFileAccessFlag,
1025              "%s: no supported helper partitions to update.", up.srcRoot);
1026        goto doneUpdatingHelpers;   // no boots -> nothing more to do
1027    }
1028
1029    /* --- updating a Boot!=Root volume --- */
1030
1031    // these are helper (not OS) partitions & should be clean
1032    up.doSanitize = true;
1033
1034    // figure out what needs updating
1035    // needUpdates() also populates the timestamp values used by updateStamps()
1036    // 12370665 tracks ignoring 'misc' files for the expectUpToDate (-U) case
1037    doAny = needUpdates(up.caches, &up.doRPS, &up.doBooters, &up.doMisc,
1038                        oodLogSpec);
1039
1040    // for -U, give the non-UUID paths a chance (possibly resetting doAny)
1041    if (doAny && expectUpToDate) {
1042        loggedOOD = true;
1043        (void)needUpdatesNoUUID(volumeURL, &doAny);
1044    }
1045
1046#ifdef BRDBG_OOD_HANG_BOOT_F
1047    // check to see if out of date at early boot should cause a hang
1048    if (up.earlyBoot && doAny) {
1049        struct stat sb;
1050        int consfd = open(_PATH_CONSOLE, O_WRONLY|O_APPEND);
1051        while (stat(BRDBG_OOD_HANG_BOOT_F, &sb) == 0) {
1052            OSKextLog(NULL, up.errLogSpec, BRDBG_HANG_MSG);
1053            if (consfd > -1)
1054                write(consfd, BRDBG_CONS_MSG, sizeof(BRDBG_CONS_MSG)-1);
1055            sleep(30);
1056        }
1057    }
1058#endif  // BRDBG_OOD_HANG_BOOT_F
1059
1060    // force ignores needUpdates() and does extra helper cleanup
1061    if (opts & kBRUForceUpdateHelpers) {
1062        up.doRPS = up.doBooters = up.doMisc = true;
1063        up.cleanOnceDir = true;
1064    } else if (!doAny) {
1065        // LogLevelBasic is only emitted with -v and above
1066        // 'Warning' level clarifies previous "not cached" messages
1067        OSKextLogSpec utdlogSpec = kOSKextLogFileAccessFlag;
1068        if (loggedOOD) {
1069            utdlogSpec |= kOSKextLogWarningLevel;
1070        } else {
1071            utdlogSpec |= kOSKextLogBasicLevel;
1072        }
1073        OSKextLog(NULL, utdlogSpec, "%s: helper partitions appear up to date.",
1074                  up.srcRoot);
1075        goto doneUpdatingHelpers;
1076    }
1077
1078    // configure hostVol-based UUIDs, etc
1079    if ((opres = addHostVolInfo(&up, volumeURL, NULL, NULL, NULL))) {
1080        OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1081                  "%s: error %d extracting volume info.", up.srcRoot, opres);
1082        result = opres; goto finish;
1083    }
1084
1085    // Update = root from volume containing caches; fill in csfdeprops
1086    strlcpy(up.host_uuid, up.caches->fsys_uuid, sizeof(up.host_uuid));
1087    if (up.caches->csfde_uuid) {
1088        opres = copyCSFDEInfo(up.caches->csfde_uuid, &up.csfdeprops, NULL);
1089        if (opres) {
1090            result = opres; goto finish;    // error logged by function
1091        }
1092    }
1093
1094    // request actual helper updates
1095    if ((opres = updateBootHelpers(&up))) {
1096        result = opres; goto finish;        // error logged by function
1097    }
1098
1099    if ((opres = updateStamps(up.caches, kBCStampsApplyTimes))) {
1100        OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1101                  "%s: could not update bootstamps.", up.srcRoot);
1102        result = opres; goto finish;
1103    }
1104
1105doneUpdatingHelpers:
1106    // success
1107    result = 0;
1108
1109    // kBRUExpectUpToDate is used to differentiate "success: everything clean"
1110    // from "successfully updated:" the latter exits with EX_OSFILE.  During
1111    // early boot, this informs launchd to force a reboot off fresh caches.
1112    if (doAny && expectUpToDate) {
1113        result = EX_OSFILE;
1114    }
1115
1116finish:
1117    if ((up.opts & kBRUHelpersOptional) && cachesUpToDate) {
1118        // partial success okay
1119        result = 0;
1120    }
1121
1122    // In early boot, kextcache -U pings kextd *after* successful non-update
1123    // instead of before.  XX improve this handoff.
1124    if (up.earlyBoot && expectUpToDate && result != EX_OSFILE) {
1125        if (takeVolumeForPath(up.srcRoot)) {
1126            OSKextLog(NULL, up.errLogSpec, "kextd lock/signal failed");
1127        }
1128    }
1129
1130    // since updateBoots() -> exit(), convert common errors to sysexits(3)
1131    if (result && result != EX_OSFILE) {
1132        result = getExitValueFor(result);
1133    }
1134
1135    // handles unlock / reporting to kextd
1136    releaseContext(&up, result);
1137
1138    // all error paths should log if the functions they call don't
1139
1140    return result;
1141}
1142
1143#define kBRCheckLogSpec (kOSKextLogArchiveFlag | kOSKextLogProgressLevel)
1144OSStatus
1145BRUpdateBootFiles(CFURLRef volURL, Boolean force)
1146{
1147    if (!volURL)
1148        return EINVAL;
1149
1150    return checkUpdateCachesAndBoots(volURL, force?kBRUForceUpdateHelpers:0);
1151}
1152
1153
1154/* error handling style in this function is an experiment to find a
1155   1. correct (doesn't miss errors)
1156   2. robust (doesn't fall over on correctness if not followed)
1157   3. accurate (returns detailed error values)
1158   4. readable (can figure out what's going on)
1159   5. concise (can we achieve #1-3 w/o using both 'errnum' and 'result'?)
1160   style for handling errors.
1161*/
1162static int
1163addHostVolInfo(struct updatingVol *up, CFURLRef hostVol,
1164               CFDictionaryRef bootPrefOverrides, CFURLRef targetStr,
1165               CFStringRef pickerLabel)
1166{
1167    OSStatus result = EOVERFLOW;    // ! only set AFTER error detected !
1168    OSStatus errnum;                // temp var for collecting error values
1169    uuid_t host_uuidbytes;
1170    CFStringRef csUUIDStr = NULL;
1171    char hostroot[PATH_MAX];
1172
1173    up->flatTarget[0] = '\0';
1174
1175    // extract any caller-specified target directory
1176    if (targetStr) {
1177        char targetdir[PATH_MAX] = "", *slash;
1178        if (!CFURLGetFileSystemRepresentation(targetStr, true /*resolve*/,
1179                                              (UInt8*)targetdir, PATH_MAX)) {
1180            result = EINVAL; goto finish;
1181        }
1182        // target dir must not be '/'
1183        slash = targetdir;
1184        while (*slash == '/')       slash++;
1185        if (*slash == '\0') {
1186            result = EINVAL; goto finish;
1187        }
1188        if (targetdir[0] != '/') {    // did caller provide a '/'?
1189            pathcat(up->flatTarget, "/");
1190        }
1191        pathcat(up->flatTarget, targetdir);
1192    }
1193
1194    // get UUIDs
1195    if (!CFURLGetFileSystemRepresentation(hostVol, true /*resolve base*/,
1196            (UInt8*)hostroot, PATH_MAX)) {
1197        result = ENOMEM; goto finish;
1198    }
1199    if ((errnum=copyVolumeInfo(hostroot,&host_uuidbytes,&csUUIDStr,NULL,NULL))){
1200        result = errnum; goto finish;
1201    }
1202    uuid_unparse_upper(host_uuidbytes, up->host_uuid);
1203
1204    // stash overrides for writeBootPrefData(), set up any CSFDE cache
1205    up->bpoverrides = bootPrefOverrides;
1206    if (up->bpoverrides) {
1207        CFRetain(up->bpoverrides);  // balances releaseContext()
1208    }
1209    if (csUUIDStr) {
1210        if ((errnum = copyCSFDEInfo(csUUIDStr, &up->csfdeprops, NULL))) {
1211            result = errnum; goto finish;
1212        }
1213    }
1214
1215    if (pickerLabel) {
1216        if (!CFStringGetFileSystemRepresentation(pickerLabel,
1217                                          up->caches->defLabel, PATH_MAX)) {
1218            result = EINVAL; goto finish;
1219        }
1220    }
1221
1222    result = 0;
1223
1224finish:
1225    if (csUUIDStr)      CFRelease(csUUIDStr);
1226
1227    return result;
1228}
1229
1230
1231/******************************************************************************
1232* copy boot files from source volume to destination partition
1233* - makes sure caches are up to date
1234* - ignore up to date bootstamps
1235* - *do* update bootstamps if srcVol == hostVol
1236* see bootroot.h for more details
1237******************************************************************************/
1238OSStatus
1239BRCopyBootFilesToDir(CFURLRef srcVol,
1240                     CFURLRef initialRoot,
1241                     CFDictionaryRef bootPrefOverrides,
1242                     CFStringRef targetBSDName,
1243                     CFURLRef targetDir,
1244                     BRBlessStyle blessSpec,
1245                     CFStringRef pickerLabel,
1246                     BRUpdateOpts_t opts)
1247{
1248    OSStatus            result = ELAST + 1;     // generic = safest
1249    OSStatus            errnum;
1250    Boolean isBRDefault;
1251    struct updatingVol  up = { /* NULL, ... */ };
1252    up.curbootfd = -1;
1253
1254    // defend libBootRoot entry point
1255    if (!srcVol || !initialRoot || !targetBSDName) {
1256        result = EINVAL; goto finish;
1257    }
1258
1259    // attempt to detect updates that will later look valid to Boot!=Root
1260    // XX: can't tell if helperBSDName will be "the" helper for srcVol
1261    // XXX: could do better on existing B!=R, but need to review DM/FDE
1262    // XX 9173158 tracks teaching B!=R to temporarily ignore custom configs
1263    isBRDefault = !targetDir && (blessSpec & kBRBlessFSDefault) &&
1264                  CFEqual(srcVol, initialRoot);
1265
1266    // configure a single-helper context
1267    errnum = initContext(&up, srcVol, targetBSDName, blessSpec, opts);
1268    if (errnum) {
1269        result = errnum; goto finish;
1270    }
1271
1272    // Make sure all caches are up to date on the source
1273    // (undefined if OOD & system's kext management can't rebuild)
1274    errnum = checkRebuildAllCaches(up.caches, kBRCheckLogSpec);
1275    if (errnum) {
1276        result = errnum; goto finish;
1277    }
1278
1279    // if appropriate, gather timestamp data to apply on success
1280    if (isBRDefault || opts & kBRAnyBootStamps) {
1281        (void)needUpdates(up.caches, NULL, NULL, NULL,
1282                          kOSKextLogGeneralFlag | kOSKextLogProgressLevel);
1283    }
1284
1285    // configure additional options
1286    errnum = addHostVolInfo(&up, initialRoot, bootPrefOverrides,
1287                            targetDir, pickerLabel);
1288    if (errnum) {
1289        result = errnum; goto finish;
1290    }
1291
1292    // BRCopyBootFiles() always copies everything
1293    up.doRPS = up.doBooters = up.doMisc = true;
1294
1295    // new content -> clean up any old temp stuff
1296    up.cleanOnceDir = true;
1297
1298    // sanitize if this is a default Boot!=Root setup
1299    // (XX disables Spotlight, FSEvents; see 4 GB check in sanitizeBoot())
1300    up.doSanitize = isBRDefault;
1301
1302    // get it updated!
1303    errnum = updateBootHelpers(&up);
1304    if (errnum) {
1305        result = errnum; goto finish;
1306    }
1307
1308    // update stamps if this is likely [to later be] a valid B!=R setup
1309    if (isBRDefault || (opts & kBRAnyBootStamps)) {
1310        if (opts & kBRAnyBootStamps) {
1311            // if writing top-level bootstamps, attempt start fresh
1312            char cachedir[PATH_MAX];
1313            pathcpy(cachedir, up.caches->root);
1314            pathcat(cachedir, kTSCacheDir);
1315            (void)sdeepunlink(up.caches->cachefd, cachedir);
1316        }
1317        errnum = updateStamps(up.caches, kBCStampsApplyTimes);
1318        if (errnum) {
1319            result = errnum; goto finish;
1320        }
1321    }
1322
1323    // success
1324    result = 0;
1325
1326finish:
1327    releaseContext(&up, result);
1328
1329    return result;
1330}
1331
1332OSStatus
1333BRCopyBootFiles(CFURLRef srcVol,
1334                CFURLRef initialRoot,
1335                CFStringRef helperBSDName,
1336                CFDictionaryRef bootPrefOverrides)
1337{
1338    return BRCopyBootFilesToDir(srcVol, initialRoot, bootPrefOverrides,
1339                                helperBSDName, NULL /*helperDir*/,
1340                                kBRBlessFSDefault, NULL /*pickerLabel*/,
1341                                kBROptsNone);
1342}
1343
1344/******************************************************************************
1345* FindRPSDir plays rock, paper scissors to identify the location of
1346* the latest complete copy of the files the booter needs.
1347******************************************************************************/
1348static int
1349FindRPSDir(struct updatingVol *up, char prev[PATH_MAX], char current[PATH_MAX],
1350            char next[PATH_MAX])
1351{
1352     char rpath[PATH_MAX], ppath[PATH_MAX], spath[PATH_MAX];
1353/*
1354 * FindRPSDir looks for a "rock," "paper," or "scissors" directory
1355 * - handle all permutations: 3 dirs, any 2 dirs, any 1 dir
1356 */
1357// static EFI_STATUS
1358// FindRPSDir(EFI_FILE_HANDLE BootDir, EFI_FILE_HANDLE *newBoot)
1359//
1360    int rval = ELAST + 1, status;
1361    struct stat r, p, s;
1362    Boolean haveR = false, haveP = false, haveS = false;
1363    char *prevp = NULL, *curp = NULL, *nextp = NULL;
1364
1365    // set up full paths with intervening slash
1366    pathcpy(rpath, up->curMount);
1367    pathcat(rpath, "/");
1368    pathcpy(ppath, rpath);
1369    pathcpy(spath, rpath);
1370
1371    pathcat(rpath, kBootDirR);
1372    pathcat(ppath, kBootDirP);
1373    pathcat(spath, kBootDirS);
1374
1375    status = stat(rpath, &r);   // easier to let this fail
1376    haveR = (status == 0);
1377    status = stat(ppath, &p);
1378    haveP = (status == 0);
1379    status = stat(spath, &s);
1380    haveS = (status == 0);
1381
1382    if (haveR && haveP && haveS) {    // NComb(3,3) = 1
1383        OSKextLog(NULL, up->warnLogSpec,
1384                  "Warning: all of R,P,S exist: picking 'R'; destroying 'P'.");
1385        curp = rpath;   nextp = ppath;  prevp = spath;
1386        if ((rval = eraseRPS(up, nextp)))
1387            goto finish;
1388    }   else if (haveR && haveP) {          // NComb(3,2) = 3
1389        // p wins
1390        curp = ppath;   nextp = spath;  prevp = rpath;
1391    } else if (haveR && haveS) {
1392        // r wins
1393        curp = rpath;   nextp = ppath;  prevp = spath;
1394    } else if (haveP && haveS) {
1395        // s wins
1396        curp = spath;   nextp = rpath;  prevp = ppath;
1397    } else if (haveR) {                     // NComb(3,1) = 3
1398        // r wins by default
1399        curp = rpath;   nextp = ppath;  prevp = spath;
1400    } else if (haveP) {
1401        // p wins by default
1402        curp = ppath;   nextp = spath;  prevp = rpath;
1403    } else if (haveS) {
1404        // s wins by default
1405        curp = spath;   nextp = rpath;  prevp = ppath;
1406    } else {                                          // NComb(3,0) = 0
1407        // we'll start with rock
1408        curp = rpath;   nextp = ppath;  prevp = spath;
1409    }
1410
1411    if (strlcpy(prev, prevp, PATH_MAX) >= PATH_MAX)     goto finish;
1412    if (strlcpy(current, curp, PATH_MAX) >= PATH_MAX)   goto finish;
1413    if (strlcpy(next, nextp, PATH_MAX) >= PATH_MAX)     goto finish;
1414
1415    rval = 0;
1416
1417finish:
1418    if (rval) {
1419        /* can't use errno here since strlcpy and strlcat don't set it */
1420        OSKextLog(NULL, up->errLogSpec,
1421                  "%s - strlcpy or cat failed - >= PATH_MAX", __FUNCTION__);
1422    }
1423
1424    return rval;
1425}
1426
1427/******************************************************************************
1428* BREraseBootFiles() un-does BRCopyBootFiles()
1429******************************************************************************/
1430// helper does wraps BLSetFinderVolumeInfo with schdir()
1431static int
1432sBLSetBootFinderInfo(struct updatingVol *up, uint32_t newvinfo[8])
1433{
1434    int result, fd = -1;
1435    uint32_t    vinfo[8];
1436
1437    result = schdir(up->curbootfd, up->curMount, &fd);
1438    if (result)         goto finish;
1439    result = BLGetVolumeFinderInfo(NULL, ".", vinfo);
1440    if (result)         goto finish;
1441    vinfo[kSystemFolderIdx] = newvinfo[kSystemFolderIdx];
1442    vinfo[kEFIBooterIdx] = newvinfo[kEFIBooterIdx];
1443    result = BLSetVolumeFinderInfo(NULL, ".", vinfo);
1444
1445finish:
1446    if (fd != -1)
1447        (void)restoredir(fd);
1448    return result;
1449}
1450
1451// helper attempts to bless the Recovery OS if present
1452static int
1453blessRecovery(struct updatingVol *up)
1454{
1455    int result;
1456    char path[PATH_MAX];
1457    struct stat sb;
1458    uint32_t vinfo[8] = { 0, };
1459
1460    // look up pathnames & file IDs
1461    result = ENAMETOOLONG;
1462
1463    makebootpath(path, "/" kRecoveryBootDir);
1464    if (stat(path, &sb) == -1) {
1465        result = errno;
1466        goto finish;
1467    }
1468    vinfo[kSystemFolderIdx] = (uint32_t)sb.st_ino;
1469
1470    // append boot.efi
1471    pathcat(path, "/");
1472    pathcat(path, basename(up->caches->efibooter.rpath));
1473    if (stat(path, &sb) == -1) {
1474        result = errno;
1475        goto finish;
1476    }
1477    vinfo[kEFIBooterIdx] = (uint32_t)sb.st_ino;
1478
1479    if ((result = sBLSetBootFinderInfo(up, vinfo))) {
1480        OSKextLog(NULL, up->warnLogSpec,
1481                 "Warning: found recovery booter but couldn't bless it.");
1482    }
1483
1484finish:
1485    return result;
1486}
1487
1488#define RECERR(up, opres, warnmsg) do { \
1489            if (opres == -1 && errno == ENOENT) { \
1490                opres = 0; \
1491            } \
1492            if (opres) { \
1493                if (warnmsg) { \
1494                    OSKextLog(NULL, up->warnLogSpec, warnmsg); \
1495                } \
1496                if (firstErr == 0) { \
1497                    OSKextLog(NULL, up->warnLogSpec, "capturing err %d / %d", \
1498                              opres, errno); \
1499                    firstErr = opres; \
1500                    if (firstErr == -1)     firstErrno = errno; \
1501                } \
1502            } \
1503        } while(0)
1504
1505OSStatus
1506BREraseBootFiles(CFURLRef srcVolRoot, CFStringRef helperBSDName)
1507{
1508    OSStatus result = ELAST + 1;
1509    int opres, firstErrno, firstErr = 0;
1510    char path[PATH_MAX], prevRPS[PATH_MAX], nextRPS[PATH_MAX];
1511    struct stat sb;
1512    uint32_t zerowords[8] = { 0, };
1513    unsigned i;
1514    struct updatingVol  up = { /* NULL, ... */ }, *upp = &up;
1515    up.curbootfd = -1;
1516
1517    // defend libBootRoot entry point
1518    if (!srcVolRoot || !helperBSDName) {
1519        result = EINVAL; goto finish;
1520    }
1521
1522    opres = initContext(&up, srcVolRoot, helperBSDName, kBRBlessFSDefault,
1523                        kBROptsNone);
1524    if (opres) {
1525        result = opres; goto finish;
1526    }
1527
1528    if ((opres = mountBoot(&up))) {        // sets curMount
1529        result = opres; goto finish;
1530    }
1531
1532    // generally best effort
1533
1534    // bless recovery booter if present; else unbless volume
1535    if ((blessRecovery(&up))) {
1536        if ((opres = sBLSetBootFinderInfo(&up, zerowords))) {
1537            firstErr = opres;
1538            OSKextLog(NULL, up.warnLogSpec,
1539                      "Warning: couldn't unbless %s", up.curMount);
1540        }
1541    }
1542
1543    // kill label
1544    opres = nukeBRLabels(&up);
1545    RECERR(upp, opres,"Warning: trouble nuking (inactive?) Boot!=Root label.");
1546
1547    // unlink booters
1548    if (up.caches->ofbooter.rpath[0]) {
1549        pathcpy(path, up.curMount);
1550        pathcat(path, up.caches->ofbooter.rpath);
1551        opres = sunlink(up.curbootfd, path);
1552        RECERR(upp, opres, "couldn't unlink OF booter" /* remove w/9217695 */);
1553    }
1554    if (up.caches->efibooter.rpath[0]) {
1555        pathcpy(path, up.curMount);
1556        pathcat(path, up.caches->efibooter.rpath);
1557        opres = sunlink(up.curbootfd, path);
1558        RECERR(upp, opres, "couldn't unlink EFI booter" /* NULL w/9217695 */);
1559    }
1560
1561    // find & nuke all RPS directories
1562    opres = FindRPSDir(&up, prevRPS, up.dstdir, nextRPS);
1563    if (opres == 0) {
1564        opres = eraseRPS(&up, prevRPS);
1565        RECERR(upp, opres, "Warning: trouble erasing R.");
1566        opres = eraseRPS(&up, up.dstdir);
1567        RECERR(upp, opres, "Warning: trouble erasing P.");
1568        opres = eraseRPS(&up, nextRPS);
1569        RECERR(upp, opres, "Warning: trouble erasing S.");
1570    } else {
1571        RECERR(upp, opres, "Warning: couldn't find RPS directories.");
1572    }
1573
1574    for (i=0; i < up.caches->nmisc; i++) {
1575        char *rpath = up.caches->miscpaths[i].rpath;
1576
1577        if (strlcpy(path, up.curMount, PATH_MAX) > PATH_MAX)   continue;
1578        if (strlcat(path, rpath, PATH_MAX) > PATH_MAX)         continue;
1579        opres = sdeepunlink(up.curbootfd, path);
1580        RECERR(upp, opres, "error unlinking miscpath" /* NULL w/9217695 */);
1581    }
1582
1583    // clean up com.apple.boot.once if it exists
1584    pathcpy(path, up.curMount);
1585    pathcat(path, kBRBootOnceDir);
1586    if (0 == stat(path, &sb)) {
1587        opres = sdeepunlink(up.curbootfd, path);
1588        RECERR(upp, opres, "error unlinking" kBRBootOnceDir);
1589    }
1590
1591    // no errors above, so firstErr == 0 -> success
1592    if (firstErr == -1) {
1593        firstErr = firstErrno;      // recorded by RECERR
1594    }
1595    result = firstErr;
1596
1597finish:
1598    unmountBoot(&up);
1599    releaseContext(&up, result);
1600
1601    return result;
1602}
1603
1604
1605/******************************************************************************
1606* revertState() rolls back incomplete changes
1607******************************************************************************/
1608static int
1609revertState(struct updatingVol *up)
1610{
1611    int rval = 0;       // optimism to accumulate errors with |=
1612    char path[PATH_MAX], oldpath[PATH_MAX];
1613    struct bootCaches *caches = up->caches;
1614    Boolean doMisc;
1615    struct stat sb;
1616
1617    OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
1618              "Rolling back any incomplete updates.");
1619
1620    switch (up->changestate) {
1621        // inactive booters are still good
1622        case activatedBooters:
1623            // we've blessed the new booters; so let's bless the old ones
1624            pathcat(up->ofdst, OLDEXT);
1625            pathcat(up->efidst, OLDEXT);
1626            // reactivates the old *if* present
1627            rval |= activateBooters(up);
1628        case activatingEFIBooter:
1629    case activatingOFBooter:        // unneeded since 'bless' is one op
1630        case copiedBooters:
1631    case copyingEFIBooter:
1632        if (caches->efibooter.rpath[0]) {
1633            makebootpath(path, caches->efibooter.rpath);
1634            pathcpy(oldpath, path);         // old ones are blessed; rename
1635            pathcat(oldpath, OLDEXT);
1636            // only unlink current booter if old one present
1637            if (stat(oldpath, &sb) == 0) {
1638                (void)sunlink(up->curbootfd, path);
1639                rval |= srename(up->curbootfd, oldpath, path);
1640            }
1641        }
1642
1643    case copyingOFBooter:
1644        if (caches->ofbooter.rpath[0]) {
1645            makebootpath(path, caches->ofbooter.rpath);
1646            pathcpy(oldpath, path);
1647            pathcat(oldpath, OLDEXT);
1648            // only unlink current booter if old one present
1649            if (stat(oldpath, &sb) == 0) {
1650                (void)sunlink(up->curbootfd, path);
1651                rval |= srename(up->curbootfd, oldpath, path);
1652            }
1653        }
1654
1655    // XX
1656    // case copyingMisc:
1657    // would clean up the .new turds
1658
1659        case noLabel:
1660            // XX hacky (c.f. nukeFallbacks which nukes .disabled label)
1661            doMisc = up->doMisc;
1662            up->doMisc = false;
1663            rval |= activateMisc(up);  // writes new label if !doMisc
1664            up->doMisc = doMisc;
1665
1666        case nothingSerious:
1667            // everything is good
1668            break;
1669    }
1670
1671finish:
1672    if (rval) {
1673        OSKextLog(NULL, kOSKextLogErrorLevel,
1674                  "error rolling back incomplete updates.");
1675    }
1676
1677    return rval;
1678};
1679
1680/******************************************************************************
1681* mountBoot digs in for the root, and mounts up the Apple_Boots
1682* mountpoint -> up->curMount
1683******************************************************************************/
1684static int
1685_mountBootDA(struct updatingVol *up)
1686{
1687    int rval = ELAST + 1;
1688    CFStringRef mountargs[] = { CFSTR("perm"), CFSTR("nobrowse"), NULL };
1689    DADissenterRef dis = (void*)kCFNull;
1690    CFDictionaryRef ddesc = NULL;
1691    CFURLRef volURL;
1692
1693    if (!(up->curBoot=DADiskCreateFromBSDName(nil,up->dasession,up->bsdname))){
1694        goto finish;
1695    }
1696
1697    OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
1698              "Mounting %s...", up->bsdname);
1699
1700    // DADiskMountWithArgument might call _daDone before it returns (e.g. if it
1701    // knows your request is impossible ...)
1702    // _daDone updates our 'dis[senter]'
1703    DADiskMountWithArguments(up->curBoot, NULL/*mnt*/,kDADiskMountOptionDefault,
1704                             _daDone, &dis, mountargs);
1705
1706    // ... so we use kCFNull and check the value before CFRunLoopRun()
1707    if (dis == (void*)kCFNull) {
1708        CFRunLoopRun();         // stopped by _daDone (which updates 'dis')
1709    }
1710    if (dis) {
1711        rval = DADissenterGetStatus(dis);
1712        // only an error if it's not already mounted
1713        if (rval != kDAReturnBusy) {
1714            goto finish;
1715        }
1716    }
1717
1718    // get and stash the mountpoint of the boot partition
1719    if (!(ddesc = DADiskCopyDescription(up->curBoot)))  goto finish;
1720    volURL = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumePathKey);
1721    if (!volURL || CFGetTypeID(volURL) != CFURLGetTypeID())  goto finish;
1722    if (!CFURLGetFileSystemRepresentation(volURL, true /*resolve base*/,
1723            (UInt8*)up->curMount, PATH_MAX))        goto finish;
1724
1725    // success
1726    rval = 0;
1727
1728finish:
1729    if (rval) {
1730        if (rval != ELAST + 1) {
1731            if (rval == -1)     rval = errno;
1732            OSKextLog(NULL, up->errLogSpec,
1733                "Failed to mount helper (%d/%#x): %s", rval,
1734                rval & ~(err_local|err_local_diskarbitration), strerror(rval));
1735        } else {
1736            OSKextLog(NULL, up->errLogSpec,"Failed to mount helper partition.");
1737        }
1738    }
1739
1740    if (ddesc)      CFRelease(ddesc);
1741    if (dis && dis != (void*)kCFNull) { // for spurious CFRunLoopRun() return
1742        CFRelease(dis);
1743    }
1744
1745    return rval;
1746}
1747
1748/* _mountBootBuiltIn() will mount with mount(2) in /var/run.  Use
1749 * _findMountedhelper() first to see if it's already mounted. */
1750// Creating BRMNT_PARENT instead of using _PATH_VARRUN because the latter
1751// contains a trailing '/' and lacks the /private that mount(2) expects.
1752#define BRMNT_PARENT "/private/var/run"
1753#define BRMNT BRMNT_PARENT "/brmnt"
1754static int
1755_mountBootBuiltIn(struct updatingVol *up)
1756{
1757    int bsderr, rval = ELAST + 1;       // all paths should set rval
1758    int vrfd = -1;
1759    int fd = -1;
1760    struct stat sb;
1761    char devpath[DEVMAXPATHSIZE];
1762    struct hfs_mount_args hfsargs;
1763
1764    // establish parent fd (assume /var/run safe)
1765    if (((vrfd = open(_PATH_VARRUN, O_RDONLY))) == -1) {
1766        rval = vrfd; LOGERRxlate(up, _PATH_VARRUN, NULL, rval); goto finish;
1767    }
1768
1769    // examine any existing filesystem object at intended mount point
1770    // [Can't use sopen() since BRMNT already hosts a mount.]
1771    fd = open(BRMNT, O_RDONLY);
1772
1773    // if it exists but isn't a directory, nuke it
1774    if (fd != -1 && fstat(fd, &sb)==0 && S_ISDIR(sb.st_mode)==false) {
1775        if ((bsderr = sunlink(vrfd, BRMNT))) {
1776            rval = bsderr; LOGERRxlate(up, BRMNT, NULL, rval); goto finish;
1777        }
1778        // it should be gone now: reset fd, etc
1779        close(fd);
1780        fd = open(BRMNT, O_RDONLY);
1781        if (fd != -1) {
1782            rval = EEXIST; LOGERRxlate(up, BRMNT, NULL, rval); goto finish;
1783        }
1784    }
1785
1786    // If BRMNT exists, it is a directory; if not, create it.
1787    if (fd == -1 && errno == ENOENT) {
1788        if ((bsderr = smkdir(vrfd, BRMNT, kCacheDirMode))) {
1789            rval = bsderr; LOGERRxlate(up, "mkdir", BRMNT, rval); goto finish;
1790        }
1791    }
1792
1793    // set up args & mount
1794    bzero(&hfsargs, sizeof(hfsargs));
1795    // _PATH_DEV contains a trailing '/'
1796    (void)snprintf(devpath, sizeof(devpath), _PATH_DEV "%s", up->bsdname);
1797    hfsargs.fspec = devpath;
1798    if ((bsderr = mount("hfs", BRMNT, MNT_DONTBROWSE, &hfsargs))) {
1799        rval = bsderr; LOGERRxlate(up, "mount", BRMNT, rval); goto finish;
1800    }
1801
1802    // record result in context
1803    if (strlcpy(up->curMount, BRMNT, MNAMELEN) >= MNAMELEN) {
1804        rval = EOVERFLOW; LOGERRxlate(up,up->curMount,NULL,rval); goto finish;
1805    }
1806
1807    // success
1808    rval = 0;
1809
1810finish:
1811    if (fd != -1)       close(fd);
1812    if (vrfd != -1)     close(vrfd);
1813
1814    return rval;
1815}
1816
1817// loop copied from kextd_watchvol.c:reconsiderVolumes()
1818static int
1819_findMountedHelper(struct updatingVol *up)
1820{
1821    int rval = ELAST + 1;
1822    int nfsys, i;
1823    int bufsz;
1824    struct statfs *mounts = NULL;
1825
1826    // get mount list
1827    if (-1 == (nfsys = getfsstat(NULL, 0, MNT_NOWAIT))) {
1828        rval = errno; goto finish;
1829    }
1830    bufsz = nfsys * sizeof(struct statfs);
1831    if (!(mounts = malloc(bufsz))) {
1832        rval = errno; goto finish;
1833    }
1834    if (-1 == getfsstat(mounts, bufsz, MNT_NOWAIT)) {
1835        rval = errno; goto finish;
1836    }
1837
1838    // see whether the filesystem is already mounted somewhere
1839    for (i = 0; i < nfsys; i++) {
1840        struct statfs *sfs = &mounts[i];
1841        if (strlen(sfs->f_mntfromname) < sizeof(_PATH_DEV) ||
1842                0 != strcmp(sfs->f_fstypename, "hfs")) {
1843            continue;
1844        }
1845        if (0 == strcmp(sfs->f_mntfromname+strlen(_PATH_DEV), up->bsdname)){
1846            if (strlcpy(up->curMount, sfs->f_mntonname, MNAMELEN)>=MNAMELEN) {
1847                rval = EOVERFLOW; goto finish;
1848            }
1849            // we found it!
1850            rval = 0;
1851            goto finish;
1852        }
1853    }
1854
1855    // default = not found (success in loop)
1856    rval = ENOENT;
1857
1858finish:
1859    if (mounts)     free(mounts);
1860
1861    return rval;
1862}
1863
1864static int
1865mountBoot(struct updatingVol *up)
1866{
1867    int errnum, rval = ELAST + 1;
1868    CFStringRef str;
1869    struct statfs bsfs;
1870    uint32_t mntgoal;
1871    struct stat sb;
1872    Boolean pureBootOnce = ((up->blessSpec & kBRBlessOnce) &&
1873                            (up->blessSpec & kBRBlessFSDefault) == 0);
1874
1875    OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
1876              "Mounting helper partition...");
1877
1878    // request the Apple_Boot mount
1879    str = (CFStringRef)CFArrayGetValueAtIndex(up->boots, up->bootIdx);
1880    if (!str || CFGetTypeID(str) != CFStringGetTypeID()) {
1881        goto finish;
1882    }
1883    if (!CFStringGetFileSystemRepresentation(str,up->bsdname,DEVMAXPATHSIZE)){
1884        goto finish;
1885    }
1886    if (up->dasession) {
1887        if ((errnum = _mountBootDA(up))) {
1888            rval = errnum; goto finish;     // error logged by function
1889        }
1890    } else {
1891        if (_findMountedHelper(up) == ENOENT &&
1892                (errnum = _mountBootBuiltIn(up))) {
1893            rval = errnum; goto finish;     // error logged by function
1894        }
1895    }
1896
1897    // Sec: get a non-spoofable handle to the current helper (extend trust)
1898    if (-1 == (up->curbootfd = open(up->curMount, O_RDONLY, 0))) {
1899        rval = errno; LOGERRxlate(up, up->curMount, NULL, rval); goto finish;
1900    }
1901    // if the source volume still exists, we now have fd's for source & dest
1902    if (fstat(up->caches->cachefd, &sb)) {
1903        rval = errno; LOGERRxlate(up, "cachefd MIA?", NULL, rval); goto finish;
1904    }
1905
1906    // Make sure the mount is read/write and has owners enabled.
1907    // Because helper partitions should always have owners enabled
1908    // and because we soft-unmount afterwards, we don't attempt to
1909    // restore this state.
1910    if (fstatfs(up->curbootfd, &bsfs)) {
1911        rval = errno; LOGERRxlate(up, "curboot MIA?", NULL, rval); goto finish;
1912    }
1913    mntgoal = bsfs.f_flags;
1914    mntgoal &= ~(MNT_RDONLY|MNT_IGNORE_OWNERSHIP);
1915    if ((bsfs.f_flags != mntgoal) && updateMount(up->curMount, mntgoal)) {
1916        OSKextLog(NULL, up->warnLogSpec,
1917                  "Warning: couldn't update mount to read/write + owners");
1918    }
1919
1920    // we only support 128+ MB Apple_Boot partitions
1921    if (bsfs.f_blocks * bsfs.f_bsize < (128 * 1<<20)) {
1922        rval = EFTYPE;
1923        OSKextLog(NULL, up->errLogSpec, "skipping Apple_Boot helper < 128 MB.");
1924        goto finish;
1925    }
1926
1927    // check targetDir, blessSpec for errors, use of c.a.boot.once
1928    if (up->flatTarget[0]) {
1929        char path[PATH_MAX];
1930        pathcpy(path, up->curMount);
1931        pathcat(path, up->flatTarget);
1932        if (stat(path, &sb) != 0) {
1933            if (errno == ENOENT) {
1934                // B!=R only creates non-standard dir in c.a.boot.once
1935                if (pureBootOnce) {
1936                    up->useOnceDir = true;
1937                } else {
1938                    rval = ENOENT;
1939                    LOGERRxlate(up, "target directory must exist", path, rval);
1940                    goto finish;
1941                }
1942            }
1943        } else if (!S_ISDIR(sb.st_mode)) {
1944            rval = ENOTDIR; LOGERRxlate(up, path, NULL, rval); goto finish;
1945        }
1946        // directory exists, B!=R is happy to do whatever to it
1947    } else if (pureBootOnce) {
1948        // no need to cruft up the standard locations if it's just once
1949        up->useOnceDir = true;
1950    }
1951    // the above should ensure that we won't fs-bless c.a.boot.once
1952
1953    rval = 0;
1954
1955finish:
1956    if (rval != 0 && (up->curBoot || up->curMount[0])) {
1957        (void)unmountBoot(up);      // undo anything significant
1958    }
1959
1960    return rval;
1961}
1962
1963/******************************************************************************
1964* unmountBoot
1965* attempt to unmount; no worries on failure
1966******************************************************************************/
1967static void
1968unmountBoot(struct updatingVol *up)
1969{
1970    int errnum = 0;
1971    DADissenterRef dis = (void*)kCFNull;
1972
1973    // clean up curbootfd
1974    if (up->curbootfd != -1) {
1975        close(up->curbootfd);
1976        up->curbootfd = -1;
1977    }
1978
1979    // specifying a target directory => might not be a helper volume!
1980    if (up->flatTarget[0])      return;
1981
1982    if (up->curMount[0]) {
1983        OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
1984                  "Unmounting helper partition %s.", up->bsdname);
1985    }
1986
1987    // clean up any DiskArb-mounted filesystem
1988    if (up->curBoot) {
1989        // _daDone populates 'dis'[senter]
1990        DADiskUnmount(up->curBoot,kDADiskMountOptionDefault,_daDone,&dis);
1991        if (dis == (void*)kCFNull) {    // DA.Unmount can call _daDone
1992            CFRunLoopRun();
1993        }
1994
1995        // if that didn't work, just log
1996        if (dis) {
1997            OSKextLog(NULL, up->warnLogSpec,
1998                      "%s didn't unmount, leaving mounted", up->bsdname);
1999            if (dis != (void*)kCFNull) {
2000                CFRelease(dis);
2001            }
2002        }
2003        up->curMount[0] = '\0';     // only try to unmount once
2004        CFRelease(up->curBoot);
2005        up->curBoot = NULL;
2006    }
2007
2008    // unmount anything mounted by _mountBuiltIn()
2009    if (up->dasession == NULL && up->curMount[0] != '\0') {
2010        if (unmount(up->curMount, 0)) {
2011            errnum = errno;
2012        }
2013        up->curMount[0] = '\0';     // only try to unmount once
2014    }
2015
2016    if (errnum) {
2017        OSKextLog(NULL, up->errLogSpec,
2018            "Failed to unmount helper (%d/%#x): %s", errnum,
2019            errnum & ~(err_local|err_local_diskarbitration), strerror(errnum));
2020    }
2021}
2022
2023
2024/******************************************************************************
2025* ucopyRPS unlinks old/copies new RPS content w/o activating
2026* RPS files are considered important -- non-zero file sizes only!
2027* XX could validate the kernel with Mach-o header
2028* several intervening helpers including eraseRPS()
2029******************************************************************************/
2030static int
2031writeBootPrefs(struct updatingVol *up, char *dstpath)
2032{
2033    int         opres, rval = ELAST + 1;
2034    CFDataRef   bpdata = NULL;
2035    char        dstparent[PATH_MAX];
2036    ssize_t     len;
2037    int         fd = -1;
2038
2039    // create data to be written (uses up->useOnceDir from mountBoot)
2040    bpdata = createBootPrefData(up, up->host_uuid, up->bpoverrides);
2041    if (!bpdata)    { rval = ENOMEM; goto finish; }
2042
2043    // recursively create the parent directory
2044    if (strlcpy(dstparent,dirname(dstpath),PATH_MAX) >= PATH_MAX) {
2045        rval = EOVERFLOW; goto finish;
2046    }
2047    opres = sdeepmkdir(up->curbootfd, dstparent, kCacheDirMode);
2048    if (opres) {
2049        rval = opres; goto finish;
2050    }
2051
2052    // sopen adds O_EXCL to O_CREAT
2053    (void)sunlink(up->curbootfd, dstpath);
2054    fd = sopen(up->curbootfd, dstpath, O_WRONLY|O_CREAT, kCacheFileMode);
2055    if (fd == -1) {
2056        rval = errno; goto finish;
2057    }
2058
2059    len = CFDataGetLength(bpdata);
2060    if (write(fd,CFDataGetBytePtr(bpdata),len) != len) {
2061        rval = errno; goto finish;
2062    }
2063
2064    rval = 0;
2065
2066finish:
2067    if (rval) {
2068        LOGERRxlate(up, dstpath, NULL, rval);
2069    }
2070
2071    if (fd != -1)   close(fd);
2072    if (bpdata)     CFRelease(bpdata);
2073
2074    return rval;
2075}
2076
2077// correctly erase (hopefully old :) items in the Apple_Boot
2078static int
2079eraseRPS(struct updatingVol *up, char *toErase)
2080{
2081    int rval = ELAST+1;
2082    char path[PATH_MAX];
2083    struct stat sb;
2084
2085    // if nothing to erase, return cleanly
2086    if (stat(toErase, &sb) == -1 && errno == ENOENT) {
2087        rval = 0;
2088        goto finish;
2089    }
2090
2091    if (up->caches->erpropcache->rpath) {
2092        // pathc*() seed errno
2093        pathcpy(path, toErase);
2094        pathcat(path, up->caches->erpropcache->rpath);
2095        // szerofile() won't complain if it is missing
2096        if (szerofile(up->curbootfd, path))
2097            goto finish;
2098    }
2099
2100    rval = sdeepunlink(up->curbootfd, toErase);
2101
2102finish:
2103    if (rval) {
2104        OSKextLog(NULL, up->errLogSpec | kOSKextLogFileAccessFlag,
2105                  "%s - %s. errno %d %s",
2106                  __FUNCTION__, toErase, errno, strerror(errno));
2107    }
2108
2109    return rval;
2110}
2111
2112static int
2113_writeFDEPropsToHelper(struct updatingVol *up, char *dstpath)
2114{
2115    int errnum, rval = ELAST + 1;   // everyone sets it?
2116    char *stage;
2117    CFDictionaryRef matching;       // IOServiceGetMatchingServices() releases
2118    io_service_t helper = IO_OBJECT_NULL;
2119    CFNumberRef unitNum = NULL;
2120    CFNumberRef partNum = NULL;
2121    int partnum;
2122    const void *keys[2], *vals[2];
2123    CFDictionaryRef props = NULL;
2124    io_service_t bearer = IO_OBJECT_NULL;
2125    CFStringRef partType = NULL;
2126    CFStringRef partBSD = NULL;
2127    char csbsd[DEVMAXPATHSIZE];
2128
2129    stage = "check argument";
2130    if (up->onAPM) {
2131        rval = EINVAL; goto finish;
2132    }
2133
2134    stage = "find current helper partition";
2135    if (!(matching = IOBSDNameMatching(kIOMasterPortDefault, 0, up->bsdname))){
2136        rval = ENOMEM; goto finish;
2137    }
2138    helper = IOServiceGetMatchingService(kIOMasterPortDefault, matching);
2139    matching = NULL;        // IOServiceGetMatchingService() released
2140    if (!helper) {
2141        rval = ENOENT; goto finish;
2142    }
2143    unitNum = (CFNumberRef)IORegistryEntryCreateCFProperty(helper,
2144                           CFSTR(kIOBSDUnitKey), nil, 0);
2145    if (!unitNum || CFGetTypeID(unitNum) != CFNumberGetTypeID()) {
2146        rval = ENODEV; goto finish;
2147    }
2148    partNum = (CFNumberRef)IORegistryEntryCreateCFProperty(helper,
2149                           CFSTR(kIOMediaPartitionIDKey), nil, 0);
2150    if (!partNum || CFGetTypeID(partNum) != CFNumberGetTypeID()) {
2151        rval = ENODEV; goto finish;
2152    }
2153
2154    stage = "create description of corresponding data partition";
2155    CFNumberGetValue(partNum, kCFNumberIntType, &partnum);
2156    CFRelease(partNum);
2157    partNum = NULL;
2158    // in GPT, data the partition comes before the Apple_Boot
2159    if (--partnum <= 0) {
2160        rval = ENODEV; goto finish;
2161    }
2162    partNum = CFNumberCreate(nil, kCFNumberIntType, &partnum);
2163    if (!partNum) {
2164        rval = ENOMEM; goto finish;
2165    }
2166    // create property and matching dictionaries
2167    keys[0] = CFSTR(kIOMediaPartitionIDKey);
2168    vals[0] = partNum;
2169    keys[1] = CFSTR(kIOBSDUnitKey);
2170    vals[1] = unitNum;
2171    if (!(props = CFDictionaryCreate(nil, keys, vals, 2,
2172                                  &kCFTypeDictionaryKeyCallBacks,
2173                                  &kCFTypeDictionaryValueCallBacks))) {
2174        rval = ENOMEM; goto finish;
2175    }
2176    keys[0] = CFSTR(kIOProviderClassKey);
2177    vals[0] = CFSTR(kIOMediaClass);
2178    keys[1] = CFSTR(kIOPropertyMatchKey);
2179    vals[1] = props;
2180    if (!(matching = CFDictionaryCreate(nil, keys, vals, 2,
2181                                  &kCFTypeDictionaryKeyCallBacks,
2182                                  &kCFTypeDictionaryValueCallBacks))) {
2183        rval = ENOMEM; goto finish;
2184    }
2185
2186    stage = "find & validate data partition";
2187    bearer = IOServiceGetMatchingService(kIOMasterPortDefault, matching);
2188    matching = NULL;        // IOServiceGetMatchingService() released
2189    if (!bearer) {
2190        rval = ENOENT; goto finish;
2191    }
2192    // extract BSD Name
2193    partBSD = (CFStringRef)IORegistryEntryCreateCFProperty(bearer,
2194                           CFSTR(kIOBSDNameKey), nil, 0);
2195    if (!partBSD || CFGetTypeID(partBSD) != CFStringGetTypeID()) {
2196        rval = ENODEV; goto finish;
2197    }
2198    if (!CFStringGetFileSystemRepresentation(partBSD, csbsd, sizeof(csbsd))){
2199        rval = EOVERFLOW; goto finish;
2200    }
2201    // the data partition's type must be Apple_CoreStorage
2202    partType = (CFStringRef)IORegistryEntryCreateCFProperty(bearer,
2203                            CFSTR(kIOMediaContentKey), nil, 0);
2204    if (!partType || CFGetTypeID(partType) != CFStringGetTypeID()) {
2205        rval = ENODEV; goto finish;
2206    }
2207    if (!CFEqual(partType, CFSTR(APPLE_CORESTORAGE_UUID))) {
2208        rval = ENODEV;
2209        LOGERRxlate(up, csbsd, "must be of type Apple_CoreStorage", rval);
2210        stage = NULL;   // logged our own error
2211        goto finish;
2212    }
2213
2214    stage = NULL;       // writeCSFDEProps logs its own errors
2215    // writeCSFDEProps() uses csbsd's wipe key to encrypt the context data.
2216    if ((errnum=writeCSFDEProps(up->curbootfd,up->csfdeprops,csbsd,dstpath))){
2217        rval = errnum; goto finish;
2218    }
2219
2220    // success!
2221    rval = 0;
2222
2223finish:
2224    if (rval && stage) {
2225        OSKextLog(NULL, up->errLogSpec | kOSKextLogFileAccessFlag,
2226                  "%s() failed trying to %s", __func__, stage);
2227    }
2228
2229    if (partBSD)                    CFRelease(partBSD);
2230    if (partType)                   CFRelease(partType);
2231    if (bearer != IO_OBJECT_NULL)   IOObjectRelease(bearer);
2232    if (props)                      CFRelease(props);
2233    if (partNum)                    CFRelease(partNum);
2234    if (unitNum)                    CFRelease(unitNum);
2235    if (helper != IO_OBJECT_NULL)   IOObjectRelease(helper);
2236
2237    return rval;
2238}
2239
2240
2241/*
2242 * ucopyRPS - copy new RPS directory to "inactive" location
2243 * bails on any error because only a whole RPS dir makes sense
2244 */
2245static int
2246ucopyRPS(struct updatingVol *up)
2247{
2248    int bsderr, rval = ELAST + 1;   // generic safest
2249    char prevRPS[PATH_MAX], curRPS[PATH_MAX], discard[PATH_MAX];
2250    char *erdir;
2251    unsigned i;
2252    char srcpath[PATH_MAX], dstpath[PATH_MAX];
2253    COMPILE_TIME_ASSERT(sizeof(BOOTPLIST_NAME)==sizeof(BOOTPLIST_APM_NAME));
2254
2255    OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
2256              "Copying files used by the booter.");
2257
2258    if ((bsderr = FindRPSDir(up, prevRPS, curRPS, discard))) {
2259        rval = bsderr; goto finish;     // error logged by function
2260    }
2261
2262    if (up->flatTarget[0] || up->useOnceDir) {
2263        // copy desired target into dstdir
2264        pathcpy(up->dstdir, up->curMount);
2265        if (up->useOnceDir) {
2266            pathcat(up->dstdir, kBRBootOnceDir);
2267        }
2268        pathcat(up->dstdir, up->flatTarget);
2269        erdir = curRPS;
2270    } else {
2271        // we're going to copy into the currently-inactive directory
2272        pathcpy(up->dstdir, prevRPS);
2273        erdir = prevRPS;
2274    }
2275
2276    // we expect to have removed it and eraseRPS() doesn't mind it missing
2277    if ((bsderr = eraseRPS(up, up->dstdir))) {
2278        rval = bsderr; goto finish;     // error logged by function
2279    }
2280
2281    // create the directory (RPS should not exist?)
2282    if ((bsderr = sdeepmkdir(up->curbootfd, up->dstdir, kCacheDirMode))) {
2283        rval = bsderr; LOGERRxlate(up, up->dstdir, NULL, rval); goto finish;
2284    }
2285
2286    // and loop
2287    for (i = 0; i < up->caches->nrps; i++) {
2288        cachedPath *curItem = &up->caches->rpspaths[i];
2289
2290        pathcpy(srcpath, up->caches->root);
2291        pathcat(srcpath, curItem->rpath);
2292        pathcpy(dstpath, up->dstdir);
2293        // EfiLoginUI.a still digs down to its cache dirs
2294        if ((up->flatTarget[0] || up->useOnceDir)
2295                && curItem != up->caches->efidefrsrcs
2296                && curItem != up->caches->efiloccache) {
2297            /* XX 10561671: basename unsafe */
2298            pathcat(dstpath, "/");
2299            pathcat(dstpath, basename(curItem->rpath));
2300        } else {
2301            pathcat(dstpath, curItem->rpath);
2302        }
2303
2304        // check for special files; first Boot.plist
2305        if (curItem == up->caches->bootconfig) {
2306            // PR-5115900 - call it com.apple.boot.plist on APM since Tiger
2307            // (since Tiger bless scribbles on com.apple.Boot.plist)
2308            if (up->onAPM) {
2309                char * plistNamePtr;
2310                // see assert above
2311                plistNamePtr = strstr(dstpath, BOOTPLIST_NAME);
2312                if (plistNamePtr) {
2313                    strncpy(plistNamePtr, BOOTPLIST_APM_NAME, strlen(BOOTPLIST_NAME));
2314                }
2315            }
2316            // write customized com.apple.Boot.plist data
2317            if ((bsderr = writeBootPrefs(up, dstpath))) {
2318                rval = bsderr; goto finish;     // error logged by function
2319            }
2320        } else {
2321            // could deny zero-size cookies, busted Mach-O, etc here
2322            // scopyitem creates any intermediate directories
2323            OSKextLog(NULL, kOSKextLogGeneralFlag|kOSKextLogDetailLevel,
2324                      "copying %s to %s", srcpath, up->dstdir);
2325            bsderr=scopyitem(up->caches->cachefd,srcpath,up->curbootfd,dstpath);
2326            if (bsderr) {
2327                // erpropcache, efiloccache are optional
2328                if ((curItem == up->caches->erpropcache ||
2329                            curItem == up->caches->efiloccache)
2330                        && bsderr == -1 && errno == ENOENT) {
2331                    ; // no-op to allow real CSFDE data to be written
2332                } else {
2333                    rval = bsderr == -1 ? errno : bsderr;
2334                    OSKextLog(0,up->errLogSpec,"Error %d copying %s to %s: %s",
2335                              rval, srcpath, dstpath, strerror(rval));
2336                    goto finish;
2337                }
2338            }
2339
2340            // having copied any existing file (for HFS conversions),
2341            // we now prefer the real data
2342            if (up->csfdeprops && curItem == up->caches->erpropcache &&
2343                    up->onAPM == false) {
2344                if ((bsderr = _writeFDEPropsToHelper(up, dstpath))) {
2345                    rval = bsderr; goto finish;     // error logged by function
2346                }
2347            }
2348        }
2349    }
2350
2351    // XX EFI is happier if there is a SystemVersion.plist it can find
2352
2353    // 10561691 wasn't fixed until 10.8 so we implement "mostly flat"
2354    // for 10.7-era systems when flatTarget is set.
2355    // re-write correctly-encrypted context to secondary location
2356    if ((up->flatTarget[0] || up->useOnceDir)
2357            && up->caches->erpropTSOnly == false && up->onAPM == false
2358            && up->caches->erpropcache && up->csfdeprops) {
2359        pathcpy(dstpath, erdir);
2360        pathcat(dstpath, up->caches->erpropcache->rpath);
2361        if ((bsderr = _writeFDEPropsToHelper(up, dstpath))) {
2362            rval = bsderr; goto finish;     // error logged by function
2363        }
2364
2365        if (up->caches->efidefrsrcs) {
2366            pathcpy(srcpath, up->caches->root);
2367            pathcat(srcpath, up->caches->efidefrsrcs->rpath);
2368            pathcpy(dstpath, erdir);
2369            pathcat(dstpath, up->caches->efidefrsrcs->rpath);
2370            bsderr=scopyitem(up->caches->cachefd,srcpath,up->curbootfd,dstpath);
2371            if (bsderr) {
2372                rval = bsderr == -1 ? errno : bsderr;
2373                OSKextLog(NULL,up->errLogSpec,"Error %d copying %s to %s: %s",
2374                          rval, srcpath, dstpath, strerror(rval));
2375                goto finish;
2376            }
2377        }
2378    }
2379
2380    // success
2381    rval = 0;
2382
2383finish:
2384    return rval;
2385}
2386
2387/******************************************************************************
2388* ucopyMisc writes misc files to .new (inactive) name
2389******************************************************************************/
2390static int
2391ucopyMisc(struct updatingVol *up)
2392{
2393    int bsderr, rval = -1;
2394    unsigned i, nprocessed = 0;
2395    char srcpath[PATH_MAX], dstpath[PATH_MAX];
2396    struct stat sb;
2397
2398    OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
2399              "Copying files read before the booter runs.");
2400
2401    for (i = 0; i < up->caches->nmisc; i++) {
2402        pathcpy(srcpath, up->caches->root);
2403        pathcat(srcpath, up->caches->miscpaths[i].rpath);
2404        makebootpath(dstpath, up->caches->miscpaths[i].rpath);
2405        pathcat(dstpath, ".new");
2406
2407        if (stat(srcpath, &sb) == 0) {
2408            // file exists and is accessible
2409            if ((bsderr = scopyitem(up->caches->cachefd, srcpath,
2410                                    up->curbootfd, dstpath))) {
2411                if (bsderr == -1)  bsderr = errno;
2412                OSKextLog(NULL, up->errLogSpec, "Error %d copying %s to %s: %s",
2413                          bsderr, srcpath, dstpath, strerror(bsderr));
2414                continue;
2415            }
2416        } else if (errno != ENOENT) {
2417            continue;
2418        }
2419
2420        nprocessed++;
2421    }
2422
2423    if (nprocessed == i) {
2424        rval = 0;
2425    } else {
2426        rval = errno;
2427    }
2428
2429finish:
2430    if (rval) {
2431        LOGERRxlate(up, __func__, "failure copying pre-booter files", rval);
2432    }
2433
2434    return rval;
2435}
2436
2437/******************************************************************************
2438* moveLabels() deactivates the but preserves it for later
2439* activateMisc() will move these back if needed
2440* no label -> hint of indeterminate state (label key in plist/other file??)
2441* XX put/switch in some sort of "(updating!)" label (see BL[ess] routines)
2442******************************************************************************/
2443static int
2444moveLabels(struct updatingVol *up)
2445{
2446    int rval = -1;
2447    char path[PATH_MAX];
2448    struct stat sb;
2449    int fd = -1;
2450
2451    OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
2452              "Moving aside old label.");
2453
2454    // pathc*() seed errno
2455    makebootpath(path, up->caches->label->rpath);
2456    if (0 == (stat(path, &sb))) {
2457        char newpath[PATH_MAX];
2458        unsigned char nulltype[32] = {'\0', };
2459
2460        // rename
2461        pathcpy(newpath, path);
2462        pathcat(newpath, NEWEXT);
2463        rval = srename(up->curbootfd, path, newpath);
2464        if (rval)       goto finish;
2465
2466        // remove magic type/creator
2467        if (-1 == (fd=sopen(up->curbootfd, newpath, O_RDWR, 0)))  goto finish;
2468        if(fsetxattr(fd,XATTR_FINDERINFO_NAME,&nulltype,sizeof(nulltype),0,0)) {
2469            goto finish;
2470        }
2471    }
2472
2473    up->changestate = noLabel;
2474    rval = 0;
2475
2476finish:
2477    if (fd != -1)   close(fd);
2478
2479    if (rval) {
2480        OSKextLog(NULL, up->errLogSpec,
2481                  "%s - Error moving aside old label. errno %d %s.",
2482                  __FUNCTION__, errno, strerror(errno));
2483   }
2484
2485    return rval;
2486}
2487
2488/******************************************************************************
2489* nukeBRLabels gets rid of the label and .contentDetails files
2490* no label -> hint of indeterminate state (label key in plist/other file?)
2491* someday: some sort of "(updating!)" label?
2492******************************************************************************/
2493static int
2494nukeBRLabels(struct updatingVol *up)
2495{
2496    int rval = EOVERFLOW;       // path*()
2497    int opres, firstErrno, firstErr = 0;
2498    char labelp[PATH_MAX], dstparent[PATH_MAX];
2499    struct stat sb;
2500
2501    OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
2502              "Removing current disk label.");
2503
2504    // .disk_label
2505    makebootpath(labelp, up->caches->label->rpath);
2506    if (0 == (stat(labelp, &sb))) {
2507        opres = sunlink(up->curbootfd, labelp);
2508        RECERR(up, opres, "error removing label" /*NULL w/9217695*/);
2509    } else {
2510        errno = 0;
2511    }
2512
2513    // .disk_label_2x
2514    pathcpy(labelp, up->curMount);
2515    if (up->useOnceDir) {
2516        pathcat(labelp, kBRBootOnceDir);
2517    }
2518    pathcat(labelp, up->caches->label->rpath);
2519    pathcat(labelp, SCALE_2xEXT);       // append extension
2520    if (0 == (stat(labelp, &sb))) {
2521        opres = sunlink(up->curbootfd, labelp);
2522        RECERR(up, opres, "error removing .contentDetails" /*NULL w/9217695*/);
2523    } else {
2524        errno = 0;
2525    }
2526
2527    // .disk_label.contentsDetail
2528    pathcpy(labelp, up->curMount);
2529    if (up->useOnceDir) {
2530        pathcat(labelp, kBRBootOnceDir);
2531    }
2532    pathcat(labelp, up->caches->label->rpath);
2533    pathcat(labelp, CONTENTEXT);        // append extension
2534    if (0 == (stat(labelp, &sb))) {
2535        opres = sunlink(up->curbootfd, labelp);
2536        RECERR(up, opres, "error removing " CONTENTEXT /*NULL w/9217695*/);
2537    } else {
2538        errno = 0;
2539    }
2540
2541    // and possible .root_uuid
2542    makebootpath(labelp, up->caches->label->rpath);
2543    pathcpy(dstparent, dirname(labelp));
2544    pathcpy(labelp, dstparent);
2545    pathcat(labelp, "/" kBRRootUUIDFile);
2546    if (0 == (stat(labelp, &sb))) {
2547        opres = sunlink(up->curbootfd, labelp);
2548        RECERR(up, opres, "error removing " kBRRootUUIDFile /*NULL w/9217695*/);
2549    } else {
2550        errno = 0;
2551    }
2552
2553    up->changestate = noLabel;
2554
2555    if (firstErr == -1)     errno = firstErrno;
2556    rval = firstErr;
2557
2558finish:
2559    if (rval)
2560        OSKextLog(NULL, kOSKextLogErrorLevel, "Error removing disk label.");
2561
2562    return rval;
2563}
2564
2565/******************************************************************************
2566* ucopyBooters unlink/copies down booters but doesn't bless them
2567******************************************************************************/
2568static int
2569ucopyBooters(struct updatingVol *up)
2570{
2571    int rval = ELAST + 1;
2572    int bsderr;
2573    char srcpath[PATH_MAX], oldpath[PATH_MAX];
2574    int nbooters = 0;
2575
2576    if (up->caches->ofbooter.rpath[0])      nbooters++;
2577    if (up->caches->efibooter.rpath[0])     nbooters++;
2578    OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
2579              "Copying new booter%s.", nbooters == 1 ? "" : "s");
2580
2581    // copy BootX, boot.efi
2582    up->changestate = copyingOFBooter;
2583    if (up->caches->ofbooter.rpath[0]) {
2584        // pathc*() seed errno
2585        pathcpy(srcpath, up->caches->root);
2586        pathcat(srcpath, up->caches->ofbooter.rpath);   // <root>/S/L/CS/BootX
2587        makebootpath(up->ofdst, up->caches->ofbooter.rpath); // <boot>/../BootX
2588        pathcpy(oldpath, up->ofdst);
2589        pathcat(oldpath, OLDEXT);                  // <boot>/S/L/CS/BootX.old
2590
2591        (void)sunlink(up->curbootfd, oldpath);
2592        bsderr = srename(up->curbootfd, up->ofdst, oldpath);
2593        if (bsderr && errno !=ENOENT) {
2594            OSKextLog(NULL, up->errLogSpec,
2595                      "%s - Error rename old %s new %s",
2596                      __FUNCTION__, up->ofdst, oldpath);
2597            goto finish;
2598        }
2599        if ((bsderr = scopyitem(up->caches->cachefd, srcpath,
2600                                up->curbootfd, up->ofdst))) {
2601            rval = bsderr == -1 ? errno : bsderr;
2602            if (!(up->opts & kBRUHelpersOptional)) {
2603                OSKextLog(NULL,up->errLogSpec,"Error %d copying %s to %s: %s",
2604                          rval, srcpath, up->ofdst, strerror(rval));
2605            }
2606            goto finish;
2607        }
2608    }
2609
2610    up->changestate = copyingEFIBooter;
2611    if (up->caches->efibooter.rpath[0]) {
2612        // pathc*() seed errno
2613        pathcpy(srcpath, up->caches->root);
2614        pathcat(srcpath, up->caches->efibooter.rpath);   // ... boot.efi
2615        makebootpath(up->efidst, up->caches->efibooter.rpath);
2616        pathcpy(oldpath, up->efidst);
2617        pathcat(oldpath, OLDEXT);
2618
2619        (void)sunlink(up->curbootfd, oldpath);
2620        bsderr = srename(up->curbootfd, up->efidst, oldpath);
2621        if (bsderr && errno != ENOENT) {
2622            OSKextLog(NULL, up->errLogSpec,
2623                      "%s - Error rename old %s new %s",
2624                      __FUNCTION__, up->efidst, oldpath);
2625            goto finish;
2626        }
2627        if ((bsderr = scopyitem(up->caches->cachefd, srcpath,
2628                                up->curbootfd, up->efidst))) {
2629            if (!(up->opts & kBRUHelpersOptional)) {
2630                rval = bsderr == -1 ? errno : bsderr;
2631                OSKextLog(NULL,up->errLogSpec,"Error %d copying %s to %s: %s",
2632                          rval, srcpath, up->efidst, strerror(rval));
2633            }
2634            goto finish;
2635        }
2636    }
2637
2638    up->changestate = copiedBooters;
2639    rval = 0;
2640
2641finish:
2642    // all goto paths log
2643
2644    return rval;
2645}
2646
2647
2648// booters have worst critical:fragile ratio (basically point of no return)
2649/******************************************************************************
2650* bless recently-copied booters
2651* operatens entirely on up->??dst which allows revertState to use it ..?
2652******************************************************************************/
2653#define CLOSE(fd) do { (void)close(fd); fd = -1; } while(0)
2654static int
2655activateBooters(struct updatingVol *up)
2656{
2657    int errnum, rval = ELAST + 1;
2658    int fd = -1;
2659    uint32_t vinfo[8] = { 0, };
2660    struct stat sb;
2661    char parent[PATH_MAX];
2662    int nbooters = 0;
2663    BLContext blctx = { 0, BRBLLogFunc, NULL };
2664
2665    if (up->caches->ofbooter.rpath[0])      nbooters++;
2666    if (up->caches->efibooter.rpath[0])     nbooters++;
2667
2668    OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
2669              "Activating new booter%s.", nbooters == 1 ? "" : "s");
2670
2671    // flush everything in this helper partition to disk
2672    if ((errnum = fcntl(up->curbootfd, F_FULLFSYNC))) {
2673        rval = errnum; goto finish;
2674    }
2675
2676    // activate BootX, boot.efi
2677    up->changestate = activatingOFBooter;
2678    if (up->caches->ofbooter.rpath[0]) {
2679        unsigned char tbxichrp[32] = {'t','b','x','i','c','h','r','p','\0',};
2680
2681        // apply type/creator (assuming same folder as previous, now active)
2682        if (-1==(fd=sopen(up->curbootfd, up->ofdst, O_RDONLY, 0))) {
2683            rval = errno; goto finish;
2684        }
2685        if(fsetxattr(fd,XATTR_FINDERINFO_NAME,&tbxichrp,sizeof(tbxichrp),0,0)){
2686            rval = errno; goto finish;
2687        }
2688        CLOSE(fd);
2689
2690        // get fileID of booter's enclosing folder
2691        pathcpy(parent, dirname(up->ofdst));
2692        if (-1 == (fd=sopen(up->curbootfd, parent, O_RDONLY, 0))
2693                    || fstat(fd, &sb)) {
2694            rval = errno; goto finish;
2695        }
2696        CLOSE(fd);
2697        if (sb.st_ino < (__darwin_ino64_t)2<<31) {
2698            vinfo[kSystemFolderIdx] = (uint32_t)sb.st_ino;
2699        } else {
2700            rval = EOVERFLOW; goto finish;
2701        }
2702    }
2703
2704    up->changestate = activatingEFIBooter;
2705    if (up->caches->efibooter.rpath[0]) {
2706        // get file ID
2707        if (-1==(fd=sopen(up->curbootfd, up->efidst, O_RDONLY, 0))
2708                    || fstat(fd, &sb)) {
2709            rval = errno; goto finish;
2710        }
2711        CLOSE(fd);
2712        if (sb.st_ino < (__darwin_ino64_t)2<<31) {
2713            vinfo[kEFIBooterIdx] = (uint32_t)sb.st_ino;
2714        } else {
2715            rval = EOVERFLOW; goto finish;
2716        }
2717
2718        // get folder ID of enclosing folder if not provided by ofbooter
2719        if (!vinfo[0]) {
2720            pathcpy(parent, dirname(up->efidst));
2721            if (-1 == (fd=sopen(up->curbootfd, parent, O_RDONLY, 0))
2722                    || fstat(fd, &sb)) {
2723                rval = errno; goto finish;
2724            }
2725            CLOSE(fd);
2726            if (sb.st_ino < (__darwin_ino64_t)2<<31) {
2727                vinfo[kSystemFolderIdx] = (uint32_t)sb.st_ino;
2728            } else {
2729                rval = EOVERFLOW; goto finish;
2730            }
2731        }
2732    }
2733
2734    // configure blessing as requested
2735    // FSDefault is a single unique bit.
2736    if (up->blessSpec & kBRBlessFSDefault) {
2737        if ((errnum = sBLSetBootFinderInfo(up, vinfo))) {
2738            rval = errnum; goto finish;
2739        }
2740    }
2741    // BlessFull = (FSDefault | setNVRAM)
2742    if (up->blessSpec == kBRBlessFull) {
2743        if (BLSetEFIBootDevice(&blctx, up->bsdname)) {
2744            rval = ENODEV; goto finish;
2745        }
2746    }
2747    // BlessOnce is a unique bit. Use BLSetEFIBootDeviceOnce() if we
2748    // just made the target the default for the filesystem.
2749    if (up->blessSpec & kBRBlessOnce) {
2750        if (up->blessSpec & kBRBlessFSDefault) {
2751            if (BLSetEFIBootDeviceOnce(&blctx, up->bsdname)) {
2752                rval = ENODEV; goto finish;
2753            }
2754        } else {
2755            if (BLSetEFIBootFileOnce(&blctx, up->efidst)) {
2756                rval = ENODEV; goto finish;
2757            }
2758        }
2759    }
2760
2761    up->changestate = activatedBooters;
2762
2763    // success
2764    rval = 0;
2765
2766finish:
2767    if (fd != -1)   close(fd);
2768
2769    if (rval)
2770        OSKextLog(NULL, kOSKextLogErrorLevel, "Error activating booter.");
2771
2772    return rval;
2773}
2774
2775/******************************************************************************
2776* leap-frog w/rename()
2777******************************************************************************/
2778static int
2779activateRPS(struct updatingVol *up)
2780{
2781    int rval = ELAST + 1;
2782    char prevRPS[PATH_MAX], curRPS[PATH_MAX], nextRPS[PATH_MAX];
2783
2784    OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
2785              "Activating files used by the booter.");
2786
2787    // if using default RPS dirs, make fresh one current
2788    if (up->flatTarget[0] == '\0' && up->useOnceDir == false) {
2789        if (FindRPSDir(up, prevRPS, curRPS, nextRPS))   goto finish;
2790
2791        // if current != the one we just populated
2792        if (strncmp(curRPS, up->dstdir, PATH_MAX) != 0) {
2793            // rename prev -> next ... done!?
2794            if (srename(up->curbootfd, prevRPS, nextRPS))   goto finish;
2795        }
2796    }
2797
2798    // thwunk everything to disk (now that essential boot files are in place)
2799    if (fcntl(up->curbootfd, F_FULLFSYNC))              goto finish;
2800
2801    rval = 0;
2802
2803finish:
2804    if (rval) {
2805        OSKextLog(NULL, kOSKextLogErrorLevel,
2806              "Error activating files used by the booter.");
2807    }
2808
2809    return rval;
2810}
2811
2812
2813/******************************************************************************
2814* activateMisc renames .new files to final names and relabels the volumes
2815* active label indicates an updated helper partition
2816* - construct new label with a trailing number as appropriate
2817* - use BLGenerateLabelData() and overwrite any copied-down label
2818* X need to be consistent throughout regarding missing misc files (esp. label?)
2819******************************************************************************/
2820/*
2821 * writeLabels() writes correctly-formatted label and related files.
2822 * These files should be removed first via nukeLabels().
2823 *
2824 * Since com.apple.recovery.boot is generally only present in CoreStorage
2825 * helpers, the net effect of writeLabel()'s policy of
2826 *     if (up->bootIdx == 0 || up->detectedRecovery) {
2827 * is that CoreStorage will get .root_uuid files (and matching label data)
2828 * in all Apple_Boot helpers while non-CS (AppleRAID, third party) will get
2829 * 'Mac HD', 'Mac HD 2', ... 'Mac HD <n>' in their helpers.  The absence of
2830 * .root_uuid in subsequent helpers should prevent EFI from merging any of
2831 * these non-CS helpers.  See 11129639 and related for more details.
2832 */
2833
2834// see makebootpath() at top of file
2835#define MAKEBOOTPATHcont(path, rpath) do { \
2836                                    PATHCPYcont(path, up->curMount); \
2837                                    if (up->useOnceDir) { \
2838                                        PATHCATcont(path, kBRBootOnceDir); \
2839                                    } \
2840                                    if (up->flatTarget[0] || up->useOnceDir) { \
2841                                        PATHCATcont(path, up->flatTarget); \
2842                                        /* XXX 10561671: basename unsafe */ \
2843                                        PATHCATcont(path, "/"); \
2844                                        PATHCATcont(path, basename(rpath)); \
2845                                    } else { \
2846                                        PATHCATcont(path, rpath); \
2847                                    } \
2848                                } while(0)
2849static int
2850activateMisc(struct updatingVol *up)     // rename the .new
2851{
2852    int rval = ELAST + 1;
2853    char path[PATH_MAX], opath[PATH_MAX];
2854    unsigned i = 0, nprocessed = 0;
2855    int fd = -1;
2856    struct stat sb;
2857    unsigned char tbxjchrp[32] = { 't','b','x','j','c','h','r','p','\0', };
2858
2859    if (up->doMisc) {
2860        OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
2861                  "Activating files used before the booter runs.");
2862
2863        // do them all
2864        for (i = 0; i < up->caches->nmisc; i++) {
2865            MAKEBOOTPATHcont(path, up->caches->miscpaths[i].rpath);
2866            if (strlcpy(opath, path, PATH_MAX) >= PATH_MAX)     continue;
2867            if (strlcat(opath, NEWEXT, PATH_MAX) >= PATH_MAX)   continue;
2868
2869            if (stat(opath, &sb) == 0) {
2870                if (srename(up->curbootfd, opath, path))        continue;
2871            }
2872
2873            nprocessed++;
2874        }
2875
2876    }
2877
2878    makebootpath(path, up->caches->label->rpath);
2879        // move label back
2880        char newpath[PATH_MAX];
2881
2882        pathcpy(newpath, path);     // just rename
2883        pathcat(newpath, NEWEXT);
2884        (void)srename(up->curbootfd, newpath, path);
2885    }
2886
2887    // assign type/creator to the label
2888    if (0 == (stat(path, &sb))) {
2889        if (-1 == (fd = sopen(up->curbootfd, path, O_RDWR, 0)))   goto finish;
2890
2891        if (fsetxattr(fd,XATTR_FINDERINFO_NAME,&tbxjchrp,sizeof(tbxjchrp),0,0))
2892            goto finish;
2893        close(fd); fd = -1;
2894    }
2895
2896    rval = (i != nprocessed);
2897
2898finish:
2899    if (fd != -1)   close(fd);
2900
2901    if (rval) {
2902        OSKextLog(NULL, kOSKextLogErrorLevel,
2903                  "Error activating files used before the booter runs.");
2904    }
2905
2906    return rval;
2907}
2908
2909/******************************************************************************
2910* get rid of everything "extra"
2911******************************************************************************/
2912static int
2913nukeFallbacks(struct updatingVol *up)
2914{
2915    int rval = 0;               // OR-ative return value
2916    int bsderr;
2917    char delpath[PATH_MAX];
2918    struct bootCaches *caches = up->caches;
2919
2920    OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
2921              "Cleaning up fallbacks.");
2922
2923    // using pathcpy b/c if that's failing, it's worth bailing
2924    // XX should probably only try to unlink if present
2925
2926    // maybe mount failed (in which case there aren't any fallbacks)
2927    if (up->curMount[0] == '\0')    goto finish;
2928
2929    // if needed, unlink .old booters
2930    if (up->doBooters) {
2931        if (caches->ofbooter.rpath[0]) {
2932            makebootpath(delpath, caches->ofbooter.rpath);
2933            pathcat(delpath, OLDEXT);
2934            if ((bsderr = sunlink(up->curbootfd, delpath)) && errno != ENOENT) {
2935                rval |= bsderr;
2936            }
2937        }
2938        if (caches->efibooter.rpath[0]) {
2939            makebootpath(delpath, caches->efibooter.rpath);
2940            pathcat(delpath, OLDEXT);
2941            if ((bsderr = sunlink(up->curbootfd, delpath)) && errno != ENOENT) {
2942                rval |= bsderr;
2943            }
2944        }
2945    }
2946
2947    // if needed, erase prevRPS
2948    // which, conveniently, will be right regardless of whether we succeeded
2949    if (up->doRPS) {
2950        char ignore[PATH_MAX];
2951
2952        if (0 == FindRPSDir(up, delpath, ignore, ignore)) {
2953            // eraseRPS ignores if missing (and logs other errors)
2954            rval |= eraseRPS(up, delpath);
2955        }
2956    }
2957
2958finish:
2959    if (rval)
2960        OSKextLog(NULL, kOSKextLogErrorLevel, "Error cleaning up fallbacks.");
2961
2962    return rval;
2963}
2964
2965#if 0
2966/*********************************************************************
2967// XXX not yet used / tested
2968*********************************************************************/
2969static int
2970kill_kextd(void)
2971{
2972    int           result         = -1;
2973    kern_return_t kern_result    = kOSReturnError;
2974    mach_port_t   bootstrap_port = MACH_PORT_NULL;
2975    mach_port_t   kextd_port     = MACH_PORT_NULL;
2976    int           kextd_pid      = -1;
2977
2978    kern_result = task_get_bootstrap_port(mach_task_self(), &bootstrap_port);
2979    if (kern_result != kOSReturnSuccess) {
2980        goto finish;
2981    }
2982
2983    kern_result = bootstrap_look_up(bootstrap_port,
2984        (char *)KEXTD_SERVER_NAME, &kextd_port);
2985    if (kern_result != kOSReturnSuccess) {
2986        goto finish;
2987    }
2988
2989    kern_result = pid_for_task(kextd_port, &kextd_pid);
2990    if (kern_result != kOSReturnSuccess) {
2991        goto finish;
2992    }
2993
2994    result = kill(kextd_pid, SIGKILL);
2995    if (-1 == result) {
2996        OSKextLog(/* kext */ NULL,
2997            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
2998             "kill kextd failed - %s.", strerror(errno));
2999    }
3000
3001finish:
3002    if (kern_result != kOSReturnSuccess) {
3003        OSKextLog(/* kext */ NULL,
3004            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
3005             "kill kextd failed - %s.", safe_mach_error_string(kern_result));
3006    }
3007    return result;
3008}
3009
3010/******************************************************************************
3011// XXX not yet used / tested
3012******************************************************************************/
3013int
3014renameBootcachesPlist(
3015    char * hostVolume,
3016    char * oldPlistPath,
3017    char * newPlistPath)
3018{
3019    int    result            = -1;
3020    int    bootcachesPlistFd = -1;
3021    char * errorMessage      = NULL;
3022    char * errorPath         = NULL;
3023    char   oldname[PATH_MAX];
3024    char   newname[PATH_MAX];
3025    char * kextcacheArgs[] = {
3026        "/usr/sbin/kextcache",
3027        "-f",
3028        "-u",
3029        NULL, // replace with hostVolume
3030        NULL };
3031
3032    errorMessage = "path concatenation error";
3033    errorPath = hostVolume;
3034
3035    if (strlcpy(oldname, hostVolume, PATH_MAX) >= PATH_MAX) {
3036        goto finish;
3037    }
3038    if (strlcpy(newname, hostVolume, PATH_MAX) >= PATH_MAX) {
3039        goto finish;
3040    }
3041
3042    errorPath = oldPlistPath;
3043    if (strlcpy(oldname, oldPlistPath, PATH_MAX) >= PATH_MAX) {
3044        goto finish;
3045    }
3046
3047    errorPath = newPlistPath;
3048    if (strlcpy(newname, newPlistPath, PATH_MAX) >= PATH_MAX) {
3049        goto finish;
3050    }
3051
3052    errorPath = oldname;
3053    bootcachesPlistFd = open(oldname, O_RDONLY);
3054    if (-1 == bootcachesPlistFd) {
3055        errorMessage = strerror(errno);
3056        goto finish;
3057    }
3058
3059    if (-1 == srename(bootcachesPlistFd, oldname, newname)) {
3060        errorMessage = "rename failed.";
3061        goto finish;
3062    }
3063
3064    errorMessage = "couldn't kill kextd";
3065    if (-1 == kill_kextd()) {
3066        goto finish;
3067    }
3068
3069   /* Do we want to check fork_program's return value?
3070    */
3071    kextcacheArgs[3] = hostVolume;
3072    result = fork_program(kextcacheArgs[0], kextcacheArgs, true /* wait */);
3073
3074finish:
3075    if (errorMessage) {
3076        OSKextLog(/* kext */ NULL,
3077            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
3078             "%s - %s.", errorPath, errorMessage);
3079    }
3080    if (bootcachesPlistFd >= 0) {
3081        close(bootcachesPlistFd);
3082    }
3083    return result;
3084}
3085#endif      // UNUSED
3086
3087/******************************************************************************
3088* takeVolumeForPath turns the path into a volume UUID and locks with kextd
3089******************************************************************************/
3090// upstat() stat()s "up" the path if a file doesn't exist
3091static int
3092upstat(const char *path, struct stat *sb, struct statfs *sfs)
3093{
3094    int rval = ELAST+1;
3095    char buf[PATH_MAX], *tpath = buf;
3096    struct stat defaultsb;
3097
3098    if (strlcpy(buf, path, PATH_MAX) > PATH_MAX)        goto finish;
3099
3100    if (!sb)    sb = &defaultsb;
3101    while ((rval = stat(tpath, sb)) == -1 && errno == ENOENT) {
3102        // "." and "/" should always exist, but you never know
3103        if (tpath[0] == '.' && tpath[1] == '\0')  goto finish;
3104        if (tpath[0] == '/' && tpath[1] == '\0')  goto finish;
3105        tpath = dirname(tpath);     // Tiger's dirname() took const char*
3106    }
3107
3108    // call statfs if the caller needed it
3109    if (sfs)
3110        rval = statfs(tpath, sfs);
3111
3112finish:
3113    if (rval) {
3114        OSKextLog(/* kext */ NULL,
3115            kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
3116                "Couldn't find volume for %s.", path);
3117    }
3118
3119    return rval;
3120}
3121
3122
3123/******************************************************************************
3124* theoretically, takeVolumeForPaths() ensured all paths are on the given
3125* volume, then locked
3126******************************************************************************/
3127// int takeVolumeForPaths(char *volPath)
3128
3129/******************************************************************************
3130* takeVolumeForPath() is all we ended up needing ...
3131* can return success if a lock isn't needed
3132* can return failure if sBRUptLock is already in use
3133******************************************************************************/
3134#define WAITFORLOCK 1
3135int
3136takeVolumeForPath(const char *path)
3137{
3138    int rval = ELAST + 1;
3139    kern_return_t macherr = KERN_SUCCESS;
3140    int lckres = 0;
3141    struct statfs sfs;
3142    const char *volPath = "<unknown>";  // llvm can't track lckres/macherr
3143    mach_port_t taskport = MACH_PORT_NULL;
3144
3145    if (sBRUptLock) {
3146        return EALREADY;        // only support one lock at a time
3147    }
3148
3149    if (geteuid() != 0) {
3150        // kextd shouldn't be watching anything you can touch
3151        // and ignores locking requests from non-root anyway
3152        OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
3153                 "Warning: non-root can't lock the volume for %s", path);
3154        rval = 0;
3155        goto finish;
3156    }
3157
3158    // look up kextd port if not cached
3159    // XX if there's a way to know kextd isn't already running, we could skip
3160    // unnecessarily bringing it up in the boot-time case (see 5108882).
3161    if (!sKextdPort) {
3162        macherr=bootstrap_look_up(bootstrap_port,KEXTD_SERVER_NAME,&sKextdPort);
3163        if (macherr)  goto finish;
3164    }
3165
3166    // get the volume's UUID
3167    if ((rval = upstat(path, NULL, &sfs)))      goto finish;
3168    volPath = sfs.f_mntonname;
3169    if ((rval = copyVolumeInfo(volPath,&s_vol_uuid,NULL,NULL,NULL))) {
3170        goto finish;
3171    }
3172
3173    // allocate a port to pass (in case we die -- kernel cleans up on exit())
3174    taskport = mach_task_self();
3175    if (taskport == MACH_PORT_NULL)  goto finish;
3176    macherr = mach_port_allocate(taskport,MACH_PORT_RIGHT_RECEIVE,&sBRUptLock);
3177    if (macherr)  goto finish;
3178
3179    // try to take the lock; warn if it's busy and then wait for it
3180    // X kextcache -U, if it is going to lock at all, needs only WAITFORLOCK
3181    macherr = kextmanager_lock_volume(sKextdPort, sBRUptLock, s_vol_uuid,
3182                                      !WAITFORLOCK, &lckres);
3183    if (macherr)        goto finish;
3184
3185    // 5519500: sleep until kextd is up and running (w/diskarb, etc)
3186    while (lckres == EAGAIN) {
3187        OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
3188            "kextd wasn't ready; waiting 10 seconds and trying again.");
3189        sleep(10);
3190        macherr = kextmanager_lock_volume(sKextdPort, sBRUptLock, s_vol_uuid,
3191                                          !WAITFORLOCK, &lckres);
3192        if (macherr)    goto finish;
3193    }
3194
3195    // With kextd set up, we sleep until the volume is free.
3196    // WAITFORLOCK should cause the function not to return w/o the lock
3197    // but 8679674 suggested something was going awry so we'll retry.
3198    while (lckres == EBUSY) {
3199        OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
3200            "%s locked; waiting for lock.", volPath);
3201        macherr = kextmanager_lock_volume(sKextdPort, sBRUptLock, s_vol_uuid,
3202                                          WAITFORLOCK, &lckres);
3203        if (macherr)    goto finish;
3204        if (lckres == 0) {
3205            OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
3206                "Lock acquired; proceeding.");
3207        }
3208    }
3209
3210
3211    // kextd might not be watching this volume (isn't currently competing)
3212    // so we set our success to the existance of the volume's root
3213    if (lckres == ENOENT) {
3214        struct stat sb;
3215        rval = stat(volPath, &sb);
3216        if (rval == 0) {
3217            OSKextLog(NULL, kOSKextLogProgressLevel | kOSKextLogIPCFlag,
3218                "Note: kextd not watching %s; proceeding w/o lock", volPath);
3219        }
3220    } else {
3221        rval = lckres;
3222    }
3223
3224finish:
3225    if (sBRUptLock != MACH_PORT_NULL && (lckres != 0 || macherr)) {
3226        mach_port_mod_refs(taskport, sBRUptLock, MACH_PORT_RIGHT_RECEIVE, -1);
3227        sBRUptLock = MACH_PORT_NULL;
3228    }
3229
3230    /* XX needs unraveling XX */
3231    // if kextd isn't competing with us, then we didn't need the lock
3232    if (macherr == BOOTSTRAP_UNKNOWN_SERVICE ||
3233            macherr == MACH_SEND_INVALID_DEST) {
3234        OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
3235            "Warning: kextd unavailable; proceeding w/o lock for %s", volPath);
3236        rval = 0;
3237    } else if (macherr) {
3238        OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
3239            "Couldn't lock %s: %s (%x).", path,
3240            safe_mach_error_string(macherr), macherr);
3241        rval = macherr;
3242    } else if (rval) {
3243        // dump rval
3244        if (rval == -1)     rval = errno;
3245        OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
3246            "Couldn't lock %s: %s", path, strerror(rval));
3247    }
3248
3249    return rval;
3250}
3251
3252/******************************************************************************
3253* putVolumeForPath will unlock the relevant volume, passing 'status' to
3254* inform kextd whether we succeded, failed, or just need more time
3255******************************************************************************/
3256int
3257putVolumeForPath(const char *path, int status)
3258{
3259    int rval = KERN_SUCCESS;
3260
3261    // if not locked, don't sweat it
3262    if (sBRUptLock == MACH_PORT_NULL)
3263        goto finish;
3264
3265    rval = kextmanager_unlock_volume(sKextdPort,sBRUptLock,s_vol_uuid,status);
3266
3267    // tidy up; the server will clean up its stuff if we die prematurely
3268    mach_port_mod_refs(mach_task_self(),sBRUptLock,MACH_PORT_RIGHT_RECEIVE,-1);
3269    sBRUptLock = MACH_PORT_NULL;
3270
3271finish:
3272    if (rval) {
3273        OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
3274            "Couldn't unlock volume for %s: %s (%d).",
3275            path, safe_mach_error_string(rval), rval);
3276    }
3277
3278    return rval;
3279}
3280