#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, copyingOFBooter, copyingEFIBooter, copiedBooters, activatingOFBooter, activatingEFIBooter, activatedBooters };
const char * bootReversionsStrings[] = {
NULL, "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:;}
#define OLDEXT ".old"
#define NEWEXT ".new"
#define CONTENTEXT ".contentDetails"
#define BOOTPLIST_NAME "com.apple.Boot.plist"
#define BOOTPLIST_APM_NAME "com.apple.boot.plist"
struct updatingVol {
struct bootCaches *caches; Boolean doRPS, doMisc, doBooters; Boolean expectUpToDate; enum bootReversions changestate; CFArrayRef boots; DASessionRef dasession;
int bootIdx; char bsdname[DEVMAXPATHSIZE]; DADiskRef curBoot; char curMount[MNAMELEN]; int curbootfd; char curRPS[PATH_MAX]; char efidst[PATH_MAX], ofdst[PATH_MAX];
Boolean onAPM;
};
static int mountBoot(struct updatingVol *up);
static int 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 nukeLabels(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 revertState(struct updatingVol *up);
#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)
#define makebootpath(path, rpath) do { \
pathcpy(path, up->curMount); \
pathcat(path, rpath); \
} 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;
}
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,
true, (UInt8 *)volRoot,
sizeof(volRoot))) {
OSKextLogStringError( NULL);
result = EX_OSERR;
goto finish;
}
if (stat(volRoot, &volumeStatBuffer)) {
OSKextLog( 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 );
if (!volURL) goto finish;
dadisk = DADiskCreateFromVolumePath(nil, up.dasession, volURL);
if (!dadisk) goto finish;
errmsg = NULL;
if (expectUpToDate == false) {
if (takeVolumeForPaths(volRoot)) { goto finish;
}
} else if (getppid() != 2 ) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"Warning: no locks preventing kextd-triggered updates.");
}
if (!(up.caches = readCaches(dadisk))) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
"%s: no " kBootCachesPath "; nothing to do.",
volRoot);
result = 0;
goto finish;
}
if (check_mkext(up.caches)) {
OSKextLog( NULL, kOSKextLogProgressLevel,
"Rebuilding out-of-date mkext.");
setenv("_com_apple_kextd_skiplocks", "1", 1); if (rebuild_mkext(up.caches, true ) != 0) {
goto finish;
}
}
errmsg = "Couldn't get Apple_Boot information.";
if (!hasBootRootBoots(up.caches, &up.boots, &up.onAPM)) {
result = 0; OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
"%s: no supported helper partitions to update.", volRoot);
goto finish;
}
if (fstat(up.caches->cachefd, &sb)) { goto finish;
}
errmsg = "Trouble analyzing what needs updating.";
if (needUpdates(up.caches, &doAny, &up.doRPS, &up.doBooters, &up.doMisc)) {
goto finish;
}
if (!doAny && !force) {
result = 0;
OSKextLog( 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;
errmsg = "trouble updating one or more helper partitions";
DASessionScheduleWithRunLoop(up.dasession, CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode);
bootcount = CFArrayGetCount(up.boots);
for (up.bootIdx = 0; up.bootIdx < bootcount; up.bootIdx++) {
up.changestate = nothingSerious; if ((mountBoot(&up))) {
goto bootfail; }
if (up.doRPS && ucopyRPS(&up)) {
goto bootfail; }
if (up.doMisc) {
(void) ucopyMisc(&up); }
if (expectUpToDate) {
if (moveLabels(&up)) {
goto bootfail;
}
} else {
if (nukeLabels(&up)) {
goto bootfail;
}
}
if (up.doBooters && ucopyBooters(&up)) { goto bootfail;
}
if (up.doBooters && activateBooters(&up)) { goto bootfail;
}
if (up.doRPS && activateRPS(&up)) { goto bootfail;
}
if (activateMisc(&up)) {
goto bootfail; }
up.changestate = nothingSerious;
bootupdates++; OSKextLog( NULL,
kOSKextLogBasicLevel | kOSKextLogFileAccessFlag,
"Successfully updated helper partition %s.", up.bsdname);
bootfail:
if (up.changestate != nothingSerious) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Error updating helper partition %s, state %d: %s.",
up.bsdname, up.changestate,
bootReversionsStrings[up.changestate]);
}
(void)revertState(&up);
if (nukeFallbacks(&up))
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Helper partition %s may be untidy.", up.bsdname);
if (up.curBoot && unmountBoot(&up))
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Couldn't unmount helper partition %s", up.bsdname);
}
if (bootupdates != bootcount) {
goto finish;
}
errmsg = "trouble updating bootstamps";
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;
}
if (expectUpToDate) {
result = EX_OSFILE;
} else {
result = EX_OK;
}
finish:
if (expectUpToDate && result != EX_OSFILE && geteuid() == 0) {
if (takeVolumeForPaths(volRoot) != EAGAIN) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Unexpected error triggering kextd volume watching.");
}
}
if (result && result != EX_OSFILE) {
result = getExitValueFor(result);
}
putVolumeForPath(volRoot, result);
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( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"%s: %s.", volRoot, errmsg);
}
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;
OSKextLog( NULL,
kOSKextLogProgressLevel | kOSKextLogFileAccessFlag,
"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);
(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);
}
case noLabels:
doMisc = up->doMisc;
up->doMisc = false;
rval |= activateMisc(up); up->doMisc = doMisc;
case nothingSerious:
break;
}
finish:
return rval;
};
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( NULL,
kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
"Mounting helper partition.");
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( NULL,
kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
"Mounted %s.", up->bsdname);
DADiskMountWithArguments(up->curBoot, NULL,kDADiskMountOptionDefault,
_daDone, &dis, mountargs);
if (dis == (void*)kCFNull) {
CFRunLoopRun(); }
if (dis) {
rval = DADissenterGetStatus(dis);
if (rval == kDAReturnBusy && up->curMount[0] != '\1') {
up->curMount[0] = '\1';
if (0 == unmountBoot(up)) {
return mountBoot(up);
}
}
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;
if (-1 == (up->curbootfd = open(up->curMount, O_RDONLY, 0))) goto finish;
if (fstat(up->caches->cachefd, &secsb)) goto finish;
if (fstatfs(up->curbootfd, &bsfs)) goto finish;
if (bsfs.f_blocks * bsfs.f_bsize < (128 * 1<<20)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Apple_Boot helper < 128 MB; skipping.");
goto finish;
}
rval = 0;
finish:
if (ddesc) CFRelease(ddesc);
if (dis && dis != (void*)kCFNull) { CFRelease(dis);
}
if (rval != 0 && up->curBoot) {
unmountBoot(up); }
if (rval) {
if (rval != ELAST + 1) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Failed to mount helper partition: error %#X (DA err# %#.2x).",
rval,rval & ~(err_local|err_local_diskarbitration));
} else {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Failed to mount helper partition.");
}
}
return rval;
}
static int unmountBoot(struct updatingVol *up)
{
int rval = 0;
DADissenterRef dis = (void*)kCFNull;
OSKextLog( NULL, kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
"Unmounting helper partition %s.", up->bsdname);
if (!up->curBoot) goto finish;
if (!up->curMount[0]) goto finish;
if (up->curbootfd != -1) close(up->curbootfd);
rval = ELAST + 1;
DADiskUnmount(up->curBoot, kDADiskMountOptionDefault, _daDone, &dis);
if (dis == (void*)kCFNull) { CFRunLoopRun();
}
if (dis) {
CFRelease(dis);
dis = (void*)kCFNull;
OSKextLog( 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'; if (up->curBoot) {
CFRelease(up->curBoot);
up->curBoot = NULL;
}
if (dis && dis != (void*)kCFNull) {
CFRelease(dis);
}
return rval;
}
typedef int EFI_STATUS;
typedef struct stat EFI_FILE_HANDLE;
typedef char UINT16;
typedef Boolean BOOLEAN;
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, haveP, haveS;
char *prevp, *curp, *nextp;
haveR = haveP = haveS = false;
prevp = curp = 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,
kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
"Warning: all of R,P,S exist: picking 'R'.");
curp = rpath; nextp = ppath; prevp = spath;
} 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,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"FindRPSDir(): %s", strerror(errno));
}
return rval;
}
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;
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;
pldict = (CFMutableDictionaryRef)CFPropertyListCreateFromXMLData(nil, data,
kCFPropertyListMutableContainers, NULL );
if (!pldict || CFGetTypeID(pldict)!=CFDictionaryGetTypeID()) {
pldict = CFDictionaryCreateMutable(nil, 0 ,
&kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks);
if (!pldict) goto finish;
}
str = CFStringCreateWithCString(nil, up->caches->uuid_str, kCFStringEncodingASCII);
if (!str) goto finish;
CFDictionarySetValue(pldict, CFSTR(kRootUUIDKey), str);
(void)sunlink(up->curbootfd, dstpath);
dirmode = ((sb.st_mode&~S_IFMT) | S_IWUSR | S_IXUSR );
if (dirmode & S_IRGRP) dirmode |= S_IXGRP; if (dirmode & S_IROTH) dirmode |= S_IXOTH;
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;
}
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( NULL,
kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
"Beginning copy and atomic activation.");
if (FindRPSDir(up, up->curRPS, discard, discard)) goto finish;
if (stat(up->curRPS, &sb) == 0) {
if (sdeepunlink(up->curbootfd, up->curRPS)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"%s: %s.", up->curRPS, strerror(errno));
goto finish;
}
}
if (smkdir(up->curbootfd, up->curRPS, kRPSDirMask)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"%s: %s.", up->curRPS, strerror(errno));
goto finish;
}
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);
if (&up->caches->rpspaths[i] == up->caches->bootconfig) {
COMPILE_TIME_ASSERT(sizeof(BOOTPLIST_NAME) == sizeof(BOOTPLIST_APM_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( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Error populating config file %s.", dstpath);
goto finish;
}
} else {
#if 0
if (stat(srcpath, &sb) == 0 && sb.st_size == 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Zero-size RPS file %s?", srcpath);
goto finish;
}
#endif
if (scopyfile(up->caches->cachefd,srcpath,up->curbootfd,dstpath)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Error copying %s.", srcpath);
goto finish;
}
}
}
rval = 0;
finish:
return rval;
}
static int ucopyMisc(struct updatingVol *up)
{
int rval = -1;
int i, nprocessed = 0;
char srcpath[PATH_MAX], dstpath[PATH_MAX];
struct stat sb;
OSKextLog( 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( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Error copying %s to %s.",
srcpath, dstpath);
}
continue;
}
nprocessed++;
}
rval = (nprocessed != i);
finish:
return rval;
}
static int moveLabels(struct updatingVol *up)
{
int rval = 0;
char path[PATH_MAX];
struct stat sb;
int fd = -1;
OSKextLog( 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', };
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,&tbxichrp,sizeof(tbxichrp),0,0)) {
goto finish;
}
}
up->changestate = noLabels;
finish:
if (fd != -1) close(fd);
return rval;
}
static int nukeLabels(struct updatingVol *up)
{
int rval = 0;
char labelp[PATH_MAX];
struct stat sb;
OSKextLog( 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);
}
pathcat(labelp, CONTENTEXT);
if (0 == (stat(labelp, &sb))) {
rval |= sunlink(up->curbootfd, labelp);
}
up->changestate = noLabels;
finish:
return rval;
}
static int ucopyBooters(struct updatingVol *up)
{
int rval = ELAST + 1;
char srcpath[PATH_MAX], oldpath[PATH_MAX];
OSKextLog( NULL,
kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
"Copying new booters.");
up->changestate = copyingOFBooter;
if (up->caches->ofbooter.rpath[0]) {
pathcpy(srcpath, up->caches->root);
pathcat(srcpath, up->caches->ofbooter.rpath); pathcpy(up->ofdst, up->curMount);
pathcat(up->ofdst, up->caches->ofbooter.rpath); pathcpy(oldpath, up->ofdst);
pathcat(oldpath, OLDEXT);
(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( 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); 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( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Error copying booter %s.", srcpath);
goto finish;
}
}
up->changestate = copiedBooters;
rval = 0;
finish:
return rval;
}
#define CLOSE(fd) do { (void)close(fd); fd = -1; } while(0)
enum blessIndices {
kSystemFolderIdx = 0,
kEFIBooterIdx = 1
};
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( NULL,
kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
"Activating new booters.");
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_RDWR, 0))) goto finish;
if (fcntl(fd, F_FULLFSYNC)) goto finish;
if(fsetxattr(fd,XATTR_FINDERINFO_NAME,&tbxichrp,sizeof(tbxichrp),0,0)) {
goto finish;
}
CLOSE(fd);
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]) {
if (-1==(fd=sopen(up->curbootfd, up->efidst, O_RDONLY, 0))) goto finish;
if (fcntl(fd, F_FULLFSYNC)) goto finish;
if (fstat(fd, &sb)) 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))) {
goto finish;
}
if (fstat(fd, &sb)) goto finish;
CLOSE(fd);
vinfo[kSystemFolderIdx] = sb.st_ino;
}
}
if (schdir(up->curbootfd, up->curMount, &fd)) goto finish;
if ((rval = BLSetVolumeFinderInfo(NULL, ".", vinfo))) goto finish;
(void)restoredir(fd); fd = -1;
up->changestate = activatedBooters;
finish:
if (fd != -1) close(fd);
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 | kOSKextLogFileAccessFlag,
"Completing copy and atomic activation.");
if (FindRPSDir(up, prevRPS, curRPS, nextRPS)) goto finish;
if (strncmp(curRPS, up->curRPS, PATH_MAX) != 0) {
if (srename(up->curbootfd, prevRPS, nextRPS)) goto finish;
}
if (fcntl(up->curbootfd, F_FULLFSYNC)) goto finish;
rval = 0;
finish:
return rval;
}
#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( 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;
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;
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) {
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( NULL,
kOSKextLogDetailLevel | kOSKextLogFileAccessFlag,
"Activating non-booter files.");
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
char newpath[PATH_MAX];
pathcpy(newpath, path); pathcat(newpath, NEWEXT);
(void)srename(up->curbootfd, newpath, path);
#ifndef OPENSOURCE
} else {
(void)sunlink(up->curbootfd, path);
if (writeLabels(up, path)) goto finish;
#endif
}
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;
}
static int nukeFallbacks(struct updatingVol *up)
{
int rval = 0; int bsderr;
char delpath[PATH_MAX];
struct bootCaches *caches = up->caches;
if (!up->curBoot) 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 toss[PATH_MAX];
if (0 == FindRPSDir(up, delpath, toss, toss)) {
if ((bsderr=sdeepunlink(up->curbootfd,delpath)) && bsderr!=ENOENT) {
rval |= bsderr;
}
}
}
finish:
return rval;
}