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