#include <bless.h>
#include <miscfs/devfs/devfs.h> // UID_ROOT, GID_WHEEL
#include <fcntl.h>
#include <hfs/hfs_mount.h> // hfs_mount_args
#include <libgen.h>
#include <mach/mach_error.h>
#include <mach/mach_port.h> // mach_port_allocate()
#include <servers/bootstrap.h>
#include <sysexits.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <apfs/apfs_fsctl.h>
#include <IOKit/kext/kextmanager_types.h>
#include <IOKit/kext/OSKextPrivate.h>
#include <IOKit/kext/kextmanager_types.h>
#include <IOKit/kext/kextmanager_mig.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOBSD.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/storage/IOPartitionScheme.h>
#include <MediaKit/GPTTypes.h>
#include <bootfiles.h>
#include <CoreFoundation/CoreFoundation.h>
#include <DiskArbitration/DiskArbitration.h>
#include <DiskArbitration/DiskArbitrationPrivate.h>
#include "bootcaches.h"
#include "bootroot_internal.h" // includes bootroot.h
#include "fork_program.h"
#include "safecalls.h"
#include "kext_tools_util.h"
static mach_port_t sBRUptLock = MACH_PORT_NULL;
static mach_port_t sKextdPort = MACH_PORT_NULL;
static int sBRUptLockCtr = 0;
static uuid_t s_vol_uuid;
enum bootReversions {
nothingSerious = 0,
noLabel, copyingOFBooter, copyingEFIBooter, copiedBooters, activatingOFBooter, activatingEFIBooter, activatedBooters, };
enum blessIndices {
kSystemFolderIdx = 0,
kEFIBooterIdx = 1
};
const char * bootReversionsStrings[] = {
NULL, "Label deleted",
"Unlinking and copying BootX booter",
"Unlinking and copying EFI booter",
"Booters copied",
"Activating BootX",
"Activating EFI booter",
"Booters activated"
};
struct updatingVol {
struct bootCaches *caches; char srcRoot[PATH_MAX]; uuid_string_t host_uuid; CFDictionaryRef bpoverrides; CFDictionaryRef csfdeprops; char flatTarget[PATH_MAX]; OSKextLogSpec warnLogSpec; OSKextLogSpec errLogSpec; CFArrayRef boots; DASessionRef dasession; BRBlessStyle blessSpec; BRUpdateOpts_t opts;
Boolean doRPS, doMisc, doBooters; Boolean doSanitize, cleanOnceDir; Boolean customSource, customDest; Boolean useOnceDir; Boolean useStagingDir; Boolean skipFDECopy;
int bootIdx; enum bootReversions changestate; char bsdname[DEVMAXPATHSIZE]; DADiskRef curBoot; char curMount[MNAMELEN]; int curbootfd; char dstdir[PATH_MAX]; char efidst[PATH_MAX], ofdst[PATH_MAX];
Boolean onAPM; Boolean detectedRecovery; };
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}
#define OLDEXT ".old"
#define NEWEXT ".new"
#define SCALE_2xEXT "_2x"
#define CONTENTEXT ".contentDetails"
#define kBRRootUUIDFile ".root_uuid"
#define kBRBootOnceDir "/com.apple.boot.once"
#define BOOTPLIST_NAME "com.apple.Boot.plist"
#define BOOTPLIST_APM_NAME "com.apple.boot.plist"
static int mountBoot(struct updatingVol *up);
static void unmountBoot(struct updatingVol *up);
static int ucopyRPS(struct updatingVol *s); static int ucopyMisc(struct updatingVol *s); static int ucopyBooters(struct updatingVol *s); static int moveLabels(struct updatingVol *s); static int nukeBRLabels(struct updatingVol *s); static int activateBooters(struct updatingVol *s); static int activateRPS(struct updatingVol *s); static int activateMisc(struct updatingVol *s); static int nukeFallbacks(struct updatingVol *s);
static int eraseRPS(struct updatingVol *up, char *toErase);
static int addHostVolInfo(struct updatingVol *up, CFURLRef hostVol,
CFDictionaryRef bootPrefOverrides, CFURLRef targetStr,
CFStringRef pickerLabel);
static CFStringRef copy_kcsuffix(void);
static int revertState(struct updatingVol *up);
#define pathcpy(dst, src) do { \
Boolean useErrno = (errno == 0); \
if (useErrno) errno = ENAMETOOLONG; \
if (strlcpy(dst, src, PATH_MAX) >= PATH_MAX) goto finish; \
if (useErrno) errno = 0; \
} while(0)
#define pathcat(dst, src) do { \
Boolean useErrno = (errno == 0); \
if (useErrno) errno = ENAMETOOLONG; \
if (strlcat(dst, src, PATH_MAX) >= PATH_MAX) goto finish; \
if (useErrno) errno = 0; \
} while(0)
#define makebootpath(path, rpath) do { \
pathcpy(path, up->curMount); \
if (up->useOnceDir) { \
pathcat(path, kBRBootOnceDir); \
} \
if (up->useOnceDir || up->flatTarget[0]) { \
pathcat(path, up->flatTarget); \
\
pathcat(path, "/"); \
pathcat(path, basename(rpath)); \
} else { \
pathcat(path, rpath); \
} \
} while(0)
#define PATHCPYcont(dst, src) do { \
if (strlcpy(dst, src, PATH_MAX) >= PATH_MAX) continue; \
} while(0)
#define PATHCATcont(dst, src) do { \
if (strlcat(dst, src, PATH_MAX) >= PATH_MAX) continue; \
} while(0)
#define PATHCPYbreak(dst, src) do { \
if (strlcpy(dst, src, PATH_MAX) >= PATH_MAX) break; \
} while(0)
#define PATHCATbreak(dst, src) do { \
if (strlcat(dst, src, PATH_MAX) >= PATH_MAX) break; \
} while(0)
#define LOGERRxlate(up, ctx1, ctx2, errval) do { \
char *c2cpy = ctx2, ctx[256]; \
if (ctx2 != NULL) { \
snprintf(ctx, sizeof(ctx), "%s: %s", ctx1, c2cpy); \
} else { \
snprintf(ctx, sizeof(ctx), "%s", ctx1); \
} \
\
if (errval == -1) errval = errno; \
OSKextLog( NULL, up->errLogSpec, \
"%s: %s", ctx, strerror(errval)); \
} while(0)
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;
}
#define MOBILEBACKUPS_DIR "/.MobileBackups"
#define MDS_BULWARK "/.metadata_never_index"
#define MDS_DIR "/.Spotlight-V100"
#define FSEVENTS_BULWARK "/.fseventsd/no_log"
#define FSEVENTS_DIR "/.fseventsd"
#define NETBOOT_SHADOW "/.com.apple.NetBootX/shadowfile"
static int
sanitizeBoot(struct updatingVol *up)
{
int lastErrno = 0; int fd;
struct statfs sfs;
char bloatp[PATH_MAX], blockp[PATH_MAX];
Boolean blockMissing = true;
struct stat sb;
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Removing unnecessary bloat.");
if ((fstatfs(up->curbootfd, &sfs) == 0) &&
(sfs.f_blocks * sfs.f_bsize > 1ULL<<32)) {
goto finish;
}
if ((fstat(up->curbootfd, &sb) == 0) &&
(sb.st_uid != UID_ROOT || sb.st_gid != GID_WHEEL)) {
if (fchown(up->curbootfd, UID_ROOT, GID_WHEEL) == -1) {
lastErrno = errno;
}
}
makebootpath(bloatp, MOBILEBACKUPS_DIR);
if (0 == (stat(bloatp, &sb))) {
if (sdeepunlink(up->curbootfd, bloatp) == -1) {
lastErrno = errno;
}
}
makebootpath(bloatp, NETBOOT_SHADOW);
if (0 == (stat(bloatp, &sb))) {
if (sdeepunlink(up->curbootfd, bloatp) == -1) {
lastErrno = errno;
}
}
makebootpath(blockp, MDS_BULWARK);
if (-1 == stat(blockp, &sb) && errno == ENOENT) {
fd = sopen(up->curbootfd, blockp, O_CREAT, kCacheFileMode);
if (fd == -1) {
lastErrno = errno;
} else {
close(fd);
}
}
makebootpath(bloatp, MDS_DIR);
if (0 == (stat(bloatp, &sb))) {
if (sdeepunlink(up->curbootfd, bloatp) == -1) {
lastErrno = errno;
}
}
makebootpath(bloatp, FSEVENTS_DIR);
makebootpath(blockp, FSEVENTS_BULWARK);
if (0 == (stat(bloatp, &sb))) {
if (-1 == stat(blockp, &sb) && errno == ENOENT) {
if (sdeepunlink(up->curbootfd, bloatp) == -1) {
lastErrno = errno;
}
} else {
blockMissing = false;
}
}
if (blockMissing) {
if (sdeepmkdir(up->curbootfd, bloatp, kCacheDirMode) == -1) {
lastErrno = errno;
}
fd = sopen(up->curbootfd, blockp, O_CREAT, kCacheFileMode);
if (fd == -1) {
lastErrno = errno;
} else {
close(fd);
}
}
finish:
if (lastErrno) {
OSKextLog(NULL, up->warnLogSpec, "sanitizeBoot(): Warning: %s",
strerror(lastErrno));
}
return lastErrno;
}
static void
checkBootContents(struct updatingVol *up)
{
unsigned i;
char srcpath[PATH_MAX], dstpath[PATH_MAX];
struct stat sb;
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Looking for missing files.");
if (notBRDefault(up->curMount, NULL)) {
up->doRPS = up->doBooters = up->doMisc = true;
goto finish;
}
if (!up->doMisc) {
for (i = 0; i < up->caches->nmisc; i++) {
pathcpy(srcpath, up->caches->root);
pathcat(srcpath, up->caches->miscpaths[i].rpath);
makebootpath(dstpath, up->caches->miscpaths[i].rpath);
if (stat(srcpath, &sb) == 0) {
if (stat(dstpath, &sb) != 0 && errno == ENOENT) {
up->doMisc = true;
OSKextLog(nil,kOSKextLogFileAccessFlag|kOSKextLogBasicLevel,
"Helper partition missing misc files, forcing update");
break;
}
}
}
}
if (!up->doBooters) {
if (up->caches->efibooter.rpath[0]) {
makebootpath(dstpath, up->caches->efibooter.rpath);
if (stat(dstpath, &sb) != 0 && errno == ENOENT) {
up->doBooters = true;
OSKextLog(NULL, kOSKextLogFileAccessFlag|kOSKextLogBasicLevel,
"Helper partition missing EFI booter, forcing update");
goto finish;
}
}
if (up->caches->ofbooter.rpath[0]) {
makebootpath(dstpath, up->caches->ofbooter.rpath);
if (stat(dstpath, &sb) != 0 && errno == ENOENT) {
up->doBooters = true;
OSKextLog(NULL, kOSKextLogFileAccessFlag|kOSKextLogBasicLevel,
"Helper partition missing OF booter, forcing update");
goto finish;
}
}
}
finish:
return;
}
static int
updateBootHelpers(struct updatingVol *up)
{
int errnum, result = 0;
struct stat sb;
CFIndex bootcount, bootupdates = 0;
if (up->curbootfd != -1) {
close(up->curbootfd);
up->curbootfd = -1;
}
if ((result = fstat(up->caches->cachefd, &sb))) {
OSKextLog(NULL, up->errLogSpec, "fstat(cachefd): %s", strerror(errno));
goto finish;
}
bootcount = CFArrayGetCount(up->boots);
for (up->bootIdx = 0; up->bootIdx < bootcount; up->bootIdx++) {
char path[PATH_MAX];
up->changestate = nothingSerious; if ((errnum = mountBoot(up))) { result = errnum; goto bootfail;
}
if (up->doSanitize) {
(void)sanitizeBoot(up);
}
if (up->cleanOnceDir &&
strlcpy(path, up->curMount, PATH_MAX) < PATH_MAX &&
strlcat(path, kBRBootOnceDir, PATH_MAX) < PATH_MAX &&
0 == stat(path, &sb)) {
(void)sdeepunlink(up->curbootfd, path);
}
checkBootContents(up);
if (up->customSource && !up->customDest) {
markNotBRDefault(up->curbootfd, up->curMount, NULL, true);
}
if (up->doRPS && (result = ucopyRPS(up))) {
goto bootfail; }
if (up->doMisc) {
(void)ucopyMisc(up); }
if (up->opts & kBRUExpectUpToDate) {
if ((result = moveLabels(up))) {
goto bootfail;
}
} else {
if ((result = nukeBRLabels(up))) {
goto bootfail;
}
}
if (up->doBooters && (result = ucopyBooters(up))) {
goto bootfail; }
if (up->doBooters && (result = activateBooters(up))) { goto bootfail;
}
if (up->doRPS && (result = activateRPS(up))) { goto bootfail;
}
if ((result = activateMisc(up))) {
goto bootfail; }
if (!up->customSource && !up->customDest) {
markNotBRDefault(up->curbootfd, up->curMount, NULL, false);
}
up->changestate = nothingSerious;
bootupdates++; OSKextLog(NULL,kOSKextLogFileAccessFlag|((up->opts & kBRUExpectUpToDate)
? kOSKextLogWarningLevel : kOSKextLogBasicLevel),
"Successfully updated %s%s.", up->bsdname, up->flatTarget);
bootfail:
if (up->changestate!=nothingSerious && !(up->opts&kBRUHelpersOptional)){
OSKextLog(NULL, up->errLogSpec,
"Error updating helper partition %s, state %d: %s.",
up->bsdname, up->changestate,
bootReversionsStrings[up->changestate]);
}
(void)revertState(up);
if (nukeFallbacks(up)) {
OSKextLog(NULL, up->errLogSpec, "Warning: %s%s may be untidy.",
up->bsdname, up->flatTarget);
}
unmountBoot(up); }
if (bootupdates != bootcount && !(up->opts&kBRUHelpersOptional)) {
OSKextLog(NULL, up->errLogSpec, "Failed to update helper partition%s.",
bootcount - bootupdates == 1 ? "" : "s");
if (result == 0) {
result = ELAST + 1;
}
goto finish;
}
finish:
return result;
}
int
checkRebuildAllCaches(struct bootCaches *caches,
int oodLogSpec,
Boolean invalidateKextCache,
Boolean earlyBootCheckUpdate,
Boolean startupKextsOk,
Boolean buildImmutableKernel,
Boolean *anyUpdates)
{
int opres, result = ELAST + 1; struct stat sb;
Boolean didUpdate = false;
#if DEV_KERNEL_SUPPORT
char * suffixPtr = NULL; char * tmpKernelPath = NULL; cachedPath *extraPaths = NULL; #endif
char imk_path[PATH_MAX] = {};
if (caches == NULL) goto finish;
if ((opres = fstat(caches->cachefd, &sb))) {
result = opres; goto finish;
}
OSKextLog(NULL, kOSKextLogProgressLevel | kOSKextLogArchiveFlag,
"Ensuring %s's caches are up to date.", caches->root);
if (buildImmutableKernel) {
if (!translatePrelinkedToImmutablePath(caches->kext_boot_cache_file->rpath,
imk_path, sizeof(imk_path))) {
OSKextLog( NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"Cannot construct immutablekernel path from \"%s\".",
caches->kext_boot_cache_file->rpath);
result = EX_SOFTWARE;
goto finish;
}
}
if (invalidateKextCache ||
check_kext_boot_cache_file(caches,
caches->kext_boot_cache_file->rpath,
caches->kernelpath, imk_path)) {
OSKextLog(nil, oodLogSpec, "rebuilding %s%s",
caches->root ? caches->root : "/",
caches->kext_boot_cache_file->rpath);
if ((opres = rebuild_kext_boot_cache_file(caches,
caches->kext_boot_cache_file->rpath,
caches->kernelpath,
startupKextsOk,
buildImmutableKernel))) {
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Error %d rebuilding %s", result,
caches->kext_boot_cache_file->rpath);
result = opres; goto finish;
} else {
didUpdate = true;
}
} else {
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogArchiveFlag,
"Primary kext cache does not need update.");
}
#if DEV_KERNEL_SUPPORT
if (caches->extraKernelCachePaths) {
int i;
int numExtraPaths;
if ((opres = getExtraKernelCachePaths(caches, &extraPaths, &numExtraPaths, false)) != 0) {
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Error %d getting extra kernelcache paths.", opres);
result = opres; goto finish;
}
tmpKernelPath = malloc(PATH_MAX);
if (tmpKernelPath) {
for (i = 0; i < caches->nekcp; i++) {
cachedPath *cp = &extraPaths[i];
SAFE_FREE_NULL(suffixPtr);
suffixPtr = getPathExtension(cp->rpath);
if (suffixPtr == NULL)
continue;
if (strlcpy(tmpKernelPath, caches->kernelpath, PATH_MAX) >= PATH_MAX)
continue;
if (strlcat(tmpKernelPath, ".", PATH_MAX) >= PATH_MAX)
continue;
if (strlcat(tmpKernelPath, suffixPtr, PATH_MAX) >= PATH_MAX)
continue;
if (buildImmutableKernel) {
if (!translatePrelinkedToImmutablePath(cp->rpath, imk_path, sizeof(imk_path))) {
OSKextLog( NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"Cannot construct immutablekernel path from \"%s\".",
cp->rpath);
result = EX_SOFTWARE;
goto finish;
}
}
if (invalidateKextCache ||
check_kext_boot_cache_file(caches, cp->rpath, tmpKernelPath, imk_path)) {
if ((opres = rebuild_kext_boot_cache_file(caches,
cp->rpath,
tmpKernelPath,
startupKextsOk,
buildImmutableKernel))){
result = opres; goto finish;
}
}
} }
}
#endif
if (check_csfde(caches)) {
OSKextLog(NULL,oodLogSpec,"rebuilding %s",caches->erpropcache->rpath);
if ((opres = rebuild_csfde_cache(caches))) {
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Error %d rebuilding %s", result,
caches->erpropcache->rpath);
result = opres; goto finish;
} else {
didUpdate = true;
}
} else {
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogArchiveFlag,
"CSFDE property cache does not need update.");
}
if (!earlyBootCheckUpdate && check_loccache(caches)) {
OSKextLog(NULL,oodLogSpec,"rebuilding %s",caches->efiloccache->rpath);
if ((result = rebuild_loccache(caches))) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogArchiveFlag,
"Warning: Error rebuilding %s", caches->efiloccache->rpath);
} else {
didUpdate = true;
}
} else {
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogArchiveFlag,
"Localized EFI Login resources do not need update.");
}
result = 0;
if (anyUpdates) *anyUpdates = didUpdate;
finish:
#if DEV_KERNEL_SUPPORT
SAFE_FREE(extraPaths);
SAFE_FREE(tmpKernelPath);
SAFE_FREE(suffixPtr);
#endif
return result;
}
#define BOOTCOUNT 1
static int
initContext(struct updatingVol *up, CFURLRef srcVol, CFStringRef helperBSDName,
BRUpdateOpts_t opts)
{
int opres, result = ELAST + 1; const void *values[BOOTCOUNT] = { helperBSDName };
struct statfs fs = { };
bzero(up, sizeof(struct updatingVol));
up->curbootfd = -1;
up->warnLogSpec = kOSKextLogArchiveFlag | kOSKextLogWarningLevel;
up->errLogSpec = kOSKextLogArchiveFlag | kOSKextLogErrorLevel;
up->blessSpec = kBRBlessFSDefault;
up->opts = opts;
if (!CFURLGetFileSystemRepresentation(srcVol, true,
(UInt8 *)up->srcRoot,sizeof(up->srcRoot))){
OSKextLogStringError(NULL);
result = ENOMEM; goto finish;
}
if (statfs(up->srcRoot, &fs) < 0) {
OSKextLog(NULL, up->errLogSpec,
"Error initializing volume update context: %d (%s)",
errno, strerror(errno));
result = errno; goto finish;
}
if ((fs.f_flags & (MNT_RDONLY | MNT_SNAPSHOT)) == (MNT_RDONLY | MNT_SNAPSHOT) &&
strcmp(fs.f_fstypename, "apfs") == 0)
{
result = EROFS; goto finish;
}
if ((opts & kBRUEarlyBoot) == 0) {
if ((opres = takeVolumeForPath(up->srcRoot))) { result = opres; goto finish;
}
}
if (!(up->caches = readBootCaches(up->srcRoot, opts))) {
result = errno ? errno : ELAST + 1;
goto finish;
}
if ((up->dasession = DASessionCreate(nil))) {
DASessionScheduleWithRunLoop(up->dasession, CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode);
} else {
OSKextLog(NULL, up->warnLogSpec, "Warning: proceeding w/o DiskArb");
}
if (helperBSDName) {
up->boots = CFArrayCreate(nil,values,BOOTCOUNT,&kCFTypeArrayCallBacks);
}
up->useStagingDir = (opts & kBRUseStagingDir) != 0;
result = 0;
finish:
return result;
}
static void
releaseContext(struct updatingVol *up, int status)
{
if (up->curBoot) CFRelease(up->curBoot);
if (up->curbootfd != -1) {
close(up->curbootfd);
up->curbootfd = -1;
}
if (up->dasession) {
DASessionUnscheduleFromRunLoop(up->dasession, CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode);
CFRelease(up->dasession);
up->dasession = NULL;
}
if (up->boots) CFRelease(up->boots);
if (up->csfdeprops) CFRelease(up->csfdeprops);
if (up->bpoverrides) CFRelease(up->bpoverrides);
if (up->caches) destroyCaches(up->caches);
putVolumeForPath(up->srcRoot, status);
}
static void
addDictOverride(const void *key, const void *value, void *ctx)
{
CFMutableDictionaryRef tgtDict = (CFMutableDictionaryRef)ctx;
if (CFDictionaryContainsKey(tgtDict, key))
CFDictionaryRemoveValue(tgtDict, key);
CFDictionaryAddValue(tgtDict, key, value);
}
static CFDataRef
createBootPrefData(struct updatingVol *up, uuid_string_t root_uuid,
CFDictionaryRef bootPrefOverrides)
{
CFDataRef rval = NULL;
char srcpath[PATH_MAX];
int fd = -1;
void *buf = NULL;
CFDataRef data = NULL;
CFMutableDictionaryRef pldict = NULL;
CFStringRef UUIDStr = NULL;
CFStringRef kernPathStr = NULL;
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"creating com.apple.Boot.plist data with UUID %s.",
root_uuid);
do {
struct stat sb;
PATHCPYcont(srcpath, up->caches->root);
PATHCATcont(srcpath, up->caches->bootconfig->rpath);
if (-1 == (fd=sopen(up->caches->cachefd,srcpath,O_RDONLY,0)))
break;
if (fstat(fd, &sb)) break;
if (sb.st_size > UINT_MAX || sb.st_size > LONG_MAX) break;
if (!(buf = malloc((size_t)sb.st_size))) break;
if (read(fd, buf, (size_t)sb.st_size) != sb.st_size)break;
if (!(data = CFDataCreate(nil, buf, (long)sb.st_size)))
break;
pldict =(CFMutableDictionaryRef)
CFPropertyListCreateWithData(nil,
data,
kCFPropertyListMutableContainers,
NULL,
NULL);
} while(0);
errno = 0;
if (!pldict || CFGetTypeID(pldict)!=CFDictionaryGetTypeID()) {
if (pldict) CFRelease(pldict); pldict = CFDictionaryCreateMutable(nil, 1,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!pldict) goto finish;
}
errno = 0;
UUIDStr = CFStringCreateWithCString(nil,root_uuid,kCFStringEncodingASCII);
if (!UUIDStr) goto finish;
CFDictionarySetValue(pldict, CFSTR(kRootUUIDKey), UUIDStr);
if (!CFEqual(CFDictionaryGetValue(pldict,CFSTR(kRootUUIDKey)), UUIDStr))
goto finish;
if (up->flatTarget[0] || up->useOnceDir) {
char kpath[PATH_MAX] = "";
if (up->useOnceDir) {
pathcat(kpath, kBRBootOnceDir);
}
pathcat(kpath, up->flatTarget);
pathcat(kpath, "/");
if (up->caches->prefer_fileset && up->caches->fileset_bootkc) {
pathcat(kpath, basename(up->caches->fileset_bootkc->rpath));
} else if (up->caches->readonly_kext_boot_cache_file) {
pathcat(kpath, basename(up->caches->readonly_kext_boot_cache_file->rpath));
} else {
pathcat(kpath, basename(up->caches->kext_boot_cache_file->rpath));
}
kernPathStr = CFStringCreateWithFileSystemRepresentation(nil, kpath);
if (!kernPathStr) goto finish;
CFDictionarySetValue(pldict, CFSTR(kKernelCacheKey), kernPathStr);
}
if (bootPrefOverrides) {
CFDictionaryApplyFunction(bootPrefOverrides,addDictOverride,pldict);
}
rval = CFPropertyListCreateData(nil, pldict, kCFPropertyListXMLFormat_v1_0,
0 , NULL);
finish:
if (kernPathStr) CFRelease(kernPathStr);
if (UUIDStr) CFRelease(UUIDStr);
if (pldict) CFRelease(pldict);
if (data) CFRelease(data);
if (buf) free(buf);
if (fd != -1) close(fd);
return rval;
}
static bool
noUUIDStampsUpToDate(CFURLRef volURL)
{
Boolean altStampsUTD = false;
char volRoot[PATH_MAX];
struct bootCaches *caches = NULL;
if (!CFURLGetFileSystemRepresentation(volURL, true,
(UInt8*)volRoot, sizeof(volRoot))) {
OSKextLogStringError(NULL);
goto finish;
}
caches = readBootCaches(volRoot, kBRAnyBootStamps);
if (!caches) {
goto finish;
}
altStampsUTD = (false == needUpdates(caches, kBROptsNone,
NULL, NULL, NULL,
kOSKextLogGeneralFlag | kOSKextLogDetailLevel));
finish:
if (caches) destroyCaches(caches);
return altStampsUTD;
}
#define BRDBG_OOD_HANG_BOOT_F "/var/db/.BRHangBootOnOODCaches"
#define BRDBG_HANG_MSG PRODUCT_NAME ": " BRDBG_OOD_HANG_BOOT_F \
" -> hanging on out of date caches"
#define BRDBG_CONS_MSG "[via /dev/console] " BRDBG_HANG_MSG "\n"
int
checkUpdateCachesAndBoots(CFURLRef volumeURL, BRUpdateOpts_t opts)
{
int opres, result = ELAST + 1; OSKextLogSpec oodLogSpec = kOSKextLogGeneralFlag | kOSKextLogBasicLevel;
Boolean earlyBootCheckUpdate; Boolean anyCacheUpdates = false; Boolean doAny = false, cachesUpToDate = false, doMisc;
Boolean loggedOOD = false;
struct updatingVol up = { };
up.curbootfd = -1;
earlyBootCheckUpdate = ((opts & kBRUExpectUpToDate) && (opts & kBRUEarlyBoot));
if ((opres = initContext(&up, volumeURL, NULL, opts))) {
char *bcmsg = NULL;
CFArrayRef helpers;
switch (opres) { case ENOENT: bcmsg = "no " kBootCachesPath; break;
case EFTYPE: bcmsg = "unrecognized " kBootCachesPath; break;
case EROFS: bcmsg = "Not updating APFS snapshot"; break;
default: break;
}
if ((opts & kBRUForceUpdateHelpers) &&
(helpers = BRCopyActiveBootPartitions(volumeURL))) {
OSKextLog(NULL,up.errLogSpec,"%s: %s; aborting",up.srcRoot,bcmsg);
CFRelease(helpers);
result = opres; goto finish;
} else if (bcmsg) {
OSKextLog(NULL, oodLogSpec, "%s: %s; skipping",up.srcRoot,bcmsg);
result = 0; goto finish;
} else {
OSKextLog(NULL, up.errLogSpec, "%s: error %d reading "
kBootCachesPath, up.srcRoot, opres);
result = opres; goto finish;
}
}
if (opts & kBRUExpectUpToDate) {
oodLogSpec = up.errLogSpec;
}
if ((opres = checkRebuildAllCaches(up.caches, oodLogSpec,
(opts & kBRUInvalidateKextcache),
earlyBootCheckUpdate,
true ,
(opts & kBRUImmutableKernel),
&anyCacheUpdates))) {
result = opres; goto finish; }
cachesUpToDate = true;
if (opts & kBRUCachesOnly) {
goto doneUpdatingHelpers;
}
if (!hasBootRootBoots(up.caches, &up.boots, NULL, &up.onAPM)) {
OSKextLog(NULL, kOSKextLogBasicLevel | kOSKextLogFileAccessFlag,
"%s: no supported helper partitions to update.", up.srcRoot);
goto doneUpdatingHelpers; }
up.doSanitize = true;
if (needUpdates(up.caches, opts, &up.doRPS, &up.doBooters, &doMisc,
oodLogSpec)) {
loggedOOD = true;
}
if (!earlyBootCheckUpdate) {
up.doMisc = doMisc;
}
doAny = up.doRPS || up.doBooters || up.doMisc;
if (doAny && earlyBootCheckUpdate && noUUIDStampsUpToDate(volumeURL)) {
doAny = false;
}
#ifdef BRDBG_OOD_HANG_BOOT_F
if (doAny && earlyBootCheckUpdate) {
struct stat sb;
int consfd = open(_PATH_CONSOLE, O_WRONLY|O_APPEND);
while (stat(BRDBG_OOD_HANG_BOOT_F, &sb) == 0) {
OSKextLog(NULL, up.errLogSpec, BRDBG_HANG_MSG);
if (consfd > -1)
write(consfd, BRDBG_CONS_MSG, sizeof(BRDBG_CONS_MSG)-1);
sleep(30);
}
}
#endif // BRDBG_OOD_HANG_BOOT_F
if (opts & kBRUForceUpdateHelpers) {
up.doRPS = up.doBooters = up.doMisc = true;
up.cleanOnceDir = true;
} else if (!doAny) {
OSKextLogSpec utdlogSpec = kOSKextLogFileAccessFlag;
if (loggedOOD) {
utdlogSpec |= kOSKextLogWarningLevel;
} else {
utdlogSpec |= kOSKextLogBasicLevel;
}
OSKextLog(NULL, utdlogSpec, "%s: helper partitions %s up to date.",
up.srcRoot,
(loggedOOD|earlyBootCheckUpdate) ? "sufficiently":"appear");
goto doneUpdatingHelpers;
}
if ((opres = addHostVolInfo(&up, volumeURL, NULL, NULL, NULL))) {
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"%s: error %d extracting volume info.", up.srcRoot, opres);
result = opres; goto finish;
}
strlcpy(up.host_uuid, up.caches->fsys_uuid, sizeof(up.host_uuid));
if (up.caches->csfde_uuid) {
opres = copyCSFDEInfo(up.caches->csfde_uuid, &up.csfdeprops, NULL);
if (opres) {
result = opres; goto finish; }
}
if ((opres = updateBootHelpers(&up))) {
if (opres == ENOSPC && strcmp(up.caches->root, "/") == 0) {
OSKextLog(NULL, kOSKextLogWarningLevel,
"Out of space in helper for '/', trying w/o -all-loaded");
if ((opres = checkRebuildAllCaches(up.caches, oodLogSpec,
true ,
earlyBootCheckUpdate,
false ,
(opts & kBRUImmutableKernel),
&anyCacheUpdates))) {
result = opres; goto finish; }
opres = updateBootHelpers(&up);
}
if (opres) {
result = opres; goto finish; }
}
if ((opres = updateStamps(up.caches, kBCStampsApplyTimes))) {
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"%s: could not update bootstamps.", up.srcRoot);
result = opres; goto finish;
}
doneUpdatingHelpers:
result = 0;
if ((opts & kBRUExpectUpToDate) && (anyCacheUpdates || doAny)) {
if (earlyBootCheckUpdate) {
OSKextLog(NULL, oodLogSpec, "%s updated critical boot files, "
"requesting launchd reboot", PRODUCT_NAME);
}
result = EX_OSFILE;
}
finish:
if ((up.opts & kBRUHelpersOptional) && cachesUpToDate) {
result = 0;
}
if (result && result != EX_OSFILE) {
result = getExitValueFor(result);
}
releaseContext(&up, result);
return result;
}
#define kBRCheckLogSpec (kOSKextLogArchiveFlag | kOSKextLogProgressLevel)
OSStatus
BRUpdateBootFiles(CFURLRef volURL, Boolean force)
{
if (!volURL)
return EINVAL;
return checkUpdateCachesAndBoots(volURL, force?kBRUForceUpdateHelpers:0);
}
static int
addHostVolInfo(struct updatingVol *up, CFURLRef hostVol,
CFDictionaryRef bootPrefOverrides, CFURLRef targetStr,
CFStringRef pickerLabel)
{
OSStatus result = EOVERFLOW; OSStatus errnum; uuid_t host_uuidbytes;
CFStringRef csUUIDStr = NULL;
char hostroot[PATH_MAX];
up->flatTarget[0] = '\0';
if (targetStr && up->useStagingDir) {
result = EINVAL; goto finish;
}
if (targetStr) {
char targetdir[PATH_MAX] = "", *slash;
if (!CFURLGetFileSystemRepresentation(targetStr, true ,
(UInt8*)targetdir, PATH_MAX)) {
result = EINVAL; goto finish;
}
slash = targetdir;
while (*slash == '/') slash++;
if (*slash == '\0') {
result = EINVAL; goto finish;
}
if (targetdir[0] != '/') { pathcat(up->flatTarget, "/");
}
pathcat(up->flatTarget, targetdir);
}
if (!CFURLGetFileSystemRepresentation(hostVol, true ,
(UInt8*)hostroot, PATH_MAX)) {
result = ENOMEM; goto finish;
}
if ((errnum=copyVolumeInfo(hostroot,&host_uuidbytes,&csUUIDStr,NULL,NULL))){
result = errnum; goto finish;
}
uuid_unparse_upper(host_uuidbytes, up->host_uuid);
up->bpoverrides = bootPrefOverrides;
if (up->bpoverrides) {
CFRetain(up->bpoverrides); }
if (csUUIDStr) {
if ((errnum = copyCSFDEInfo(csUUIDStr, &up->csfdeprops, NULL))) {
result = errnum; goto finish;
}
}
if (pickerLabel) {
if (!CFStringGetFileSystemRepresentation(pickerLabel,
up->caches->defLabel, PATH_MAX)) {
result = EINVAL; goto finish;
}
}
result = 0;
finish:
if (csUUIDStr) CFRelease(csUUIDStr);
return result;
}
OSStatus
BRCopyBootFilesToDir(CFURLRef srcVol,
CFURLRef initialRoot,
CFDictionaryRef bootPrefOverrides,
CFStringRef targetBSDName,
CFURLRef targetDir,
BRBlessStyle blessSpec,
CFStringRef pickerLabel,
BRUpdateOpts_t opts)
{
OSStatus result = ELAST + 1; OSStatus errnum;
CFArrayRef helpers;
CFStringRef firstHelper;
Boolean doUpdateStamps = false;
Boolean doFDEResouceCopy = false;
struct updatingVol up = { };
up.curbootfd = -1;
if (!srcVol || !initialRoot || !targetBSDName) {
result = EINVAL; goto finish;
}
errnum = initContext(&up, srcVol, targetBSDName, opts);
if (errnum == EROFS) {
result = 0; goto finish;
} else if (errnum) {
result = errnum; goto finish;
}
up.blessSpec = blessSpec;
up.useOnceDir = ((blessSpec & kBRBlessOnce) &&
(blessSpec & kBRBlessFSDefault) == 0) || (up.useStagingDir);
if (up.useOnceDir || targetDir) {
up.customDest = true;
} else if (CFEqual(srcVol, initialRoot) == false) {
up.customSource = true;
} else if ((helpers = BRCopyActiveBootPartitions(initialRoot))) {
if ((CFArrayGetCount(helpers)) != 1 ||
(firstHelper = CFArrayGetValueAtIndex(helpers, 0)) == NULL ||
CFEqual(targetBSDName, firstHelper) == false) {
up.customSource = true;
} else {
doUpdateStamps = true;
}
CFRelease(helpers);
} else {
doUpdateStamps = true;
}
if (opts & kBRAnyBootStamps) {
doUpdateStamps = true;
}
up.doSanitize = doUpdateStamps;
if (opts & kBROptsNoFDEResCopy) {
doFDEResouceCopy = true;
}
up.skipFDECopy = doFDEResouceCopy;
errnum = checkRebuildAllCaches(up.caches, kBRCheckLogSpec,
(opts & kBRUInvalidateKextcache),
(opts & kBRUExpectUpToDate) && (opts & kBRUEarlyBoot),
true,
false,
NULL);
if (errnum) {
result = errnum; goto finish;
}
if (doUpdateStamps) {
(void)needUpdates(up.caches, kBROptsNone, NULL, NULL, NULL,
kOSKextLogGeneralFlag | kOSKextLogProgressLevel);
}
errnum = addHostVolInfo(&up, initialRoot, bootPrefOverrides,
targetDir, pickerLabel);
if (errnum) {
result = errnum; goto finish;
}
up.doRPS = up.doBooters = up.doMisc = true;
up.cleanOnceDir = true;
if ((errnum = updateBootHelpers(&up))) {
result = errnum; goto finish;
}
if (doUpdateStamps) {
if (opts & kBRAnyBootStamps) {
char cachedir[PATH_MAX];
pathcpy(cachedir, up.caches->root);
pathcat(cachedir, kTSCacheDir);
(void)sdeepunlink(up.caches->cachefd, cachedir);
}
errnum = updateStamps(up.caches, kBCStampsApplyTimes);
if (errnum) {
result = errnum; goto finish;
}
}
if (up.customSource && !up.customDest) {
(void)taintDefaultStamps(targetBSDName); }
result = 0;
finish:
releaseContext(&up, result);
return result;
}
OSStatus
BRCopyBootFiles(CFURLRef srcVol,
CFURLRef initialRoot,
CFStringRef helperBSDName,
CFDictionaryRef bootPrefOverrides)
{
return BRCopyBootFilesToDir(srcVol, initialRoot, bootPrefOverrides,
helperBSDName, NULL ,
kBRBlessFSDefault, NULL ,
kBROptsNone);
}
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];
int rval = ELAST + 1, status;
struct stat r, p, s;
Boolean haveR = false, haveP = false, haveS = false;
char *prevp = NULL, *curp = NULL, *nextp = NULL;
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); haveR = (status == 0);
status = stat(ppath, &p);
haveP = (status == 0);
status = stat(spath, &s);
haveS = (status == 0);
if (haveR && haveP && haveS) { OSKextLog(NULL, up->warnLogSpec,
"Warning: all of R,P,S exist: picking 'R'; destroying 'P'.");
curp = rpath; nextp = ppath; prevp = spath;
if ((rval = eraseRPS(up, nextp)))
goto finish;
} else if (haveR && haveP) { curp = ppath; nextp = spath; prevp = rpath;
} else if (haveR && haveS) {
curp = rpath; nextp = ppath; prevp = spath;
} else if (haveP && haveS) {
curp = spath; nextp = rpath; prevp = ppath;
} else if (haveR) { curp = rpath; nextp = ppath; prevp = spath;
} else if (haveP) {
curp = ppath; nextp = spath; prevp = rpath;
} else if (haveS) {
curp = spath; nextp = rpath; prevp = ppath;
} else { 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(NULL, up->errLogSpec,
"%s - strlcpy or cat failed - >= PATH_MAX", __FUNCTION__);
}
return rval;
}
static int
sBLSetBootInfo(struct updatingVol *up, uint64_t newvinfo[8])
{
int result, fd = -1, isAPFS = 0;
result = schdir(up->curbootfd, up->curMount, &fd);
if (result) goto finish;
if (BLIsMountAPFS(NULL, up->curMount, &isAPFS) != 0) {
result = EINVAL;
goto finish;
}
if (isAPFS) {
uint64_t vinfo[2];
result = BLGetAPFSBlessData(NULL, ".", vinfo);
if (result) goto finish;
vinfo[0] = newvinfo[kEFIBooterIdx];
vinfo[1] = newvinfo[kSystemFolderIdx];
result = BLSetAPFSBlessData(NULL, ".", vinfo);
} else {
uint32_t vinfo[8];
result = BLGetVolumeFinderInfo(NULL, ".", vinfo);
if (result) goto finish;
vinfo[kSystemFolderIdx] = (uint32_t)newvinfo[kSystemFolderIdx];
vinfo[kEFIBooterIdx] = (uint32_t)newvinfo[kEFIBooterIdx];
result = BLSetVolumeFinderInfo(NULL, ".", vinfo);
}
finish:
if (fd != -1)
(void)restoredir(fd);
return result;
}
static int
blessRecovery(struct updatingVol *up)
{
int result;
char path[PATH_MAX];
struct stat sb;
uint64_t vinfo[8] = { 0, };
result = ENAMETOOLONG;
pathcpy(path, up->curMount);
pathcat(path, "/" kRecoveryBootDir);
if (stat(path, &sb) == -1) {
result = errno;
goto finish;
}
vinfo[kSystemFolderIdx] = (uint32_t)sb.st_ino;
pathcat(path, "/");
pathcat(path, basename(up->caches->efibooter.rpath));
if (stat(path, &sb) == -1) {
result = errno;
goto finish;
}
vinfo[kEFIBooterIdx] = (uint32_t)sb.st_ino;
if ((result = sBLSetBootInfo(up, vinfo))) {
OSKextLog(NULL, up->warnLogSpec,
"Warning: found recovery booter but couldn't bless it.");
}
if ((result = fcntl(up->curbootfd, F_FULLFSYNC))) {
LOGERRxlate(up, "fcntl(helper, F_FULLFSYNC)", NULL, result);
goto finish;
}
finish:
return result;
}
#define RECERR(up, opres, warnmsg) do { \
if (opres == -1 && errno == ENOENT) { \
opres = 0; \
} \
if (opres) { \
if (warnmsg) { \
OSKextLog(NULL, up->warnLogSpec, warnmsg); \
} \
if (firstErr == 0) { \
OSKextLog(NULL, up->warnLogSpec, "capturing err %d / %d", \
opres, errno); \
firstErr = opres; \
if (firstErr == -1) firstErrno = errno; \
} \
} \
} while(0)
OSStatus
BREraseBootFiles(CFURLRef srcVolRoot, CFStringRef helperBSDName)
{
OSStatus result = ELAST + 1;
int opres, firstErrno, firstErr = 0;
char path[PATH_MAX], prevRPS[PATH_MAX], nextRPS[PATH_MAX];
struct stat sb;
uint64_t zerowords[8] = { 0, };
unsigned i;
struct updatingVol up = { }, *upp = &up;
up.curbootfd = -1;
if (!srcVolRoot || !helperBSDName) {
result = EINVAL; goto finish;
}
opres = initContext(&up, srcVolRoot, helperBSDName, kBROptsNone);
if (opres) {
result = opres; goto finish;
}
if ((opres = mountBoot(&up))) { result = opres; goto finish;
}
if ((blessRecovery(&up))) {
if ((opres = sBLSetBootInfo(&up, zerowords))) {
firstErr = opres;
OSKextLog(NULL, up.warnLogSpec,
"Warning: couldn't unbless %s", up.curMount);
}
}
opres = nukeBRLabels(&up);
RECERR(upp, opres,"Warning: trouble nuking (inactive?) Boot!=Root label.");
if (up.caches->ofbooter.rpath[0]) {
pathcpy(path, up.curMount);
pathcat(path, up.caches->ofbooter.rpath);
opres = sunlink(up.curbootfd, path);
RECERR(upp, opres, "couldn't unlink OF booter" );
}
if (up.caches->efibooter.rpath[0]) {
pathcpy(path, up.curMount);
pathcat(path, up.caches->efibooter.rpath);
opres = sunlink(up.curbootfd, path);
RECERR(upp, opres, "couldn't unlink EFI booter" );
}
opres = FindRPSDir(&up, prevRPS, up.dstdir, nextRPS);
if (opres == 0) {
opres = eraseRPS(&up, prevRPS);
RECERR(upp, opres, "Warning: trouble erasing R.");
opres = eraseRPS(&up, up.dstdir);
RECERR(upp, opres, "Warning: trouble erasing P.");
opres = eraseRPS(&up, nextRPS);
RECERR(upp, opres, "Warning: trouble erasing S.");
} else {
RECERR(upp, opres, "Warning: couldn't find RPS directories.");
}
for (i=0; i < up.caches->nmisc; i++) {
char *rpath = up.caches->miscpaths[i].rpath;
if (strlcpy(path, up.curMount, PATH_MAX) > PATH_MAX) continue;
if (strlcat(path, rpath, PATH_MAX) > PATH_MAX) continue;
opres = sdeepunlink(up.curbootfd, path);
RECERR(upp, opres, "error unlinking miscpath" );
}
pathcpy(path, up.curMount);
pathcat(path, kBRBootOnceDir);
if (0 == stat(path, &sb)) {
opres = sdeepunlink(up.curbootfd, path);
RECERR(upp, opres, "error unlinking" kBRBootOnceDir);
}
if (firstErr == -1) {
firstErr = firstErrno; }
result = firstErr;
finish:
unmountBoot(&up);
releaseContext(&up, result);
return result;
}
static int
revertState(struct updatingVol *up)
{
int rval = 0; char path[PATH_MAX], oldpath[PATH_MAX];
struct bootCaches *caches = up->caches;
Boolean doMisc;
struct stat sb;
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Rolling back any incomplete updates.");
switch (up->changestate) {
case activatedBooters:
pathcat(up->ofdst, OLDEXT);
pathcat(up->efidst, OLDEXT);
rval |= activateBooters(up);
case activatingEFIBooter:
case activatingOFBooter: case copiedBooters:
case copyingEFIBooter:
if (caches->efibooter.rpath[0]) {
makebootpath(path, caches->efibooter.rpath);
pathcpy(oldpath, path); pathcat(oldpath, OLDEXT);
if (stat(oldpath, &sb) == 0) {
(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);
if (stat(oldpath, &sb) == 0) {
(void)sunlink(up->curbootfd, path);
rval |= srename(up->curbootfd, oldpath, path);
}
}
case noLabel:
doMisc = up->doMisc;
up->doMisc = false;
rval |= activateMisc(up); up->doMisc = doMisc;
case nothingSerious:
break;
}
finish:
if (rval) {
OSKextLog(NULL, kOSKextLogErrorLevel,
"error rolling back incomplete updates.");
}
return rval;
};
static int
_mountBootDA(struct updatingVol *up)
{
int rval = ELAST + 1;
CFStringRef mountargs[] = { CFSTR("perm"), CFSTR("nobrowse"), NULL };
DADissenterRef dis = (void*)kCFNull;
CFDictionaryRef ddesc = NULL;
CFURLRef volURL;
if (!(up->curBoot=DADiskCreateFromBSDName(nil,up->dasession,up->bsdname))){
goto finish;
}
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Mounting %s...", up->bsdname);
DADiskMountWithArguments(up->curBoot, NULL,kDADiskMountOptionDefault,
_daDone, &dis, mountargs);
if (dis == (void*)kCFNull) {
CFRunLoopRun(); }
if (dis) {
rval = DADissenterGetStatus(dis);
if (rval != kDAReturnBusy) {
goto finish;
}
}
if (!(ddesc = DADiskCopyDescription(up->curBoot))) goto finish;
volURL = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumePathKey);
if (!volURL || CFGetTypeID(volURL) != CFURLGetTypeID()) goto finish;
if (!CFURLGetFileSystemRepresentation(volURL, true ,
(UInt8*)up->curMount, PATH_MAX)) goto finish;
rval = 0;
finish:
if (rval) {
if (rval != ELAST + 1) {
if (rval == -1) rval = errno;
OSKextLog(NULL, up->errLogSpec,
"Failed to mount helper (%d/%#x): %s", rval,
rval & ~(err_local|err_local_diskarbitration), strerror(rval));
} else {
OSKextLog(NULL, up->errLogSpec,"Failed to mount helper partition.");
}
}
if (ddesc) CFRelease(ddesc);
if (dis && dis != (void*)kCFNull) { CFRelease(dis);
}
return rval;
}
#define BRMNT_PARENT "/private/var/run"
#define BRMNT BRMNT_PARENT "/brmnt"
static int
_mountBootBuiltIn(struct updatingVol *up)
{
int bsderr, rval = ELAST + 1; int vrfd = -1;
int fd = -1;
struct stat sb;
char devpath[DEVMAXPATHSIZE];
struct hfs_mount_args hfsargs;
if (((vrfd = open(_PATH_VARRUN, O_RDONLY))) == -1) {
rval = vrfd; LOGERRxlate(up, _PATH_VARRUN, NULL, rval); goto finish;
}
fd = open(BRMNT, O_RDONLY);
if (fd != -1 && fstat(fd, &sb)==0 && S_ISDIR(sb.st_mode)==false) {
if ((bsderr = sunlink(vrfd, BRMNT))) {
rval = bsderr; LOGERRxlate(up, BRMNT, NULL, rval); goto finish;
}
close(fd);
fd = open(BRMNT, O_RDONLY);
if (fd != -1) {
rval = EEXIST; LOGERRxlate(up, BRMNT, NULL, rval); goto finish;
}
}
if (fd == -1 && errno == ENOENT) {
if ((bsderr = smkdir(vrfd, BRMNT, kCacheDirMode))) {
rval = bsderr; LOGERRxlate(up, "mkdir", BRMNT, rval); goto finish;
}
}
bzero(&hfsargs, sizeof(hfsargs));
(void)snprintf(devpath, sizeof(devpath), _PATH_DEV "%s", up->bsdname);
hfsargs.fspec = devpath;
if ((bsderr = mount("hfs", BRMNT, MNT_DONTBROWSE, &hfsargs))) {
rval = bsderr; LOGERRxlate(up, "mount", BRMNT, rval); goto finish;
}
if (strlcpy(up->curMount, BRMNT, MNAMELEN) >= MNAMELEN) {
rval = EOVERFLOW; LOGERRxlate(up,up->curMount,NULL,rval); goto finish;
}
rval = 0;
finish:
if (fd != -1) close(fd);
if (vrfd != -1) close(vrfd);
return rval;
}
static int
_findMountedHelper(struct updatingVol *up)
{
int rval = ELAST + 1;
int nfsys, i;
int bufsz;
struct statfs *mounts = NULL;
if (-1 == (nfsys = getfsstat(NULL, 0, MNT_NOWAIT))) {
rval = errno; goto finish;
}
bufsz = nfsys * sizeof(struct statfs);
if (!(mounts = malloc(bufsz))) {
rval = errno; goto finish;
}
if (-1 == getfsstat(mounts, bufsz, MNT_NOWAIT)) {
rval = errno; goto finish;
}
for (i = 0; i < nfsys; i++) {
struct statfs *sfs = &mounts[i];
if (strlen(sfs->f_mntfromname) < sizeof(_PATH_DEV) ||
(strcmp(sfs->f_fstypename, "hfs") &&
strcmp(sfs->f_fstypename, "apfs"))) {
continue;
}
if (0 == strcmp(sfs->f_mntfromname+strlen(_PATH_DEV), up->bsdname)){
if (strlcpy(up->curMount, sfs->f_mntonname, MNAMELEN)>=MNAMELEN) {
rval = EOVERFLOW; goto finish;
}
rval = 0;
goto finish;
}
}
rval = ENOENT;
finish:
if (mounts) free(mounts);
return rval;
}
static int
mountBoot(struct updatingVol *up)
{
int errnum, rval = ELAST + 1;
CFStringRef str;
struct statfs bsfs;
uint32_t mntgoal;
struct stat sb;
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Mounting helper partition...");
str = (CFStringRef)CFArrayGetValueAtIndex(up->boots, up->bootIdx);
if (!str || CFGetTypeID(str) != CFStringGetTypeID()) {
goto finish;
}
if (!CFStringGetFileSystemRepresentation(str,up->bsdname,DEVMAXPATHSIZE)){
goto finish;
}
if (up->dasession) {
if ((errnum = _mountBootDA(up))) {
rval = errnum; goto finish; }
} else if (_findMountedHelper(up) == ENOENT &&
(errnum = _mountBootBuiltIn(up))) {
rval = errnum; goto finish; }
if (-1 == (up->curbootfd = open(up->curMount, O_RDONLY, 0))) {
rval = errno; LOGERRxlate(up, up->curMount, NULL, rval); goto finish;
}
if (fstat(up->caches->cachefd, &sb)) {
rval = errno; LOGERRxlate(up, "cachefd MIA?", NULL, rval); goto finish;
}
if (fstatfs(up->curbootfd, &bsfs)) {
rval = errno; LOGERRxlate(up, "curboot MIA?", NULL, rval); goto finish;
}
mntgoal = bsfs.f_flags;
mntgoal &= ~(MNT_RDONLY|MNT_IGNORE_OWNERSHIP);
if ((bsfs.f_flags != mntgoal) && updateMount(up->curMount, mntgoal)) {
OSKextLog(NULL, up->warnLogSpec,
"Warning: couldn't update mount to read/write + owners");
}
if (bsfs.f_blocks * bsfs.f_bsize < (128 * 1<<20)) {
rval = EFTYPE;
OSKextLog(NULL, up->errLogSpec, "skipping Apple_Boot helper < 128 MB.");
goto finish;
}
if (!up->useOnceDir && up->flatTarget[0]) {
char path[PATH_MAX];
pathcpy(path, up->curMount);
pathcat(path, up->flatTarget);
if (stat(path, &sb) != 0) {
if (errno == ENOENT) {
rval = ENOENT;
LOGERRxlate(up, "target directory must exist", path, rval);
goto finish;
}
} else if (!S_ISDIR(sb.st_mode)) {
rval = ENOTDIR; LOGERRxlate(up, path, NULL, rval); goto finish;
}
}
rval = 0;
finish:
if (rval != 0 && (up->curBoot || up->curMount[0])) {
(void)unmountBoot(up); }
return rval;
}
static void
unmountBoot(struct updatingVol *up)
{
int errnum = 0;
DADissenterRef dis = (void*)kCFNull;
if (up->curbootfd != -1) {
close(up->curbootfd);
up->curbootfd = -1;
}
if (up->flatTarget[0] || up->useStagingDir) return;
if (up->curMount[0]) {
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Unmounting helper partition %s.", up->bsdname);
}
if (up->curBoot) {
DADiskUnmount(up->curBoot,kDADiskMountOptionDefault,_daDone,&dis);
if (dis == (void*)kCFNull) { CFRunLoopRun();
}
if (dis) {
OSKextLog(NULL, up->warnLogSpec,
"%s didn't unmount, leaving mounted", up->bsdname);
if (dis != (void*)kCFNull) {
CFRelease(dis);
}
}
up->curMount[0] = '\0'; CFRelease(up->curBoot);
up->curBoot = NULL;
}
if (up->dasession == NULL && up->curMount[0] != '\0') {
if (unmount(up->curMount, 0)) {
errnum = errno;
}
up->curMount[0] = '\0'; }
if (errnum) {
OSKextLog(NULL, up->errLogSpec,
"Failed to unmount helper (%d/%#x): %s", errnum,
errnum & ~(err_local|err_local_diskarbitration), strerror(errnum));
}
}
static int
writeBootPrefs(struct updatingVol *up, char *dstpath)
{
int opres, rval = ELAST + 1;
CFDataRef bpdata = NULL;
char dstparent[PATH_MAX];
ssize_t len;
int fd = -1;
bpdata = createBootPrefData(up, up->host_uuid, up->bpoverrides);
if (!bpdata) { rval = ENOMEM; goto finish; }
if (strlcpy(dstparent,dirname(dstpath),PATH_MAX) >= PATH_MAX) {
rval = EOVERFLOW; goto finish;
}
opres = sdeepmkdir(up->curbootfd, dstparent, kCacheDirMode);
if (opres) {
rval = opres; goto finish;
}
(void)sunlink(up->curbootfd, dstpath);
fd = sopen(up->curbootfd, dstpath, O_WRONLY|O_CREAT, kCacheFileMode);
if (fd == -1) {
rval = errno; goto finish;
}
len = CFDataGetLength(bpdata);
if (write(fd,CFDataGetBytePtr(bpdata),len) != len) {
rval = errno; goto finish;
}
rval = 0;
finish:
if (rval) {
LOGERRxlate(up, dstpath, NULL, rval);
}
if (fd != -1) close(fd);
if (bpdata) CFRelease(bpdata);
return rval;
}
static int
eraseRPS(struct updatingVol *up, char *toErase)
{
int rval = ELAST+1;
char path[PATH_MAX];
struct stat sb;
if (stat(toErase, &sb) == -1 && errno == ENOENT) {
rval = 0;
goto finish;
}
if (up->caches->erpropcache->rpath) {
pathcpy(path, toErase);
pathcat(path, up->caches->erpropcache->rpath);
if (szerofile(up->curbootfd, path))
goto finish;
}
rval = sdeepunlink(up->curbootfd, toErase);
finish:
if (rval) {
OSKextLog(NULL, up->errLogSpec | kOSKextLogFileAccessFlag,
"%s - %s. errno %d %s",
__FUNCTION__, toErase, errno, strerror(errno));
}
return rval;
}
static int
_writeFDEPropsToHelper(struct updatingVol *up, char *dstpath)
{
int errnum, rval = ELAST + 1; char *stage;
CFDictionaryRef matching; io_service_t helper = IO_OBJECT_NULL;
CFNumberRef unitNum = NULL;
CFNumberRef partNum = NULL;
int partnum;
const void *keys[2], *vals[2];
CFDictionaryRef props = NULL;
io_service_t bearer = IO_OBJECT_NULL;
CFStringRef partType = NULL;
CFStringRef partBSD = NULL;
char csbsd[DEVMAXPATHSIZE];
stage = "check argument";
if (up->onAPM) {
rval = EINVAL; goto finish;
}
stage = "find current helper partition";
if (!(matching = IOBSDNameMatching(kIOMasterPortDefault, 0, up->bsdname))){
rval = ENOMEM; goto finish;
}
helper = IOServiceGetMatchingService(kIOMasterPortDefault, matching);
matching = NULL; if (!helper) {
rval = ENOENT; goto finish;
}
unitNum = (CFNumberRef)IORegistryEntryCreateCFProperty(helper,
CFSTR(kIOBSDUnitKey), nil, 0);
if (!unitNum || CFGetTypeID(unitNum) != CFNumberGetTypeID()) {
rval = ENODEV; goto finish;
}
partNum = (CFNumberRef)IORegistryEntryCreateCFProperty(helper,
CFSTR(kIOMediaPartitionIDKey), nil, 0);
if (!partNum || CFGetTypeID(partNum) != CFNumberGetTypeID()) {
rval = ENODEV; goto finish;
}
stage = "create description of corresponding data partition";
CFNumberGetValue(partNum, kCFNumberIntType, &partnum);
CFRelease(partNum);
partNum = NULL;
if (--partnum <= 0) {
rval = ENODEV; goto finish;
}
partNum = CFNumberCreate(nil, kCFNumberIntType, &partnum);
if (!partNum) {
rval = ENOMEM; goto finish;
}
keys[0] = CFSTR(kIOMediaPartitionIDKey);
vals[0] = partNum;
keys[1] = CFSTR(kIOBSDUnitKey);
vals[1] = unitNum;
if (!(props = CFDictionaryCreate(nil, keys, vals, 2,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks))) {
rval = ENOMEM; goto finish;
}
keys[0] = CFSTR(kIOProviderClassKey);
vals[0] = CFSTR(kIOMediaClass);
keys[1] = CFSTR(kIOPropertyMatchKey);
vals[1] = props;
if (!(matching = CFDictionaryCreate(nil, keys, vals, 2,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks))) {
rval = ENOMEM; goto finish;
}
stage = "find & validate data partition";
bearer = IOServiceGetMatchingService(kIOMasterPortDefault, matching);
matching = NULL; if (!bearer) {
rval = ENOENT; goto finish;
}
partBSD = (CFStringRef)IORegistryEntryCreateCFProperty(bearer,
CFSTR(kIOBSDNameKey), nil, 0);
if (!partBSD || CFGetTypeID(partBSD) != CFStringGetTypeID()) {
rval = ENODEV; goto finish;
}
if (!CFStringGetFileSystemRepresentation(partBSD, csbsd, sizeof(csbsd))){
rval = EOVERFLOW; goto finish;
}
partType = (CFStringRef)IORegistryEntryCreateCFProperty(bearer,
CFSTR(kIOMediaContentKey), nil, 0);
if (!partType || CFGetTypeID(partType) != CFStringGetTypeID()) {
rval = ENODEV; goto finish;
}
if (!CFEqual(partType, CFSTR(APPLE_CORESTORAGE_UUID))) {
rval = ENODEV;
LOGERRxlate(up, csbsd, "must be of type Apple_CoreStorage", rval);
stage = NULL; goto finish;
}
stage = NULL; if ((errnum=writeCSFDEProps(up->curbootfd,up->csfdeprops,csbsd,dstpath))){
rval = errnum; goto finish;
}
rval = 0;
finish:
if (rval && stage) {
OSKextLog(NULL, up->errLogSpec | kOSKextLogFileAccessFlag,
"%s() failed trying to %s", __func__, stage);
}
if (partBSD) CFRelease(partBSD);
if (partType) CFRelease(partType);
if (bearer != IO_OBJECT_NULL) IOObjectRelease(bearer);
if (props) CFRelease(props);
if (partNum) CFRelease(partNum);
if (unitNum) CFRelease(unitNum);
if (helper != IO_OBJECT_NULL) IOObjectRelease(helper);
return rval;
}
static void
eraseRPSIfRecoveryBlessed(struct updatingVol *up, char *rpsDir)
{
if (blessRecovery(up) == 0) {
int bsderr;
if ((bsderr = eraseRPS(up, rpsDir)) && errno != ENOENT) {
LOGERRxlate(up, rpsDir, "couldn't free up space", bsderr);
}
up->doBooters = true;
}
}
static int
ucopyRPS(struct updatingVol *up)
{
int bsderr, rval = ELAST + 1; char prevRPS[PATH_MAX], curRPS[PATH_MAX], discard[PATH_MAX];
char *erdir;
unsigned i;
char srcpath[PATH_MAX], dstpath[PATH_MAX];
#if DEV_KERNEL_SUPPORT
CFStringRef my_kcsuffix = NULL; Boolean copiedPrefKernel = false;
#endif
COMPILE_TIME_ASSERT(sizeof(BOOTPLIST_NAME)==sizeof(BOOTPLIST_APM_NAME));
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Copying files used by the booter.");
if ((bsderr = FindRPSDir(up, prevRPS, curRPS, discard))) {
rval = bsderr; goto finish; }
if (up->flatTarget[0] || up->useOnceDir) {
pathcpy(up->dstdir, up->curMount);
if (up->useOnceDir) {
pathcat(up->dstdir, kBRBootOnceDir);
}
pathcat(up->dstdir, up->flatTarget);
erdir = curRPS;
if ((up->blessSpec & kBRBlessOnce) == 0 && (up->blessSpec & kBRBlessRecovery) != 0) {
erdir = prevRPS;
eraseRPSIfRecoveryBlessed(up, curRPS);
}
} else {
pathcpy(up->dstdir, prevRPS);
erdir = prevRPS;
eraseRPSIfRecoveryBlessed(up, curRPS);
}
if ((bsderr = eraseRPS(up, up->dstdir))) {
rval = bsderr; goto finish; }
if ((bsderr = sdeepmkdir(up->curbootfd, up->dstdir, kCacheDirMode))) {
rval = bsderr; LOGERRxlate(up, up->dstdir, NULL, rval); goto finish;
}
#if DEV_KERNEL_SUPPORT
my_kcsuffix = copy_kcsuffix();
if (up->caches->extraKernelCachePaths && my_kcsuffix) {
int i;
for (i = 0; i < up->caches->nekcp; i++) {
cachedPath *curItem = &up->caches->extraKernelCachePaths[i];
CFStringRef tempString;
Boolean hasSuffix;
tempString = CFStringCreateWithCString(NULL,
curItem->rpath,
kCFStringEncodingUTF8);
if (tempString == NULL) {
continue;
}
hasSuffix = CFStringHasSuffix(tempString, my_kcsuffix);
SAFE_RELEASE_NULL(tempString);
if (hasSuffix == false) {
continue;
}
pathcpy(srcpath, up->caches->root);
pathcat(srcpath, curItem->rpath);
pathcpy(dstpath, up->dstdir);
if (up->flatTarget[0] || up->useOnceDir) {
pathcat(dstpath, "/");
pathcat(dstpath, basename(curItem->rpath));
} else {
if (up->caches->prefer_fileset) {
pathcat(dstpath, kPrebootKCPrefix);
}
pathcat(dstpath, curItem->rpath);
}
OSKextLog(NULL, kOSKextLogGeneralFlag|kOSKextLogDetailLevel,
"copying %s to %s", srcpath, up->dstdir);
bsderr = scopyitem(up->caches->cachefd,
srcpath,
up->curbootfd,
dstpath);
if (bsderr) {
rval = bsderr == -1 ? errno : bsderr;
OSKextLog(NULL,up->errLogSpec,"Error %d copying %s to %s: %s",
rval, srcpath, dstpath, strerror(rval));
goto finish;
}
copiedPrefKernel = true;
} }
#endif
for (i = 0; i < up->caches->nrps; i++) {
cachedPath *curItem = &up->caches->rpspaths[i];
bool is_plk = ((curItem == up->caches->readonly_kext_boot_cache_file) ||
(curItem == up->caches->kext_boot_cache_file));
if (copiedPrefKernel && (curItem == up->caches->readonly_kext_boot_cache_file ||
(up->caches->readonly_kext_boot_cache_file == NULL &&
curItem == up->caches->kext_boot_cache_file) ||
(up->caches->prefer_fileset && curItem == up->caches->fileset_bootkc))) {
continue;
}
pathcpy(srcpath, up->caches->root);
pathcat(srcpath, curItem->rpath);
pathcpy(dstpath, up->dstdir);
if ((up->flatTarget[0] || up->useOnceDir)
&& curItem != up->caches->efidefrsrcs
&& curItem != up->caches->efiloccache) {
pathcat(dstpath, "/");
pathcat(dstpath, basename(curItem->rpath));
} else {
if (up->caches->prefer_fileset) {
pathcat(dstpath, kPrebootKCPrefix);
}
pathcat(dstpath, curItem->rpath);
}
if (curItem == up->caches->bootconfig) {
if (up->onAPM) {
char * plistNamePtr;
plistNamePtr = strstr(dstpath, BOOTPLIST_NAME);
if (plistNamePtr) {
strncpy(plistNamePtr, BOOTPLIST_APM_NAME, strlen(BOOTPLIST_NAME));
}
}
if ((bsderr = writeBootPrefs(up, dstpath))) {
rval = bsderr; goto finish; }
} else if (curItem == up->caches->erpropcache && up->csfdeprops && up->onAPM == false && !up->skipFDECopy) {
if ((bsderr = _writeFDEPropsToHelper(up, dstpath))) {
rval = bsderr; goto finish; }
} else {
OSKextLog(NULL, kOSKextLogGeneralFlag|kOSKextLogDetailLevel,
"copying %s to %s", srcpath, up->dstdir);
bsderr = scopyitem(up->caches->cachefd, srcpath, up->curbootfd, dstpath);
if (bsderr) {
rval = bsderr == -1 ? errno : bsderr;
if (((curItem == up->caches->erpropcache) || (curItem == up->caches->efiloccache)) && (rval == ENOENT)) {
continue;
} else if ((rval == ENOENT) && up->caches->prefer_fileset && is_plk) {
OSKextLog(NULL, up->errLogSpec, "Skipping copy of %s to %s",
srcpath, dstpath);
continue;
} else {
OSKextLog(NULL, up->errLogSpec,"Error %d copying %s to %s: %s",
rval, srcpath, dstpath, strerror(rval));
goto finish;
}
}
}
}
if ((up->flatTarget[0] || up->useOnceDir)
&& up->caches->erpropTSOnly == false && up->onAPM == false
&& up->caches->erpropcache && up->csfdeprops && !up->skipFDECopy) {
pathcpy(dstpath, erdir);
pathcat(dstpath, up->caches->erpropcache->rpath);
if ((bsderr = _writeFDEPropsToHelper(up, dstpath))) {
rval = bsderr; goto finish; }
if (up->caches->efidefrsrcs) {
pathcpy(srcpath, up->caches->root);
pathcat(srcpath, up->caches->efidefrsrcs->rpath);
pathcpy(dstpath, erdir);
pathcat(dstpath, up->caches->efidefrsrcs->rpath);
bsderr=scopyitem(up->caches->cachefd,srcpath,up->curbootfd,dstpath);
if (bsderr) {
rval = bsderr == -1 ? errno : bsderr;
OSKextLog(NULL,up->errLogSpec,"Error %d copying %s to %s: %s",
rval, srcpath, dstpath, strerror(rval));
goto finish;
}
}
}
rval = 0;
finish:
#if DEV_KERNEL_SUPPORT
SAFE_RELEASE(my_kcsuffix);
#endif
return rval;
}
static int
ucopyMisc(struct updatingVol *up)
{
int bsderr, rval = -1;
unsigned i, nprocessed = 0;
char srcpath[PATH_MAX], dstpath[PATH_MAX];
struct stat sb;
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Copying files read before the booter runs.");
for (i = 0; i < up->caches->nmisc; i++) {
pathcpy(srcpath, up->caches->root);
pathcat(srcpath, up->caches->miscpaths[i].rpath);
makebootpath(dstpath, up->caches->miscpaths[i].rpath);
pathcat(dstpath, ".new");
if (stat(srcpath, &sb) == 0) {
if ((bsderr = scopyitem(up->caches->cachefd, srcpath,
up->curbootfd, dstpath))) {
if (bsderr == -1) bsderr = errno;
OSKextLog(NULL, up->errLogSpec, "Error %d copying %s to %s: %s",
bsderr, srcpath, dstpath, strerror(bsderr));
continue;
}
} else if (errno != ENOENT) {
continue;
}
nprocessed++;
}
if (nprocessed == i) {
rval = 0;
} else {
rval = errno;
}
finish:
if (rval) {
LOGERRxlate(up, __func__, "failure copying pre-booter files", rval);
}
return rval;
}
static int
moveLabels(struct updatingVol *up)
{
int rval = -1;
char path[PATH_MAX];
struct stat sb;
int fd = -1;
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Moving aside old label.");
makebootpath(path, up->caches->label->rpath);
if (0 == (stat(path, &sb))) {
char newpath[PATH_MAX];
unsigned char nulltype[32] = {'\0', };
pathcpy(newpath, path);
pathcat(newpath, NEWEXT);
rval = srename(up->curbootfd, path, newpath);
if (rval) goto finish;
if (-1 == (fd=sopen(up->curbootfd, newpath, O_RDWR, 0))) goto finish;
if(fsetxattr(fd,XATTR_FINDERINFO_NAME,&nulltype,sizeof(nulltype),0,0)) {
goto finish;
}
}
up->changestate = noLabel;
rval = 0;
finish:
if (fd != -1) close(fd);
if (rval) {
OSKextLog(NULL, up->errLogSpec,
"%s - Error moving aside old label. errno %d %s.",
__FUNCTION__, errno, strerror(errno));
}
return rval;
}
static int
nukeBRLabels(struct updatingVol *up)
{
int rval = EOVERFLOW; int opres, firstErrno, firstErr = 0;
char labelp[PATH_MAX], dstparent[PATH_MAX];
struct stat sb;
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Removing current disk label.");
makebootpath(labelp, up->caches->label->rpath);
if (0 == (stat(labelp, &sb))) {
opres = sunlink(up->curbootfd, labelp);
RECERR(up, opres, "error removing label" );
} else {
errno = 0;
}
pathcpy(labelp, up->curMount);
if (up->useOnceDir) {
pathcat(labelp, kBRBootOnceDir);
}
pathcat(labelp, up->caches->label->rpath);
pathcat(labelp, SCALE_2xEXT); if (0 == (stat(labelp, &sb))) {
opres = sunlink(up->curbootfd, labelp);
RECERR(up, opres, "error removing .contentDetails" );
} else {
errno = 0;
}
pathcpy(labelp, up->curMount);
if (up->useOnceDir) {
pathcat(labelp, kBRBootOnceDir);
}
pathcat(labelp, up->caches->label->rpath);
pathcat(labelp, CONTENTEXT); if (0 == (stat(labelp, &sb))) {
opres = sunlink(up->curbootfd, labelp);
RECERR(up, opres, "error removing " CONTENTEXT );
} else {
errno = 0;
}
makebootpath(labelp, up->caches->label->rpath);
pathcpy(dstparent, dirname(labelp));
pathcpy(labelp, dstparent);
pathcat(labelp, "/" kBRRootUUIDFile);
if (0 == (stat(labelp, &sb))) {
opres = sunlink(up->curbootfd, labelp);
RECERR(up, opres, "error removing " kBRRootUUIDFile );
} else {
errno = 0;
}
up->changestate = noLabel;
if (firstErr == -1) errno = firstErrno;
rval = firstErr;
finish:
if (rval)
OSKextLog(NULL, kOSKextLogErrorLevel, "Error removing disk label.");
return rval;
}
static int
ucopyBooters(struct updatingVol *up)
{
int rval = ELAST + 1;
int bsderr;
char srcpath[PATH_MAX], oldpath[PATH_MAX];
int nbooters = 0;
if (up->caches->ofbooter.rpath[0]) nbooters++;
if (up->caches->efibooter.rpath[0]) nbooters++;
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Copying new booter%s.", nbooters == 1 ? "" : "s");
up->changestate = copyingOFBooter;
if (up->caches->ofbooter.rpath[0]) {
pathcpy(srcpath, up->caches->root);
pathcat(srcpath, up->caches->ofbooter.rpath); makebootpath(up->ofdst, up->caches->ofbooter.rpath); pathcpy(oldpath, up->ofdst);
pathcat(oldpath, OLDEXT);
(void)sunlink(up->curbootfd, oldpath);
bsderr = srename(up->curbootfd, up->ofdst, oldpath);
if (bsderr && errno !=ENOENT) {
OSKextLog(NULL, up->errLogSpec,
"%s - Error rename old %s new %s",
__FUNCTION__, up->ofdst, oldpath);
goto finish;
}
if ((bsderr = scopyitem(up->caches->cachefd, srcpath,
up->curbootfd, up->ofdst))) {
rval = bsderr == -1 ? errno : bsderr;
if (!(up->opts & kBRUHelpersOptional)) {
OSKextLog(NULL,up->errLogSpec,"Error %d copying %s to %s: %s",
rval, srcpath, up->ofdst, strerror(rval));
}
goto finish;
}
}
up->changestate = copyingEFIBooter;
if (up->caches->efibooter.rpath[0]) {
pathcpy(srcpath, up->caches->root);
pathcat(srcpath, up->caches->efibooter.rpath); makebootpath(up->efidst, up->caches->efibooter.rpath);
pathcpy(oldpath, up->efidst);
pathcat(oldpath, OLDEXT);
(void)sunlink(up->curbootfd, oldpath);
bsderr = srename(up->curbootfd, up->efidst, oldpath);
if (bsderr && errno != ENOENT) {
OSKextLog(NULL, up->errLogSpec,
"%s - Error rename old %s new %s",
__FUNCTION__, up->efidst, oldpath);
goto finish;
}
if ((bsderr = scopyitem(up->caches->cachefd, srcpath,
up->curbootfd, up->efidst))) {
if (!(up->opts & kBRUHelpersOptional)) {
rval = bsderr == -1 ? errno : bsderr;
OSKextLog(NULL,up->errLogSpec,"Error %d copying %s to %s: %s",
rval, srcpath, up->efidst, strerror(rval));
}
goto finish;
}
}
up->changestate = copiedBooters;
rval = 0;
finish:
return rval;
}
#define CLOSE(fd) do { (void)close(fd); fd = -1; } while(0)
static int
activateBooters(struct updatingVol *up)
{
int errnum, rval = ELAST + 1;
int fd = -1;
uint64_t vinfo[8] = { 0, };
struct stat sb;
char parent[PATH_MAX];
int nbooters = 0;
BLContext blctx = { 0, BRBLLogFunc, NULL };
if (up->caches->ofbooter.rpath[0]) nbooters++;
if (up->caches->efibooter.rpath[0]) nbooters++;
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Activating new booter%s.", nbooters == 1 ? "" : "s");
if ((errnum = fcntl(up->curbootfd, F_FULLFSYNC))) {
rval = errnum; goto finish;
}
up->changestate = activatingOFBooter;
if (up->caches->ofbooter.rpath[0]) {
unsigned char tbxichrp[32] = {'t','b','x','i','c','h','r','p','\0',};
if (-1==(fd=sopen(up->curbootfd, up->ofdst, O_RDONLY, 0))) {
rval = errno; goto finish;
}
if(fsetxattr(fd,XATTR_FINDERINFO_NAME,&tbxichrp,sizeof(tbxichrp),0,0)){
rval = errno; goto finish;
}
CLOSE(fd);
pathcpy(parent, dirname(up->ofdst));
if (-1 == (fd=sopen(up->curbootfd, parent, O_RDONLY, 0))
|| fstat(fd, &sb)) {
rval = errno; goto finish;
}
CLOSE(fd);
vinfo[kSystemFolderIdx] = sb.st_ino;
}
up->changestate = activatingEFIBooter;
if (up->caches->efibooter.rpath[0]) {
if (-1==(fd=sopen(up->curbootfd, up->efidst, O_RDONLY, 0))
|| fstat(fd, &sb)) {
rval = errno; goto finish;
}
CLOSE(fd);
vinfo[kEFIBooterIdx] = sb.st_ino;
if (!vinfo[0]) {
pathcpy(parent, dirname(up->efidst));
if (-1 == (fd=sopen(up->curbootfd, parent, O_RDONLY, 0))
|| fstat(fd, &sb)) {
rval = errno; goto finish;
}
CLOSE(fd);
vinfo[kSystemFolderIdx] = sb.st_ino;
}
}
if (up->blessSpec & kBRBlessFSDefault) {
if ((errnum = sBLSetBootInfo(up, vinfo))) {
rval = errnum; goto finish;
}
}
if (up->blessSpec == kBRBlessFull) {
if (BLSetEFIBootDevice(&blctx, up->bsdname)) {
rval = ENODEV; goto finish;
}
}
if (up->blessSpec & kBRBlessOnce) {
if (up->blessSpec & kBRBlessFSDefault) {
if (BLSetEFIBootDeviceOnce(&blctx, up->bsdname)) {
rval = ENODEV; goto finish;
}
} else {
if (BLSetEFIBootFileOnce(&blctx, up->efidst)) {
rval = ENODEV; goto finish;
}
}
}
up->changestate = activatedBooters;
rval = 0;
finish:
if (fd != -1) close(fd);
if (rval)
OSKextLog(NULL, kOSKextLogErrorLevel, "Error activating booter.");
return rval;
}
static int
activateRPS(struct updatingVol *up)
{
int rval = ELAST + 1;
char prevRPS[PATH_MAX], curRPS[PATH_MAX], nextRPS[PATH_MAX];
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Activating files used by the booter.");
if (up->flatTarget[0] == '\0' && up->useOnceDir == false) {
if (FindRPSDir(up, prevRPS, curRPS, nextRPS)) goto finish;
if (strncmp(curRPS, up->dstdir, PATH_MAX) != 0) {
if (srename(up->curbootfd, prevRPS, nextRPS)) goto finish;
}
}
if (fcntl(up->curbootfd, F_FULLFSYNC)) goto finish;
rval = 0;
finish:
if (rval) {
OSKextLog(NULL, kOSKextLogErrorLevel,
"Error activating files used by the booter.");
}
return rval;
}
#define MAKEBOOTPATHcont(path, rpath) do { \
PATHCPYcont(path, up->curMount); \
if (up->useOnceDir) { \
PATHCATcont(path, kBRBootOnceDir); \
} \
if (up->flatTarget[0] || up->useOnceDir) { \
PATHCATcont(path, up->flatTarget); \
\
PATHCATcont(path, "/"); \
PATHCATcont(path, basename(rpath)); \
} else { \
PATHCATcont(path, rpath); \
} \
} while(0)
static int
activateMisc(struct updatingVol *up) {
int rval = ELAST + 1;
char path[PATH_MAX], opath[PATH_MAX];
unsigned 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(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Activating files used before the booter runs.");
for (i = 0; i < up->caches->nmisc; i++) {
MAKEBOOTPATHcont(path, up->caches->miscpaths[i].rpath);
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++;
}
}
makebootpath(path, up->caches->label->rpath);
char newpath[PATH_MAX];
pathcpy(newpath, path); pathcat(newpath, NEWEXT);
(void)srename(up->curbootfd, newpath, path);
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);
if (rval) {
OSKextLog(NULL, kOSKextLogErrorLevel,
"Error activating files used before the booter runs.");
}
return rval;
}
static int
nukeFallbacks(struct updatingVol *up)
{
int rval = 0; int bsderr;
char delpath[PATH_MAX];
struct bootCaches *caches = up->caches;
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Cleaning up fallbacks.");
if (up->curMount[0] == '\0') goto finish;
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 (up->doRPS) {
char ignore[PATH_MAX];
if (0 == FindRPSDir(up, delpath, ignore, ignore)) {
rval |= eraseRPS(up, delpath);
}
}
finish:
if (rval)
OSKextLog(NULL, kOSKextLogErrorLevel, "Error cleaning up fallbacks.");
return rval;
}
#if 0
static int
kill_kextd(void)
{
int result = -1;
kern_return_t kern_result = kOSReturnError;
mach_port_t bootstrap_port = MACH_PORT_NULL;
mach_port_t kextd_port = MACH_PORT_NULL;
int kextd_pid = -1;
kern_result = task_get_bootstrap_port(mach_task_self(), &bootstrap_port);
if (kern_result != kOSReturnSuccess) {
goto finish;
}
kern_result = bootstrap_look_up(bootstrap_port,
(char *)KEXTD_SERVER_NAME, &kextd_port);
if (kern_result != kOSReturnSuccess) {
goto finish;
}
kern_result = pid_for_task(kextd_port, &kextd_pid);
if (kern_result != kOSReturnSuccess) {
goto finish;
}
result = kill(kextd_pid, SIGKILL);
if (-1 == result) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"kill kextd failed - %s.", strerror(errno));
}
finish:
if (kern_result != kOSReturnSuccess) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"kill kextd failed - %s.", safe_mach_error_string(kern_result));
}
return result;
}
int
renameBootcachesPlist(
char * hostVolume,
char * oldPlistPath,
char * newPlistPath)
{
int result = -1;
int bootcachesPlistFd = -1;
char * errorMessage = NULL;
char * errorPath = NULL;
char oldname[PATH_MAX];
char newname[PATH_MAX];
char * kextcacheArgs[] = {
"/usr/sbin/kextcache",
"-f",
"-u",
NULL, NULL };
errorMessage = "path concatenation error";
errorPath = hostVolume;
if (strlcpy(oldname, hostVolume, PATH_MAX) >= PATH_MAX) {
goto finish;
}
if (strlcpy(newname, hostVolume, PATH_MAX) >= PATH_MAX) {
goto finish;
}
errorPath = oldPlistPath;
if (strlcpy(oldname, oldPlistPath, PATH_MAX) >= PATH_MAX) {
goto finish;
}
errorPath = newPlistPath;
if (strlcpy(newname, newPlistPath, PATH_MAX) >= PATH_MAX) {
goto finish;
}
errorPath = oldname;
bootcachesPlistFd = open(oldname, O_RDONLY);
if (-1 == bootcachesPlistFd) {
errorMessage = strerror(errno);
goto finish;
}
if (-1 == srename(bootcachesPlistFd, oldname, newname)) {
errorMessage = "rename failed.";
goto finish;
}
errorMessage = "couldn't kill kextd";
if (-1 == kill_kextd()) {
goto finish;
}
kextcacheArgs[3] = hostVolume;
result = fork_program(kextcacheArgs[0], kextcacheArgs, true );
finish:
if (errorMessage) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"%s - %s.", errorPath, errorMessage);
}
if (bootcachesPlistFd >= 0) {
close(bootcachesPlistFd);
}
return result;
}
#endif // UNUSED
static int
upstat(const char *path, struct stat *sb, struct statfs *sfs)
{
int rval = ELAST+1;
char buf[PATH_MAX], *tpath = buf;
struct stat defaultsb;
if (strlcpy(buf, path, PATH_MAX) > PATH_MAX) goto finish;
if (!sb) sb = &defaultsb;
while ((rval = stat(tpath, sb)) == -1 && errno == ENOENT) {
if (tpath[0] == '.' && tpath[1] == '\0') goto finish;
if (tpath[0] == '/' && tpath[1] == '\0') goto finish;
tpath = dirname(tpath); }
if (sfs)
rval = statfs(tpath, sfs);
finish:
if (rval) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
"Couldn't find volume for %s.", path);
}
return rval;
}
#define WAITFORLOCK 1
int
takeVolumeForPath(const char *path)
{
int rval = ELAST + 1;
int lckres = 0;
kern_return_t macherr = KERN_SUCCESS;
const char *volPath = "<unknown>"; mach_port_t taskport = MACH_PORT_NULL;
struct statfs sfs;
if (disableKextTools()) {
rval = 0;
goto finish;
}
if (getenv("_com_apple_kextd_skiplocks")) {
rval = 0;
goto finish;
}
if (geteuid() != 0) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: non-root can't lock the volume for %s", path);
rval = 0;
goto finish;
}
if (!sKextdPort) {
macherr=bootstrap_look_up(bootstrap_port,KEXTD_SERVER_NAME,&sKextdPort);
if (macherr) goto finish;
}
if ((rval = upstat(path, NULL, &sfs))) goto finish;
volPath = sfs.f_mntonname;
if ((rval = copyVolumeInfo(volPath,&s_vol_uuid,NULL,NULL,NULL))) {
goto finish;
}
taskport = mach_task_self();
if (taskport == MACH_PORT_NULL) goto finish;
macherr = mach_port_allocate(taskport,MACH_PORT_RIGHT_RECEIVE,&sBRUptLock);
if (macherr) goto finish;
macherr = kextmanager_lock_volume(sKextdPort, sBRUptLock, s_vol_uuid,
!WAITFORLOCK, &lckres);
if (macherr) goto finish;
while (lckres == EAGAIN) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"kextd wasn't ready; waiting 10 seconds and trying again.");
sleep(10);
macherr = kextmanager_lock_volume(sKextdPort, sBRUptLock, s_vol_uuid,
!WAITFORLOCK, &lckres);
if (macherr) goto finish;
}
while (lckres == EBUSY) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"%s locked; waiting for lock.", volPath);
macherr = kextmanager_lock_volume(sKextdPort, sBRUptLock, s_vol_uuid,
WAITFORLOCK, &lckres);
if (macherr) goto finish;
if (lckres == 0) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"Lock acquired; proceeding.");
}
}
if (lckres == ENOENT) {
struct stat sb;
rval = stat(volPath, &sb);
if (rval == 0) {
OSKextLog(NULL, kOSKextLogProgressLevel | kOSKextLogIPCFlag,
"Note: kextd not watching %s; proceeding w/o lock", volPath);
}
} else {
rval = lckres;
}
finish:
if (sBRUptLock != MACH_PORT_NULL && (lckres != 0 || macherr)) {
mach_port_mod_refs(taskport, sBRUptLock, MACH_PORT_RIGHT_RECEIVE, -1);
sBRUptLock = MACH_PORT_NULL;
}
if (macherr == BOOTSTRAP_UNKNOWN_SERVICE ||
macherr == MACH_SEND_INVALID_DEST) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"Warning: kextd unavailable; proceeding w/o lock for %s", volPath);
rval = 0;
} else if (macherr) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"Couldn't lock %s: %s (%x).", path,
safe_mach_error_string(macherr), macherr);
rval = macherr;
} else if (rval) {
if (rval == -1) rval = errno;
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"Couldn't lock %s: %s", path, strerror(rval));
} else {
sBRUptLockCtr++;
if (sBRUptLock && sBRUptLockCtr == 1) {
setenv("_com_apple_kextd_skiplocks", "1", 1);
}
}
return rval;
}
int
putVolumeForPath(const char *path, int status)
{
int rval = KERN_SUCCESS;
bool wasLocked = false;
if (sBRUptLock == MACH_PORT_NULL) {
goto finish;
}
wasLocked = true;
if (sBRUptLockCtr > 1) {
goto finish;
}
rval = kextmanager_unlock_volume(sKextdPort,sBRUptLock,s_vol_uuid,status);
mach_port_mod_refs(mach_task_self(),sBRUptLock,MACH_PORT_RIGHT_RECEIVE,-1);
sBRUptLock = MACH_PORT_NULL;
finish:
if (rval) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"Couldn't unlock volume for %s: %s (%d).",
path, safe_mach_error_string(rval), rval);
} else {
sBRUptLockCtr--;
if (wasLocked && sBRUptLockCtr == 0) {
unsetenv("_com_apple_kextd_skiplocks");
}
}
return rval;
}
CFStringRef copy_kcsuffix(void)
{
CFStringRef result = NULL;
CFStringRef myTempStr;
io_registry_entry_t optionsNode = MACH_PORT_NULL; CFStringRef bootargsEntry = NULL; CFArrayRef myStringArray = NULL; CFRange findRange, myRange;
CFIndex count, i;
optionsNode = IORegistryEntryFromPath(kIOMasterPortDefault,
"IODeviceTree:/options");
while (optionsNode) {
bootargsEntry = (CFStringRef)
IORegistryEntryCreateCFProperty(optionsNode,
CFSTR("boot-args"),
kCFAllocatorDefault, 0);
if (bootargsEntry == NULL ||
(CFGetTypeID(bootargsEntry) != CFStringGetTypeID())) {
break;
}
myStringArray = CFStringCreateArrayBySeparatingStrings(
kCFAllocatorDefault,
bootargsEntry,
CFSTR(" ") );
if (myStringArray == NULL) {
break;
}
count = CFArrayGetCount(myStringArray);
for (i = 0; i < count; i++) {
CFStringRef myString = NULL;
CFIndex myStringLen;
myString = CFArrayGetValueAtIndex(myStringArray, i);
if (myString == NULL) continue;
findRange = CFStringFind(myString, CFSTR("kcsuffix="), 0);
if (findRange.length == 0) {
continue;
}
myStringLen = CFStringGetLength(myString);
if (findRange.length == 9 && myStringLen == 9) {
result = CFRetain(CFSTR(".release"));
break;
}
myRange.length = myStringLen - 9;
myRange.location = findRange.location + 9;
myTempStr = CFStringCreateWithSubstring(kCFAllocatorDefault,
myString,
myRange);
if (myTempStr) {
result = CFStringCreateWithFormat(
kCFAllocatorDefault,
NULL,
CFSTR("%s%@"),
".",
myTempStr );
CFRelease(myTempStr);
}
break;
} break;
}
if (optionsNode) IOObjectRelease(optionsNode);
SAFE_RELEASE(bootargsEntry);
SAFE_RELEASE(myStringArray);
if (result == NULL) {
result = CFRetain(CFSTR(".development"));
}
return(result);
}