update_boot.c   [plain text]


/*
 * Copyright (c) 2007 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*
 * FILE: update_boot.c
 * AUTH: Soren Spies (sspies)
 * DATE: 8 June 2006
 * DESC: implement 'kextcache -u' (copying to Apple_Boot partitions)
 *
 */

#include <bless.h>
#include <fcntl.h>
#include <libgen.h>
#include <sysexits.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <IOKit/kext/kextmanager_types.h>
#include <IOKit/kext/OSKextPrivate.h>

#include <bootfiles.h>
#include <CoreFoundation/CoreFoundation.h>
#include <DiskArbitration/DiskArbitration.h>
#include <DiskArbitration/DiskArbitrationPrivate.h>

#include "bootcaches.h"
#include "safecalls.h"
#include "update_boot.h"
#include "kext_tools_util.h"

enum bootReversions {
    nothingSerious = 0,
    noLabels,               // 1
    copyingOFBooter,        // 2
    copyingEFIBooter,       // 3
    copiedBooters,          // 4
    activatingOFBooter,     // 5
    activatingEFIBooter,    // 6
    activatedBooters        // 7
};

const char * bootReversionsStrings[] = {
    NULL,           // unused
    "Labels deleted",
    "Unlinking and copying BootX booter",
    "Unlinking and copying EFI booter",
    "Booters copied",
    "Activating BootX",
    "Activating EFI booter",
    "Booters activated"
};

#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}

// for non-RPS content, including booters
#define OLDEXT ".old"
#define NEWEXT ".new"
#define CONTENTEXT ".contentDetails"

// NOTE: These strings must be the same length, or code in ucopyRPS will break!
// There is a compile time assert in the function to this effect.
#define BOOTPLIST_NAME "com.apple.Boot.plist"
#define BOOTPLIST_APM_NAME "com.apple.boot.plist"

// for Apple_Boot update
struct updatingVol {
    struct bootCaches *caches;          // parsed bootcaches.plist data
    Boolean doRPS, doMisc, doBooters;   // what needs updating
    Boolean expectUpToDate;             // expecting things to be right (-U)
    enum bootReversions changestate;    // track changes for rolling back
    CFArrayRef boots;                   // BSD Names of Apple_Boot partitions
    DASessionRef dasession;             // handle to diskarb

    // changed for each Apple_Boot
    int bootIdx;                        // which helper are we updating
    char bsdname[DEVMAXPATHSIZE];       // bsdname of Apple_Boot
    DADiskRef curBoot;                  // and matching diskarb ref
    char curMount[MNAMELEN];            // path to current boot mountpt
    int curbootfd;                      // Sec: handle to curMount
    char curRPS[PATH_MAX];              // RPS dir inside
    char efidst[PATH_MAX], ofdst[PATH_MAX];
    Boolean onAPM;
};

// diskarb
static int mountBoot(struct updatingVol *up);
static int unmountBoot(struct updatingVol *up);

// ucopy = unlink & copy
// no race for RPS, so install it first
static int ucopyRPS(struct updatingVol *s);  // nuke/copy to inactive
// labels (e.g.) have no fallback, .new is harmless
// XX ucopy"Preboot/Firmware"
static int ucopyMisc(struct updatingVol *s);        // use/overwrite .new names
// booters have fallback paths, but originals might be broken
static int ucopyBooters(struct updatingVol *s);     // nuke/copy booters (inact)
// no label -> hint of indeterminate state (label key in plist?)
static int moveLabels(struct updatingVol *s);       // move aside
static int nukeLabels(struct updatingVol *s);       // byebye (all?)
// booters have worst critical:fragile ratio (point of departure)
static int activateBooters(struct updatingVol *s);  // bless new names
// and the RPS data needed for booting
static int activateRPS(struct updatingVol *s);      // leap-frog w/rename()
// finally, the labels (indicating a working system)
// XX activate"FirmwarePaths/postboot"
static int activateMisc(struct updatingVol *s);     // rename .new / label
// and now that we're safe
static int nukeFallbacks(struct updatingVol *s);

// cleanup routines (RPS is the last step; activateMisc handles label)
static int revertState(struct updatingVol *up);

/* Chain of Trust
 * Our goal is to do anything the bootcaches.plist says, but only to that vol.
 * #1 we only pay attention to root-owned bootcaches.plist files
 * #2 we get an fd to the bootcaches.plist              [trust is here]
// * #3 we validate the bc.plist fd after getting an fd to the volume's root
 * #4 we use stored bsdname for libbless
 * #5 we validate cachefd after the call to bless       [trust -> bsdname]
 * #6 we get curbootfd after each apple_boot mount
 * #7 we validate cachefd after the call                [trust -> curfd]
 * #8 operations take an fd limiting their scope to the mount
 */

// ? do these *need* do { } while() wrappers?
// XX should probably rename to all-caps
#define pathcpy(dst, src) do { \
        if (strlcpy(dst, src, PATH_MAX) >= PATH_MAX)  goto finish; \
    } while(0)
#define pathcat(dst, src) do { \
        if (strlcat(dst, src, PATH_MAX) >= PATH_MAX)  goto finish; \
    } while(0)
// could have made this macro sooner
#define makebootpath(path, rpath) do { \
                                    pathcpy(path, up->curMount); \
                                    pathcat(path, rpath); \
                                } while(0)

// XX there is overlap between errno values and sysexits
static int getExitValueFor(errval)
{
    int rval;

    switch (errval) {
        case ELAST + 1:
            rval = EX_SOFTWARE;
            break;
        case EPERM:
            rval = EX_NOPERM;
            break;
        case EAGAIN:
        case ENOLCK:
            rval = EX_OSERR;
            break;
        case -1:
            switch (errno) {
                case EIO:
                    rval = EX_IOERR;
                    break;
                default:
                    rval = EX_OSERR;
                    break;
            }
            break;
        default:
            rval = errval;
    }

    return rval;
}

/*******************************************************************************
* updateBoots will lock the volume and update the booter partitions 
* Sec: must ensure each target is one of the source's Apple_Boot partitions
*******************************************************************************/
int updateBoots(
    CFURLRef volumeURL,
    Boolean  force,
    Boolean  expectUpToDate)
{
    int result = ELAST + 1;
    struct stat volumeStatBuffer;
    char volRoot[PATH_MAX];
    char *errmsg = NULL;
    struct updatingVol up = { NULL, };
    up.curbootfd = -1;
    struct stat sb;
    struct statfs sfs;

    CFURLRef volURL = NULL;
    DADiskRef dadisk = NULL;
    CFIndex bootcount, bootupdates = 0;
    Boolean doAny;

    if (!CFURLGetFileSystemRepresentation(volumeURL,
        /* resolveToBase */ true, (UInt8 *)volRoot,
        sizeof(volRoot))) {

        OSKextLogStringError(/* kext */ NULL);
        result = EX_OSERR;
        goto finish;
    }
    
    if (stat(volRoot, &volumeStatBuffer)) {
        OSKextLog(/* kext */ NULL,
            kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
            "%s - %s.", volRoot, strerror(errno));
        result = EX_OSERR;
        goto finish;
    }

    errmsg = "Couldn't get description from Disk Arbitration.";
    up.dasession = DASessionCreate(nil);
    if (!up.dasession)      goto finish;
    volURL = CFURLCreateFromFileSystemRepresentation(nil, (UInt8*)volRoot,
                strlen(volRoot) + 1, true /*isDir*/);
    if (!volURL)            goto finish;
    dadisk = DADiskCreateFromVolumePath(nil, up.dasession, volURL);
    if (!dadisk)            goto finish;

    errmsg = NULL;  // locking, readCaches log their own errors

    // technically, we don't need to lock to read bootcaches.plist,
    // but if there are multiple kextcache -u processes, it leaves
    // a longer window during which the file can be updated.  Also, 
    // being locked means owners are enabled so it's okay for
    // readCaches to make [yes, SIC] the bootstamps directory.

    // kextcache -U doesn't lock (and kextd holds off for 5 minutes)
    if (expectUpToDate == false) {
        if (takeVolumeForPaths(volRoot)) { // lock (logs)
            goto finish;
        }
    } else if (getppid() != 2 /* launchctl */) { 
        OSKextLog(/* kext */ NULL,
            kOSKextLogWarningLevel | kOSKextLogIPCFlag,
            "Warning: no locks preventing kextd-triggered updates.");
    }

    // first check for usable bootcaches.plist, else politely bail
    if (!(up.caches = readCaches(dadisk))) {
        // using WarningLevel for now; only emitted by kextcache -u
        OSKextLog(/* kext */ NULL,
            kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
            "%s: no " kBootCachesPath "; nothing to do.",
            volRoot);
        result = 0;
        goto finish;
    }

    /* XX Sec reviewed: how we secure against replacing /'s mkext from external:
     * - mktmp/mkstmp used to create temp file at destination
     * - final rename must be on whatever volume provided the kexts
     * - if volume is /, then kexts owned by root can be trusted (4623559 fstat)
     * - otherwise, rename from wrong volume will fail
     */
    if (check_mkext(up.caches)) {
        // rebuild the mkext under our lock / lack thereof (-v not passed)
        OSKextLog(/* kext */ NULL, kOSKextLogProgressLevel,
            "Rebuilding out-of-date mkext.");
        setenv("_com_apple_kextd_skiplocks", "1", 1);       // no locks, child!
        if (rebuild_mkext(up.caches, true /*wait*/) != 0) {
            goto finish;
        }
    }

    // hasBoots gets helpers from bless, GPT info from the registry
    errmsg = "Couldn't get Apple_Boot information.";
    if (!hasBootRootBoots(up.caches, &up.boots, &up.onAPM)) {
        result = 0;     // no boots -> nothing to do; byebye
            OSKextLog(/* kext */ NULL,
            kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
            "%s: no supported helper partitions to update.", volRoot);
        goto finish;
    }

    if (fstat(up.caches->cachefd, &sb)) { // validate plist -> helpers
        goto finish;
    }

    // Have boot partitions and want to update them
    errmsg = "Trouble analyzing what needs updating.";
    // needUpdates populates our timestamp values for applyStamps
    if (needUpdates(up.caches, &doAny, &up.doRPS, &up.doBooters, &up.doMisc)) {
        goto finish;
    }
    if (!doAny && !force) {
        result = 0;
        // xxx - This is not a warning!
            OSKextLog(/* kext */ NULL,
            kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
            "%s: helper partitions appear up to date.", volRoot);
        goto finish;
    }
    if (force) {
        up.doRPS = up.doBooters = up.doMisc = true;
    }
    up.expectUpToDate = expectUpToDate;

    // Begin work on actual update          [updateBoots vs. checkUpdateBoots?]
    errmsg = "trouble updating one or more helper partitions";

    // mountBoot and unmountBoot will spin the runloop for this DA session
    DASessionScheduleWithRunLoop(up.dasession, CFRunLoopGetCurrent(),
            kCFRunLoopDefaultMode);
    bootcount = CFArrayGetCount(up.boots);
    for (up.bootIdx = 0; up.bootIdx < bootcount; up.bootIdx++) {
        up.changestate = nothingSerious;                // init state
        if ((mountBoot(&up))) {
            goto bootfail;  // sets curMount
        }
        if (up.doRPS && ucopyRPS(&up)) {
            goto bootfail;  // -> inactive
        }
        if (up.doMisc) {
            (void) ucopyMisc(&up);  // -> .new files
        }
        
        // get the labels out of the way (should be optional?)
        if (expectUpToDate) {
            if (moveLabels(&up)) {
                goto bootfail;
            }
        } else {
            if (nukeLabels(&up)) {
                goto bootfail;
            }
        }
        
        if (up.doBooters && ucopyBooters(&up)) {                // .old still active
            goto bootfail;
        }
        if (up.doBooters && activateBooters(&up)) {     // oh boy
            goto bootfail;
        }
        // new booters remain mostly compatible with old kernels (power outage!)
        if (up.doRPS && activateRPS(&up)) {             // mv to safety
            goto bootfail;
        }
        if (activateMisc(&up)) {
            goto bootfail;      // reverts label
        }
        
        up.changestate = nothingSerious;
        bootupdates++;      // loop success
        OSKextLog(/* kext */ NULL,
            kOSKextLogBasicLevel | kOSKextLogFileAccessFlag,
            "Successfully updated helper partition %s.", up.bsdname);
        
bootfail:
        if (up.changestate != nothingSerious) {
            OSKextLog(/* kext */ NULL,
                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
                "Error updating helper partition %s, state %d: %s.",
                up.bsdname, up.changestate,
                bootReversionsStrings[up.changestate]);
        }
        // unroll any changes we may have made
        (void)revertState(&up);     // smart enough to do nothing
        
        // always unmount
        if (nukeFallbacks(&up))
            OSKextLog(/* kext */ NULL,
                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
                "Helper partition %s may be untidy.", up.bsdname);
        if (up.curBoot && unmountBoot(&up))
            OSKextLog(/* kext */ NULL,
                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
                "Couldn't unmount helper partition %s", up.bsdname);
    }

    if (bootupdates != bootcount) {
        goto finish;
    }

    errmsg = "trouble updating bootstamps";
    // don't try to apply bootstamps to a read-only volume
    if (statfs(volRoot, &sfs) == 0) {
        if ((sfs.f_flags & MNT_RDONLY) == 0) {
            if (applyStamps(up.caches)) {
                goto finish;
            }
        } else {
            OSKextLog(NULL,
                kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
                "Warning: %s is read-only: skipping bootstamp updates",
                up.caches->root);
        }
    } else {
        goto finish;
    }

   /* We're here if we successfully updated the helpers.  If weren't expecting
    * to make updates, return EX_OSFILE, otherwise return success.  */
    if (expectUpToDate) {
        result = EX_OSFILE;
    } else {
        result = EX_OK;
    }

finish:
    // expectUpToDate means someone (usu. launchd during early boot) is
    // asking us whether we had to update anything ... and it's going to
    // use the answer to decide whether or not to reboot the system (off
    // freshened Apple_Boot bits).  EX_OSFILE is the exit status indicating
    // "success with changes."  Otherwise, we'll be proceeding with
    // boot (result != EX_OSFILE) which means we should (if we're root)
    // notify kextd that we're done by sending it a a "fake" lock request.
    if (expectUpToDate && result != EX_OSFILE && geteuid() == 0) {
        // PR-5519500 - Call overloaded takeVolumeForPath to signal kextd
        // that -U is done and it can start watching volumes.  If the 
        // lock succeeds b/c this isn't early boot, an unlock occurs below.
        if (takeVolumeForPaths(volRoot) != EAGAIN) {
            OSKextLog(/* kext */ NULL,
                kOSKextLogErrorLevel | kOSKextLogIPCFlag,
                "Unexpected error triggering kextd volume watching.");
        }
    }

    // since updateBoots() -> exit(), convert common to sysexits(3) values
    if (result && result != EX_OSFILE) {
        result = getExitValueFor(result);
    }
    putVolumeForPath(volRoot, result);          // handles not locked (& logs)

    if (volURL)             CFRelease(volURL);
    if (dadisk)             CFRelease(dadisk);
    if (up.boots)           CFRelease(up.boots);
    if (up.curbootfd != -1) close(up.curbootfd);
    if (up.dasession) {
        DASessionUnscheduleFromRunLoop(up.dasession, CFRunLoopGetCurrent(),
                kCFRunLoopDefaultMode);
        CFRelease(up.dasession);
    }
    if (up.caches)          destroyCaches(up.caches);

    if (result && result != EX_OSFILE && errmsg) {
        OSKextLog(/* kext */ NULL,
            kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
            "%s: %s.", volRoot, errmsg);
    }

    return result;
}

// ucopyBooters and activateBooters, backwards
static int revertState(struct updatingVol *up)
{
    int rval = 0;       // optimism to accumulate errors with |=
    char path[PATH_MAX], oldpath[PATH_MAX];
    struct bootCaches *caches = up->caches;
    Boolean doMisc;

    OSKextLog(/* kext */ NULL,
        kOSKextLogProgressLevel | kOSKextLogFileAccessFlag,
        "Rolling back any incomplete updates.");
        
    switch (up->changestate) {
        // inactive booters are still good
        case activatedBooters:
            // we've blessed the new booters; so let's bless the old ones
            pathcat(up->ofdst, OLDEXT);
            pathcat(up->efidst, OLDEXT);
            rval |= activateBooters(up);    // XX this should reactivate the old
        case activatingEFIBooter:
    case activatingOFBooter:        // unneeded since 'bless' is one op
        case copiedBooters:
    case copyingEFIBooter:
        if (caches->efibooter.rpath[0]) {
            makebootpath(path, caches->efibooter.rpath);
            pathcpy(oldpath, path);         // old ones are blessed; rename
            pathcat(oldpath, OLDEXT);
            (void)sunlink(up->curbootfd, path);
            rval |= srename(up->curbootfd, oldpath, path);
        }

    case copyingOFBooter:
        if (caches->ofbooter.rpath[0]) {
            makebootpath(path, caches->ofbooter.rpath);
            pathcpy(oldpath, path);
            pathcat(oldpath, OLDEXT);
            (void)sunlink(up->curbootfd, path);
            rval |= srename(up->curbootfd, oldpath, path);
        }

    // XX
    // case copyingMisc:
    // would clean up the .new turds

        case noLabels:
            // XX hacky (c.f. nukeFallbacks which nukes .disabled label)
            doMisc = up->doMisc;
            up->doMisc = false;
            rval |= activateMisc(up);  // writes new label if !doMisc
            up->doMisc = doMisc;

        case nothingSerious:
            // everything is good
            break;
    }

finish:
    return rval;
};

/*******************************************************************************
* mountBoot digs in for the root, and mounts up the Apple_Boots
* mountpoints are stored in up->bootparts
*******************************************************************************/
static int mountBoot(struct updatingVol *up)
{
    int rval = ELAST + 1;
    CFStringRef mountargs[] = { CFSTR("perm"), CFSTR("nobrowse"), NULL };
    CFStringRef str;
    DADissenterRef dis = (void*)kCFNull;
    CFDictionaryRef ddesc = NULL;
    CFURLRef volURL;
    struct statfs bsfs;
    struct stat secsb;

    OSKextLog(/* kext */ NULL,
        kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
        "Mounting helper partition.");

    // request the Apple_Boot mount
    str = (CFStringRef)CFArrayGetValueAtIndex(up->boots, up->bootIdx);
    if (!str) {
        goto finish;
    }
    if (!CFStringGetFileSystemRepresentation(str, up->bsdname, DEVMAXPATHSIZE)) {
        goto finish;
    }
    if (!(up->curBoot = DADiskCreateFromBSDName(nil,up->dasession,up->bsdname))) {
        goto finish;
    }
    
    OSKextLog(/* kext */ NULL,
        kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
        "Mounted %s.", up->bsdname);

    // DADiskMountWithArgument might call _daDone before it returns (e.g. if it
    // knows your request is impossible ...)
    // _daDone updates our 'dis[senter]'
    DADiskMountWithArguments(up->curBoot, NULL/*mnt*/,kDADiskMountOptionDefault,
                             _daDone, &dis, mountargs);

    // ... so we use kCFNull and check the value before CFRunLoopRun()
    if (dis == (void*)kCFNull) {
        CFRunLoopRun();         // stopped by _daDone (which updates 'dis')
    }
    if (dis) {
        rval = DADissenterGetStatus(dis);
        // if it's already mounted, try to unmount it? (XX skank DEBUG(?) hack)
        if (rval == kDAReturnBusy && up->curMount[0] != '\1') {
            up->curMount[0] = '\1';
            if (0 == unmountBoot(up)) {
                // try again
                return mountBoot(up);
            }
        }
        goto finish;
    }

    // get and stash the mountpoint of the boot partition
    if (!(ddesc = DADiskCopyDescription(up->curBoot)))  goto finish;
    volURL = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumePathKey);
    if (!volURL || CFGetTypeID(volURL) != CFURLGetTypeID())  goto finish;
    if (!CFURLGetFileSystemRepresentation(volURL, true /*resolve base*/,
            (UInt8*)up->curMount, PATH_MAX))        goto finish;

    // Sec: get a non-spoofable handle to the current boot (trust moves)
    if (-1 == (up->curbootfd = open(up->curMount, O_RDONLY, 0)))   goto finish;
    if (fstat(up->caches->cachefd, &secsb))  goto finish;    // rootvol extant?

    // we only support 128 MB Apple_Boot partitions
    if (fstatfs(up->curbootfd, &bsfs))  goto finish;
    if (bsfs.f_blocks * bsfs.f_bsize < (128 * 1<<20)) {
        OSKextLog(/* kext */ NULL,
            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
            "Apple_Boot helper < 128 MB; skipping.");
        goto finish;
    }

    rval = 0;

finish:
    if (ddesc)      CFRelease(ddesc);
    if (dis && dis != (void*)kCFNull) { // for spurious CFRunLoopRun() return
        CFRelease(dis);
    }

    if (rval != 0 && up->curBoot) {
        unmountBoot(up);        // unmount anything we managed to mount
    }
    if (rval) {
        if (rval != ELAST + 1) {
            OSKextLog(/* kext */ NULL,
                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
                "Failed to mount helper partition: error %#X (DA err# %#.2x).",
                rval,rval & ~(err_local|err_local_diskarbitration));
        } else {
            OSKextLog(/* kext */ NULL,
                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
                "Failed to mount helper partition.");
        }
    }

    return rval;
}

/*******************************************************************************
* unmountBoot 
* works like mountBoot, but for unmount
*******************************************************************************/
static int unmountBoot(struct updatingVol *up)
{
    int rval = 0;
    DADissenterRef dis = (void*)kCFNull;
    
    OSKextLog(/* kext */ NULL, kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
        "Unmounting helper partition %s.", up->bsdname);

    // bail if nothing to actually unmount (still free up curBoot below)
    if (!up->curBoot)           goto finish;
    if (!up->curMount[0])       goto finish;

    if (up->curbootfd != -1)    close(up->curbootfd);

    // _daDone populates 'dis'[senter]
    rval = ELAST + 1;
    DADiskUnmount(up->curBoot, kDADiskMountOptionDefault, _daDone, &dis);
    if (dis == (void*)kCFNull) {            // in case _daDone already called
        CFRunLoopRun();
    }

    // if that didn't work, try harder
    if (dis) {
        CFRelease(dis);
        dis = (void*)kCFNull;
        OSKextLog(/* kext */ NULL,
            kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
            "Trouble unmounting boot partition; forcing...");
        DADiskUnmount(up->curBoot, kDADiskUnmountOptionForce, _daDone, &dis);
        if (dis == (void*)kCFNull) {
            CFRunLoopRun();
        }
        if (dis)  goto finish;
    }

    rval = 0;

finish:
    up->curMount[0] = '\0';     // to keep tidy
    if (up->curBoot) {
        CFRelease(up->curBoot);
        up->curBoot = NULL;
    }
    if (dis && dis != (void*)kCFNull) {
        CFRelease(dis);
    }

    return rval;
}

/*******************************************************************************
* ucopyRPS unlinks old/copies new RPS content w/o activating
* RPS files are considered important -- non-zero file sizes only!
* XX could validate the kernel with Mach-o header
*******************************************************************************/
// if we were good, I'd be able to share "statRPS" with the efiboot sources
typedef int EFI_STATUS;
typedef struct stat EFI_FILE_HANDLE;
typedef char UINT16;
typedef Boolean BOOLEAN;
// typedef ...
/* 
:'a,'bs/EFI_ERROR//
:'a,'bs/L"/"/
:'a,'bs/%a/%s/
#define printf kextd_error_log
#define SPrint snprintf
#define EFI_NOT_FOUND ENOENT
#define BOOT_STRING_LEN PATH_MAX
*/
static int
FindRPSDir(struct updatingVol *up, char prev[PATH_MAX], char current[PATH_MAX],
            char next[PATH_MAX])
{
     char rpath[PATH_MAX], ppath[PATH_MAX], spath[PATH_MAX];   
/*
 * FindRPSDir looks for a "rock," "paper," or "scissors" directory
 * - handle all permutations: 3 dirs, any 2 dirs, any 1 dir
 */
// static EFI_STATUS
// FindRPSDir(EFI_FILE_HANDLE BootDir, EFI_FILE_HANDLE *newBoot)
// 
    int rval = ELAST + 1, status;
    struct stat r, p, s;
    Boolean haveR, haveP, haveS;
    char *prevp, *curp, *nextp;

    haveR = haveP = haveS = false;
    prevp = curp = nextp = NULL;

    // set up full paths with intervening slash
    pathcpy(rpath, up->curMount);
    pathcat(rpath, "/");
    pathcpy(ppath, rpath);
    pathcpy(spath, rpath);

    pathcat(rpath, kBootDirR);
    pathcat(ppath, kBootDirP);
    pathcat(spath, kBootDirS);

    status = stat(rpath, &r);   // easier to let this fail
    haveR = (status == 0);
    status = stat(ppath, &p);
    haveP = (status == 0);
    status = stat(spath, &s);
    haveS = (status == 0);

    if (haveR && haveP && haveS) {    // NComb(3,3) = 1
        OSKextLog(/* kext */ NULL,
            kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
            "Warning: all of R,P,S exist: picking 'R'.");
        curp = rpath;   nextp = ppath;  prevp = spath;
    }   else if (haveR && haveP) {          // NComb(3,2) = 3
        // p wins
        curp = ppath;   nextp = spath;  prevp = rpath;
    } else if (haveR && haveS) {
        // r wins
        curp = rpath;   nextp = ppath;  prevp = spath;
    } else if (haveP && haveS) {
        // s wins
        curp = spath;   nextp = rpath;  prevp = ppath;
    } else if (haveR) {                     // NComb(3,1) = 3
        // r wins by default
        curp = rpath;   nextp = ppath;  prevp = spath;
    } else if (haveP) {
        // p wins by default
        curp = ppath;   nextp = spath;  prevp = rpath;
    } else if (haveS) {
        // s wins by default
        curp = spath;   nextp = rpath;  prevp = ppath;
    } else {                                          // NComb(3,0) = 0
        // we'll start with rock
        curp = rpath;   nextp = ppath;  prevp = spath;
    }

    if (strlcpy(prev, prevp, PATH_MAX) >= PATH_MAX)     goto finish;
    if (strlcpy(current, curp, PATH_MAX) >= PATH_MAX)   goto finish;
    if (strlcpy(next, nextp, PATH_MAX) >= PATH_MAX)     goto finish;

    rval = 0;

finish:
    if (rval) {
        OSKextLog(/* kext */ NULL,
            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
            "FindRPSDir(): %s", strerror(errno));
    }
    return rval;
}

// UUID helper for ucopyRPS
static int insertUUID(struct updatingVol *up, char *srcpath, char *dstpath)
{
    int rval = ELAST + 1;
    int fd = -1;
    struct stat sb;
    void *buf;
    CFDataRef data = NULL;
    CFMutableDictionaryRef pldict = NULL;
    CFStringRef str = NULL;

    mode_t dirmode;
    char dstparent[PATH_MAX];
    CFIndex len;

    // suck in plist
    if (-1 == (fd = sopen(up->caches->cachefd, srcpath, O_RDONLY, 0))) {
        goto finish;
    }
    if (fstat(fd, &sb))                                 goto finish;
    if (!(buf = malloc(sb.st_size)))                    goto finish;
    if (read(fd, buf, sb.st_size) != sb.st_size)        goto finish;
    if (!(data = CFDataCreate(nil, buf, sb.st_size)))   goto finish;
    // make mutable dictionary
    pldict = (CFMutableDictionaryRef)CFPropertyListCreateFromXMLData(nil, data,
            kCFPropertyListMutableContainers, NULL /* errstring */);
    if (!pldict || CFGetTypeID(pldict)!=CFDictionaryGetTypeID()) {
        // maybe the plist is empty
        pldict = CFDictionaryCreateMutable(nil, 0 /* could be 1 */, 
            &kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks);
        if (!pldict)    goto finish;
    }

    // make a CFStr out of the UUID
    str = CFStringCreateWithCString(nil, up->caches->uuid_str, kCFStringEncodingASCII);
    if (!str)   goto finish;
    CFDictionarySetValue(pldict, CFSTR(kRootUUIDKey), str);


    // and write dictionary back

    (void)sunlink(up->curbootfd, dstpath);

    // figure out directory mode
    dirmode = ((sb.st_mode&~S_IFMT) | S_IWUSR | S_IXUSR /* u+wx */);
    if (dirmode & S_IRGRP)      dirmode |= S_IXGRP;     // add conditional o+x
    if (dirmode & S_IROTH)      dirmode |= S_IXOTH;

    // and recursively create the parent directory       
    if (strlcpy(dstparent, dirname(dstpath), PATH_MAX) >= PATH_MAX) goto finish;
    if ((sdeepmkdir(up->curbootfd, dstparent, dirmode)))            goto finish;

    close(fd);
    if (-1 == (fd=sopen(up->curbootfd, dstpath, O_WRONLY|O_CREAT, sb.st_mode))) {
        goto finish;
    }
    CFRelease(data);
    if (!(data = CFPropertyListCreateXMLData(nil, pldict)))     goto finish;
    len = CFDataGetLength(data);
    if (write(fd, CFDataGetBytePtr(data), len) != len)          goto finish;

    rval = 0;

finish:
    if (str)        CFRelease(str);
    if (data)       CFRelease(data);
    if (pldict)     CFRelease(pldict);
    if (fd != -1)   close(fd);

    return rval;
}

// we can bail on any error because only a whole RPS dir makes sense
static int ucopyRPS(struct updatingVol *up)
{
    int rval = ELAST+1;
    char discard[PATH_MAX];
    struct stat sb;
    int i;
    char srcpath[PATH_MAX], dstpath[PATH_MAX];
    char * plistNamePtr;
    
    OSKextLog(/* kext */ NULL,
        kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
        "Beginning copy and atomic activation.");

    // we're going to copy into the currently-inactive directory
    if (FindRPSDir(up, up->curRPS, discard, discard))  goto finish;

    // erase if present (we expect to have removed it)
    if (stat(up->curRPS, &sb) == 0) {
        if (sdeepunlink(up->curbootfd, up->curRPS)) {
            OSKextLog(/* kext */ NULL,
                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
                "%s: %s.", up->curRPS, strerror(errno));
            goto finish;
        }
    }

    // create the directory
    if (smkdir(up->curbootfd, up->curRPS, kRPSDirMask)) {
        OSKextLog(/* kext */ NULL,
            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
            "%s: %s.", up->curRPS, strerror(errno));
        goto finish;
    }

    // and loop
    for (i = 0; i < up->caches->nrps; i++) {
        pathcpy(srcpath, up->caches->root);
        pathcat(srcpath, up->caches->rpspaths[i].rpath);
        pathcpy(dstpath, up->curRPS);
        pathcat(dstpath, up->caches->rpspaths[i].rpath);

        // is it Boot.plist?
        if (&up->caches->rpspaths[i] == up->caches->bootconfig) {
            
            // PR-5115900 - name com.apple.boot.plist on APM since Tiger
            //  bless scribbles on com.apple.Boot.plist

            // This assert ensures BOOTPLIST_NAME and BOOTPLIST_APM_NAME are the same
            // length (defined at top of file).  If they differ, the code will not compile.
            COMPILE_TIME_ASSERT(sizeof(BOOTPLIST_NAME) == sizeof(BOOTPLIST_APM_NAME));

            // If we are on an APM partition, substitute the new plist name
            if (up->onAPM) {
                plistNamePtr = strstr(dstpath, BOOTPLIST_NAME);
                if (plistNamePtr) {
                    strncpy(plistNamePtr, BOOTPLIST_APM_NAME, strlen(BOOTPLIST_NAME));
                }
            }

            if (insertUUID(up, srcpath, dstpath)) {
                OSKextLog(/* kext */ NULL,
                    kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
                    "Error populating config file %s.", dstpath);
                goto finish;
            }
        } else {
#if 0
            // we might want a zero-length (cookie) file in the Apple_Boot
            // XX Leopard(?) other checks like is your Mach-O complete?
            if (stat(srcpath, &sb) == 0 && sb.st_size == 0) {
                OSKextLog(/* kext */ NULL,
                    kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
                    "Zero-size RPS file %s?", srcpath);
                goto finish;
            }
#endif
            // scopyfile creates any intermediate directories
            if (scopyfile(up->caches->cachefd,srcpath,up->curbootfd,dstpath)) {
                OSKextLog(/* kext */ NULL,
                    kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
                    "Error copying %s.", srcpath);
                goto finish;
            }
        }
    }

    rval = 0;

finish:
    return rval;
}

/*******************************************************************************
* ucopyMisc writes misc files (customizing labels ;?) to .new (inactive) names
* [redundant label copy would be easy to avoid]
*******************************************************************************/
static int ucopyMisc(struct updatingVol *up)
{
    int rval = -1;
    int i, nprocessed = 0;
    char srcpath[PATH_MAX], dstpath[PATH_MAX];
    struct stat sb;

    OSKextLog(/* kext */ NULL,
        kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
        "Copying new non-booter files.");
    
    for (i = 0; i < up->caches->nmisc; i++) {
        pathcpy(srcpath, up->caches->root);
        pathcat(srcpath, up->caches->miscpaths[i].rpath);
        pathcpy(dstpath, up->curMount);
        pathcat(dstpath, up->caches->miscpaths[i].rpath);
        pathcat(dstpath, ".new");

        if (stat(srcpath, &sb) == 0) { 
            if (scopyfile(up->caches->cachefd,srcpath,up->curbootfd,dstpath)) {
                OSKextLog(/* kext */ NULL,
                    kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
                    "Error copying %s to %s.",
                    srcpath, dstpath);
            }
            continue;
        }

        nprocessed++;
    }

    rval = (nprocessed != i);

finish:
    return rval;
}

/*******************************************************************************
* moveLabels moves the labels aside in case they're needed again
* activateMisc will move these back
* no label -> hint of indeterminate state (label key in plist/other file?)
* Leopard: put/switch in some sort of "(updating!)" label (see BL[ess] routines)
*******************************************************************************/
static int moveLabels(struct updatingVol *up)
{
    int rval = 0;
    char path[PATH_MAX];
    struct stat sb;
    int fd = -1;
    
    OSKextLog(/* kext */ NULL,
        kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
        "Moving aside old labels.");

    pathcpy(path, up->curMount);
    pathcat(path, up->caches->label->rpath);
    if (0 == (stat(path, &sb))) {
        char newpath[PATH_MAX];
        unsigned char tbxichrp[32] = {'\0', };

        // rename
        pathcpy(newpath, path);
        pathcat(newpath, NEWEXT);
        rval = srename(up->curbootfd, path, newpath);
        if (rval)       goto finish;

        // remove magic type/creator
        if (-1 == (fd=sopen(up->curbootfd, newpath, O_RDWR, 0)))  goto finish;
        if(fsetxattr(fd,XATTR_FINDERINFO_NAME,&tbxichrp,sizeof(tbxichrp),0,0)) {
            goto finish;
        }
    } 

    up->changestate = noLabels;

finish:
    if (fd != -1)   close(fd);

    return rval;
}

/*******************************************************************************
* nukeLabels gets rid of the label and .contentDetails files
* since activateMisc can create a new label, we just nuke
* no label -> hint of indeterminate state (label key in plist/other file?)
* Leopard: put/switch in some sort of "(updating!)" label (see BL[ess] routines)
*******************************************************************************/
static int nukeLabels(struct updatingVol *up)
{
    int rval = 0;
    char labelp[PATH_MAX];
    struct stat sb;
    
    OSKextLog(/* kext */ NULL,
        kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
        "Destroying old labels.");

    pathcpy(labelp, up->curMount);
    pathcat(labelp, up->caches->label->rpath);
    if (0 == (stat(labelp, &sb))) {
        rval |= sunlink(up->curbootfd, labelp);
    } 

    // now for the content details (if any)
    pathcat(labelp, CONTENTEXT);        // append extension

    if (0 == (stat(labelp, &sb))) {
        rval |= sunlink(up->curbootfd, labelp);
    }

    up->changestate = noLabels;

finish:
    return rval;
}

/*******************************************************************************
* ucopyBooters unlink/copies down booters but doesn't bless them
*******************************************************************************/
static int ucopyBooters(struct updatingVol *up)
{
    int rval = ELAST + 1;
    char srcpath[PATH_MAX], oldpath[PATH_MAX];

    OSKextLog(/* kext */ NULL,
        kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
        "Copying new booters.");
    
    // copy BootX, boot.efi
    up->changestate = copyingOFBooter;
    if (up->caches->ofbooter.rpath[0]) {
        pathcpy(srcpath, up->caches->root);
        pathcat(srcpath, up->caches->ofbooter.rpath);   // <root>/S/L/CS/BootX
        pathcpy(up->ofdst, up->curMount);
        pathcat(up->ofdst, up->caches->ofbooter.rpath); // <boot>/S/L/CS/BootX
        pathcpy(oldpath, up->ofdst);
        pathcat(oldpath, OLDEXT);                  // <boot>/S/L/CS/BootX.old

        (void)sunlink(up->curbootfd, oldpath);
        if (srename(up->curbootfd, up->ofdst, oldpath) && errno !=ENOENT) {
            goto finish;
        }
        if (scopyfile(up->caches->cachefd, srcpath, up->curbootfd, up->ofdst)) {
            OSKextLog(/* kext */ NULL,
                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
                "%s: %s.", srcpath, strerror(errno));
            goto finish;
        }
    }

    up->changestate = copyingEFIBooter;
    if (up->caches->efibooter.rpath[0]) {
        pathcpy(srcpath, up->caches->root);
        pathcat(srcpath, up->caches->efibooter.rpath);   // ... boot.efi
        pathcpy(up->efidst, up->curMount);
        pathcat(up->efidst, up->caches->efibooter.rpath);
        pathcpy(oldpath, up->efidst);
        pathcat(oldpath, OLDEXT);

        (void)sunlink(up->curbootfd, oldpath);
        if (srename(up->curbootfd, up->efidst, oldpath) && errno != ENOENT) {
            goto finish;
        }
        if (scopyfile(up->caches->cachefd, srcpath, up->curbootfd, up->efidst)){
            OSKextLog(/* kext */ NULL,
                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
                "Error copying booter %s.", srcpath);
            goto finish;
        }
    }

    up->changestate = copiedBooters;
    rval = 0;

finish:
    return rval;
}


// booters have worst critical:fragile ratio (basically point of no return)
/*******************************************************************************
* bless recently-copied booters
* operatens entirely on up->??dst which allows revertState to use it ..?
*******************************************************************************/
#define CLOSE(fd) do { (void)close(fd); fd = -1; } while(0)
enum blessIndices {
    kSystemFolderIdx = 0,
    kEFIBooterIdx = 1
    // Apple_Boot doesn't use 2-7
};
static int activateBooters(struct updatingVol *up)
{
    int rval = ELAST + 1;
    int fd = -1;
    uint32_t vinfo[8] = { 0, };
    struct stat sb;
    char parent[PATH_MAX];

    OSKextLog(/* kext */ NULL,
        kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
        "Activating new booters.");
    
    // activate BootX, boot.efi
    up->changestate = activatingOFBooter;
    if (up->caches->ofbooter.rpath[0]) {
        unsigned char tbxichrp[32] = {'t','b','x','i','c','h','r','p','\0',};

        // flush booter bytes to disk (really)
        if (-1 == (fd=sopen(up->curbootfd, up->ofdst, O_RDWR, 0)))  goto finish;
        if (fcntl(fd, F_FULLFSYNC))                                 goto finish;

        // apply type/creator (assuming same folder as previous, now active)
        if(fsetxattr(fd,XATTR_FINDERINFO_NAME,&tbxichrp,sizeof(tbxichrp),0,0)) {
            goto finish;
        }
        CLOSE(fd);

        // get fileID of booter's enclosing folder 
        pathcpy(parent, dirname(up->ofdst));
        if (-1 == (fd=sopen(up->curbootfd, parent, O_RDONLY, 0)))  goto finish;
        if (fstat(fd, &sb))                                 goto finish;
        CLOSE(fd);
        vinfo[kSystemFolderIdx] = sb.st_ino;
    }

    up->changestate = activatingEFIBooter;
    if (up->caches->efibooter.rpath[0]) {
        // sync to disk
        if (-1==(fd=sopen(up->curbootfd, up->efidst, O_RDONLY, 0))) goto finish;
        if (fcntl(fd, F_FULLFSYNC))                                 goto finish;

        // get file ID
        if (fstat(fd, &sb))     goto finish;
        CLOSE(fd);
        vinfo[kEFIBooterIdx] = sb.st_ino;

        // since Inca has only one booter, but we want a blessed folder
        if (!vinfo[0]) {
            // get fileID of booter's enclosing folder 
            pathcpy(parent, dirname(up->efidst));
            if (-1 == (fd=sopen(up->curbootfd, parent, O_RDONLY, 0))) {
                goto finish;
            }
            if (fstat(fd, &sb))                             goto finish;
            CLOSE(fd);
            vinfo[kSystemFolderIdx] = sb.st_ino;
        }
    }

    // blessing efiboot/sysfolder happens by updating the root of the volume
    if (schdir(up->curbootfd, up->curMount, &fd))           goto finish;
    if ((rval = BLSetVolumeFinderInfo(NULL, ".", vinfo)))   goto finish;
    (void)restoredir(fd);           // tidy up (closes fd)
    fd = -1;

    up->changestate = activatedBooters;

finish:
    if (fd != -1)   close(fd);
    return rval;
}

/*******************************************************************************
* leap-frog w/rename()
*******************************************************************************/
static int activateRPS(struct updatingVol *up)
{
    int rval = ELAST + 1;
    char prevRPS[PATH_MAX], curRPS[PATH_MAX], nextRPS[PATH_MAX];
    
    OSKextLog(/* kext */ NULL,
        kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
        "Completing copy and atomic activation.");

    if (FindRPSDir(up, prevRPS, curRPS, nextRPS))   goto finish;

    // if current != the one we just populated
    if (strncmp(curRPS, up->curRPS, PATH_MAX) != 0) {
        // rename prev -> next ... done!?
        if (srename(up->curbootfd, prevRPS, nextRPS))   goto finish;
    }

    // thwunk everything to disk (now that essential boot files are in place)
    if (fcntl(up->curbootfd, F_FULLFSYNC))              goto finish;

    rval = 0;

finish:
    return rval;
}


/*******************************************************************************
* activateMisc renames .new files to final names and relabels the volumes
* active labels indicate an updated system
* - construct new labels with trailing numbers
* - use BLGenerateOFLabel() and overwrite any copied-down label
* X need to be consistent throughout regarding missing misc files (esp. label?)
*******************************************************************************/
#ifndef OPENSOURCE      // BLGenerateOFLabel uses CG
static int writeLabels(struct updatingVol *up, char *labelp)
{
    int rval = ELAST + 1;
    CFDataRef lData = NULL;
    CFIndex len;
    int fd = -1;
    char bootname[NAME_MAX];
    char contentPath[PATH_MAX];
    char *fmt = (CFArrayGetCount(up->boots) == 1) ? "%s" : "%s %d";
    
    OSKextLog(/* kext */ NULL,
        kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
        "Writing new labels.");

    if (NAME_MAX <= snprintf(bootname, NAME_MAX, fmt,
                            up->caches->volname, up->bootIdx + 1))
        goto finish;
    if (BLGenerateOFLabel(NULL, bootname, &lData))      goto finish;

    // write the data
    if (-1 == (fd = sopen(up->curbootfd, labelp, O_CREAT|O_WRONLY, 0644)))
        goto finish;
    len = CFDataGetLength(lData);
    if (write(fd, CFDataGetBytePtr(lData), len) != len) goto finish;

    // and write the content detail
    pathcpy(contentPath, labelp);
    pathcat(contentPath, CONTENTEXT);
    close(fd);
    if (-1 == (fd = sopen(up->curbootfd, contentPath, O_CREAT|O_WRONLY, 0644)))
        goto finish;
    len = strlen(bootname);
    if (write(fd, bootname, len) != len)        goto finish;

    rval = 0;

finish:
    if (fd != -1)   close(fd);
    if (lData)      CFRelease(lData);

    return rval;
}
#endif  // OPENSOURCE

static int activateMisc(struct updatingVol *up)     // rename the .new
{
    int rval = ELAST + 1;
    char path[PATH_MAX], opath[PATH_MAX];
    int i = 0, nprocessed = 0;
    int fd = -1;
    struct stat sb;
    unsigned char tbxjchrp[32] = { 't','b','x','j','c','h','r','p','\0', };
    
    if (up->doMisc) {
        
        OSKextLog(/* kext */ NULL,
            kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
            "Activating non-booter files.");
        
        // do them all
        for (i = 0; i < up->caches->nmisc; i++) {
            if (strlcpy(path, up->curMount, PATH_MAX) >= PATH_MAX)   continue;
            if (strlcat(path, up->caches->miscpaths[i].rpath, PATH_MAX) 
                        > PATH_MAX)   continue;
            if (strlcpy(opath, path, PATH_MAX) >= PATH_MAX)     continue;
            if (strlcat(opath, NEWEXT, PATH_MAX) >= PATH_MAX)   continue;

            if (stat(opath, &sb) == 0) {
                if (srename(up->curbootfd, opath, path))        continue;
            }

            nprocessed++;
        }

    }

    pathcpy(path, up->curMount);
    pathcat(path, up->caches->label->rpath);
#ifndef OPENSOURCE
    if (up->expectUpToDate) {
#endif
        // move label back
        char newpath[PATH_MAX];

        pathcpy(newpath, path);     // just rename
        pathcat(newpath, NEWEXT);
        (void)srename(up->curbootfd, newpath, path);
#ifndef OPENSOURCE
    } else {
        // write labels
        (void)sunlink(up->curbootfd, path);
        if (writeLabels(up, path))          goto finish;
#endif
    }

    // assign type/creator to the label (non-OPENSOURCE might have copied)
    if (0 == (stat(path, &sb))) {
        if (-1 == (fd = sopen(up->curbootfd, path, O_RDWR, 0)))   goto finish;

        if (fsetxattr(fd,XATTR_FINDERINFO_NAME,&tbxjchrp,sizeof(tbxjchrp),0,0))
            goto finish;
        close(fd); fd = -1;
    }

    rval = (i != nprocessed);

finish:
    if (fd != -1)   close(fd);
    return rval;
}

/*******************************************************************************
* get rid of everything "extra"
*******************************************************************************/
static int nukeFallbacks(struct updatingVol *up)
{
    int rval = 0;               // OR-ative return value
    int bsderr;
    char delpath[PATH_MAX];
    struct bootCaches *caches = up->caches;

    // using pathcpy b/c if that's failing, it's worth bailing
    // XX should probably only try to unlink if present

    // maybe mount failed (in which there aren't any fallbacks
    if (!up->curBoot)   goto finish;

    // if needed, unlink .old booters
    if (up->doBooters) {
        if (caches->ofbooter.rpath[0]) {
            makebootpath(delpath, caches->ofbooter.rpath);
            pathcat(delpath, OLDEXT);
            if ((bsderr = sunlink(up->curbootfd, delpath)) && errno != ENOENT) {
                rval |= bsderr;
            }
        }
        if (caches->efibooter.rpath[0]) {
            makebootpath(delpath, caches->efibooter.rpath);
            pathcat(delpath, OLDEXT);
            if ((bsderr = sunlink(up->curbootfd, delpath)) && errno != ENOENT) {
                rval |= bsderr;
            }
        }
    }

    // if needed, deepunlink prevRPS
    // which, conveniently, will be right regardless of whether we succeeded
    if (up->doRPS) {
        char toss[PATH_MAX];

        if (0 == FindRPSDir(up, delpath, toss, toss)) {
            if ((bsderr=sdeepunlink(up->curbootfd,delpath)) && bsderr!=ENOENT) {
                rval |= bsderr;
            }
        }
    }

finish:
    return rval;
}