#include <asl.h>
#include <notify.h> // note notify_monitor_file below
#include <string.h> // strerror()
#include <sysexits.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/wait.h> // waitpid(2)
#include <stdlib.h> // daemon(3)
#include <signal.h>
#include <unistd.h> // e.g. execv
#include <bless.h>
#include <CoreFoundation/CoreFoundation.h>
#include <DiskArbitration/DiskArbitration.h>
#include <DiskArbitration/DiskArbitrationPrivate.h>
#include <IOKit/kext/kextmanager_types.h>
#include <IOKit/storage/CoreStorage/CoreStorageUserLib.h> // LVGChanged
#include <IOKit/kext/OSKextPrivate.h>
#ifndef kOSKextLogCacheFlag
#define kOSKextLogCacheFlag kOSKextLogArchiveFlag
#endif // no kOSKextLogCacheFlag
extern uint32_t notify_monitor_file(int token, const char *name, int flags);
extern uint32_t notify_get_event(int token, int *ev, char *buf, int *len);
#include "kextd_main.h" // handleSignal()
#include "kextd_watchvol.h" // kextd_watch_volumes
#include "kextd_globals.h" // gClientUID
#include "kextd_usernotification.h" // kextd_raise_notification
#include "bootcaches.h" // struct bootCaches
#include "bootroot.h" // BRBLLogFunc (only, for now)
#include "kextmanager_async.h" // lock_*_reply()
#include "safecalls.h" // sdeepunlink
#define kAutoUpdateDelay 300
#define kWatchKeyBase "com.apple.system.kextd.fswatch"
#define kWatchSettleTime 5
#define kMaxUpdateFailures 5 // consecutive failures
#define kMaxUpdateAttempts 25 // reset after failure->success
#define kMessageTracerDomainKey "com.apple.message.domain"
#define kMessageTracerSignatureKey "com.apple.message.signature"
#define kMessageTracerResultKey "com.apple.message.result"
#define kMTCachesDomain "com.apple.kext.caches"
#define kMTUpdatedCaches "updatedCaches"
#define kMTBeginShutdownDelay "beginShutdownDelay"
#define kMTEndShutdownDelay "endShutdownDelay"
struct watchedVol {
CFRunLoopTimerRef delayer; CFMachPortRef lock; CFMutableArrayRef waiters; int updterrs; int updtattempts; uint32_t origMntFlags; Boolean isBootRoot;
CFMutableArrayRef tokens; struct bootCaches *caches;
};
static DASessionRef sDASession = NULL; static DAApprovalSessionRef sDAApproval = NULL; static CFMachPortRef sFsysChangedPort = NULL; static CFRunLoopSourceRef sFsysChangedSource = NULL; static CFMutableDictionaryRef sFsysWatchDict = NULL; static CFMutableDictionaryRef sReplyPorts = NULL; static CFMachPortRef sRebootLock = NULL; static CFMachPortRef sRebootWaiter = NULL; static CFRunLoopTimerRef sAutoUpdateDelayer = NULL; static Boolean sBootRootCheckedIn = false;
static struct watchedVol* create_watchedVol(DADiskRef disk);
static void destroy_watchedVol(struct watchedVol *watched);
static CFMachPortRef createWatchedPort(mach_port_t mport, void *ctx);
static void vol_appeared(DADiskRef disk, void *ctx);
static void vol_changed(DADiskRef, CFArrayRef keys, void* ctx);
static void vol_disappeared(DADiskRef, void* ctx);
CF_RETURNS_RETAINED static DADissenterRef is_dadisk_busy(DADiskRef, void *ctx);
static Boolean check_vol_busy(struct watchedVol *watched);
static void fsys_changed(CFMachPortRef p, void *msg, CFIndex size, void *info);
static void checkScheduleUpdate(struct watchedVol *watched);
static void check_now(CFRunLoopTimerRef timer, void *ctx);
static Boolean check_rebuild(struct watchedVol*);
static void port_died(CFMachPortRef p, void *info);
static void checkAutoUpdate(CFRunLoopTimerRef t, void *ctx);
static Boolean reconsiderVolumes(mountpoint_t busyVol);
static Boolean checkAllWatched(mountpoint_t busyVol);
static void logMTMessage(char *signature, char *result, char *mfmt, ...);
#define CFRELEASE(x) if (x) { CFRelease(x); x = NULL; }
#if 0
#define twrite(msg) write(STDERR_FILENO, msg, sizeof(msg))
static void debug_chld(int signum) __attribute__((unused))
{
int olderrno = errno;
int status;
pid_t childpid;
if (signum != SIGCHLD)
twrite("debug_chld not registered for signal\n");
else
if ((childpid = waitpid(-1, &status, WNOHANG)) == -1)
twrite("DEBUG: SIGCHLD received, but no children available?\n");
else
if (!WIFEXITED(status))
twrite("DEBUG: child quit on signal?\n");
else
if (WEXITSTATUS(status))
twrite("DEBUG: child exited with unhappy status\n");
else
twrite("DEBUG: child exited with happy status\n");
errno = olderrno;
}
#endif
int kextd_watch_volumes(int sourcePriority)
{
int rval;
char *errmsg;
CFRunLoopRef rl;
const void *keyobjs[2] = { kDADiskDescriptionMediaContentKey,
kDADiskDescriptionVolumePathKey };
CFArrayRef dakeys = NULL;
rval = EEXIST;
errmsg = "kextd_watch_volumes() already initialized";
if (sFsysWatchDict) goto finish;
rval = ELAST + 1;
errmsg = "couldn't create data structures";
sFsysWatchDict = CFDictionaryCreateMutable(nil, 0,
&kCFTypeDictionaryKeyCallBacks, NULL); if (!sFsysWatchDict) goto finish;
sReplyPorts = CFDictionaryCreateMutable(nil, 0,
&kCFTypeDictionaryKeyCallBacks, NULL); if (!sReplyPorts) goto finish;
errmsg = "error setting up ports and sources";
rl = CFRunLoopGetCurrent();
if (!rl) goto finish;
sFsysChangedPort = CFMachPortCreate(nil, fsys_changed, NULL, NULL);
if (!sFsysChangedPort) goto finish;
sFsysChangedSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault,
sFsysChangedPort, sourcePriority++);
if (!sFsysChangedSource) goto finish;
CFRunLoopAddSource(rl, sFsysChangedSource, kCFRunLoopDefaultMode);
errmsg = "couldn't set up diskarb sessions";
sDAApproval = DAApprovalSessionCreate(nil);
if (!sDAApproval) goto finish;
DARegisterDiskUnmountApprovalCallback(sDAApproval,
kDADiskDescriptionMatchVolumeMountable, is_dadisk_busy, NULL);
DAApprovalSessionScheduleWithRunLoop(sDAApproval, rl,kCFRunLoopDefaultMode);
sDASession = DASessionCreate(nil);
if (!sDASession) goto finish;
DARegisterDiskAppearedCallback(sDASession,
kDADiskDescriptionMatchVolumeMountable, vol_appeared, NULL);
if (!(dakeys = CFArrayCreate(nil, keyobjs, 2, &kCFTypeArrayCallBacks)))
goto finish;
DARegisterDiskDescriptionChangedCallback(sDASession,
kDADiskDescriptionMatchVolumeMountable, dakeys, vol_changed, NULL);
DARegisterDiskDisappearedCallback(sDASession,
kDADiskDescriptionMatchVolumeMountable, vol_disappeared, NULL);
DASessionScheduleWithRunLoop(sDASession, rl, kCFRunLoopDefaultMode);
sAutoUpdateDelayer = CFRunLoopTimerCreate(kCFAllocatorDefault,
CFAbsoluteTimeGetCurrent() + kAutoUpdateDelay, 0,
0, sourcePriority++, checkAutoUpdate, NULL);
#pragma unused(sourcePriority)
if (!sAutoUpdateDelayer) {
goto finish;
}
CFRunLoopAddTimer(CFRunLoopGetCurrent(), sAutoUpdateDelayer,
kCFRunLoopDefaultMode);
CFRelease(sAutoUpdateDelayer);
errmsg = NULL;
rval = 0;
finish:
if (rval) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"kextd_watch_volumes: %s.", errmsg);
if (rval != EEXIST) kextd_stop_volwatch();
}
if (dakeys) CFRelease(dakeys);
return rval;
}
static void checkAutoUpdate(CFRunLoopTimerRef timer, void *ctx)
{
mountpoint_t ignore;
if (isBootRootActive() && sBootRootCheckedIn == false) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: no record of Boot!=Root early boot check");
}
sAutoUpdateDelayer = NULL;
(void)checkAllWatched(ignore);
}
static void free_dict_item(const void* key, const void *val, void *c)
{
destroy_watchedVol((struct watchedVol*)val);
}
void kextd_stop_volwatch()
{
CFRunLoopRef rl;
rl = CFRunLoopGetCurrent();
if (rl && sDASession) DASessionUnscheduleFromRunLoop(sDASession, rl,
kCFRunLoopDefaultMode);
if (rl && sDAApproval) DAApprovalSessionUnscheduleFromRunLoop(sDAApproval,
rl, kCFRunLoopDefaultMode);
if (sDASession) {
DAUnregisterCallback(sDASession, vol_disappeared, NULL);
DAUnregisterCallback(sDASession, vol_changed, NULL);
DAUnregisterCallback(sDASession, vol_appeared, NULL);
CFRELEASE(sDASession);
}
if (sDAApproval) {
DAUnregisterApprovalCallback(sDAApproval, is_dadisk_busy, NULL);
CFRELEASE(sDAApproval);
}
if (rl && sFsysChangedSource) CFRunLoopRemoveSource(rl, sFsysChangedSource,
kCFRunLoopDefaultMode);
CFRELEASE(sFsysChangedSource);
CFRELEASE(sFsysChangedPort);
if (sFsysWatchDict) {
CFDictionaryApplyFunction(sFsysWatchDict, free_dict_item, NULL);
CFRELEASE(sFsysWatchDict);
}
return;
}
static void destroy_watchedVol(struct watchedVol *watched)
{
CFIndex ntokens;
int token;
int errnum;
if (watched->tokens) {
ntokens = CFArrayGetCount(watched->tokens);
while(ntokens--) {
token = (int)(intptr_t)CFArrayGetValueAtIndex(watched->tokens,ntokens);
if ( (errnum = notify_cancel(token)))
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"destroy_watchedVol: error %d canceling notification.", errnum);
}
CFRelease(watched->tokens);
}
if (watched->caches) destroyCaches(watched->caches);
free(watched);
}
static struct watchedVol* create_watchedVol(DADiskRef disk)
{
struct watchedVol *watched, *rval = NULL;
char *errmsg;
errmsg = "allocation error";
watched = calloc(1, sizeof(*watched));
if (!watched) goto finish;
errmsg = NULL;
watched->caches = readBootCachesForDADisk(disk); if (!watched->caches) {
goto finish;
}
watched->isBootRoot = hasBootRootBoots(watched->caches, NULL, NULL, NULL);
if (!watched->isBootRoot) {
(void)updateStamps(watched->caches, kBCStampsUnlinkOnly);
}
errmsg = "allocation error";
watched->tokens = CFArrayCreateMutable(nil, 0, NULL);
if (!watched->tokens) goto finish;
errmsg = NULL;
rval = watched;
finish:
if (errmsg) {
if (watched && watched->caches && watched->caches->root[0]) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"%s: %s.", watched->caches->root, errmsg);
} else {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"create_watchedVol(): %s.", errmsg);
}
}
if (!rval && watched) {
destroy_watchedVol(watched);
}
return rval;
}
static int
cleanupPort(CFMachPortRef *port)
{
mach_port_t lport;
if (sReplyPorts)
CFDictionaryRemoveValue(sReplyPorts, *port); CFMachPortSetInvalidationCallBack(*port, NULL); lport = CFMachPortGetPort(*port);
CFRelease(*port);
*port = NULL;
return mach_port_deallocate(mach_task_self(), lport);
}
static int signalWaiter(CFMachPortRef waiter, int status)
{
int rval = KERN_FAILURE;
mach_port_t replyPort;
replyPort=(mach_port_t)(intptr_t)CFDictionaryGetValue(sReplyPorts,waiter);
CFDictionaryRemoveValue(sReplyPorts, waiter);
if (replyPort != MACH_PORT_NULL) {
if (waiter == sRebootWaiter) {
mountpoint_t empty;
rval = lock_reboot_reply(replyPort, KERN_SUCCESS, empty, status);
} else {
rval = lock_volume_reply(replyPort, KERN_SUCCESS, status);
}
}
if (rval) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"signalWaiter failed: %s.",
safe_mach_error_string(rval));
}
return rval;
}
static void handleRebootHandoff()
{
mountpoint_t busyVol;
if (checkAllWatched(busyVol) == EBUSY) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"%s is still busy, delaying reboot.", busyVol);
goto finish;
}
if (signalWaiter(sRebootWaiter, KERN_SUCCESS) == 0) {
sRebootLock = sRebootWaiter;
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"%s up to date; unblocking reboot.", busyVol);
logMTMessage(kMTEndShutdownDelay, "success", "unblocking shutdown");
}
sRebootWaiter = NULL;
finish:
return;
}
static void handleWatchedHandoff(struct watchedVol *watched)
{
CFMachPortRef waiter = NULL;
if (watched->lock)
cleanupPort(&watched->lock);
if (watched->waiters && CFArrayGetCount(watched->waiters)) {
waiter = (CFMachPortRef)CFArrayGetValueAtIndex(watched->waiters, 0);
OSKextLog(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"granting lock for %s to waiter %d", watched->caches->root,
CFMachPortGetPort(waiter));
watched->lock = waiter; CFArrayRemoveValueAtIndex(watched->waiters, 0);
if (signalWaiter(waiter, KERN_SUCCESS)) {
cleanupPort(&watched->lock); }
}
}
static int watch_path(char *path, mach_port_t port, struct watchedVol* watched)
{
int rval = ELAST + 1; char key[PATH_MAX];
int token = 0;
int errnum;
uint64_t state;
if (strlcpy(key, kWatchKeyBase, PATH_MAX) >= PATH_MAX) goto finish;
if (strlcat(key, path, PATH_MAX) >= PATH_MAX) goto finish;
if (notify_register_mach_port(key, &port, NOTIFY_REUSE, &token))
goto finish;
state = (intptr_t)watched;
if (notify_set_state(token, state)) goto finish;
if (notify_monitor_file(token, path, 0)) goto finish;
CFArrayAppendValue(watched->tokens, (void*)(intptr_t)token);
rval = 0;
finish:
if (rval && token != -1 && (errnum = notify_cancel(token)))
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"watch_path: error %d canceling token.", errnum);
return rval;
}
#define WATCH(watched, fullp, relpath, fsPort) do { \
\
COMPILE_TIME_ASSERT(sizeof(fullp) == PATH_MAX); \
if (strlcpy(fullp, watched->caches->root, PATH_MAX) >= PATH_MAX) \
goto finish; \
if (strlcat(fullp, relpath, PATH_MAX) >= PATH_MAX) \
goto finish; \
\
if (watch_path(fullp, fsPort, watched)) \
goto finish; \
} while(0)
#define kInstallCommitPath "/private/var/run/installd.commit.pid"
static void vol_appeared(DADiskRef disk, void *launchCtx)
{
int result = 0; mach_port_t fsPort;
CFDictionaryRef ddesc = NULL;
CFURLRef volURL;
CFBooleanRef traitVal;
CFUUIDRef volUUID;
struct watchedVol *watched = NULL;
Boolean launched = false;
struct bootCaches *caches;
int i;
char path[PATH_MAX];
OSKextLogCFString(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
CFSTR("%s(%@)"), __FUNCTION__, disk);
if (!disk) goto finish;
ddesc = DADiskCopyDescription(disk);
if (!ddesc) goto finish;
volUUID = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeUUIDKey);
if (!volUUID || CFGetTypeID(volUUID) != CFUUIDGetTypeID()) goto finish;
if ((watched = (void *)CFDictionaryGetValue(sFsysWatchDict, volUUID))) {
if (0 == strcmp(watched->caches->root, "/")) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"'/' re-appeared; refusing to lose existing state (8755513) "
"at the risk of missing minor changes (4620558)");
goto finish;
} else {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"WARNING: vol_appeared() removing pre-existing '%s' from"
" watch table.", watched->caches->root);
vol_disappeared(disk, NULL);
}
}
volURL = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumePathKey);
if (!volURL) goto finish;
if (CFURLGetFileSystemRepresentation(volURL,false,(UInt8*)path,PATH_MAX)) {
struct statfs sfs;
if (statfs(path, &sfs) == -1) goto finish;
if (sfs.f_flags & MNT_RDONLY) goto finish; if (sfs.f_flags & MNT_DONTBROWSE) goto finish; } else {
goto finish;
}
traitVal = CFDictionaryGetValue(ddesc, kDADiskDescriptionMediaWritableKey);
if (!traitVal || CFGetTypeID(traitVal) != CFBooleanGetTypeID()) goto finish;
if (CFEqual(traitVal, kCFBooleanFalse)) goto finish;
traitVal = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeNetworkKey);
if (!traitVal || CFGetTypeID(traitVal) != CFBooleanGetTypeID()) goto finish;
if (CFEqual(traitVal, kCFBooleanTrue)) goto finish;
if (!(watched = create_watchedVol(disk))) goto finish;
result = -1; caches = watched->caches;
fsPort = CFMachPortGetPort(sFsysChangedPort);
if (fsPort == MACH_PORT_NULL) goto finish;
WATCH(watched, path, kBootCachesPath, fsPort);
WATCH(watched, path, kInstallCommitPath, fsPort);
char *bufptr;
bufptr = caches->exts;
for (i = 0; i < caches->nexts; i++) {
WATCH(watched, path, bufptr, fsPort);
bufptr += (strlen(bufptr) + 1);
}
WATCH(watched, path, caches->kernel, fsPort);
WATCH(watched, path, caches->locSource, fsPort);
for (i = 0; i < caches->nrps; i++) {
WATCH(watched, path, caches->rpspaths[i].rpath, fsPort);
}
if (caches->efibooter.rpath[0]) {
WATCH(watched, path, caches->efibooter.rpath, fsPort);
}
if (caches->ofbooter.rpath[0]) {
WATCH(watched, path, caches->ofbooter.rpath, fsPort);
}
for (i = 0; i < caches->nmisc; i++) {
WATCH(watched, path, caches->miscpaths[i].rpath, fsPort);
}
CFDictionarySetValue(sFsysWatchDict, volUUID, watched);
if (sAutoUpdateDelayer) {
OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogDirectoryScanFlag,
"ignoring '%s' until boot is complete",
watched->caches->root);
} else {
launched = check_rebuild(watched);
}
if (launchCtx) {
Boolean *didLaunch = launchCtx;
*didLaunch = launched;
}
result = 0;
finish:
if (ddesc) CFRelease(ddesc);
if (result) {
if (watched) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Error setting up notifications on '%s'.",
watched->caches->root);
destroy_watchedVol(watched);
}
}
return;
}
#undef WATCH
static void
check_boots_set_nvram(struct watchedVol *watched)
{
char *volbsdname = watched->caches->bsdname;
CFArrayRef dparts, bparts;
Boolean hasBoots;
Boolean curSDPreferred = true; char *s;
CFStringRef firstPart;
char bsdName[DEVMAXPATHSIZE];
BLContext blctx = { 0, BRBLLogFunc, NULL };
hasBoots = hasBootRootBoots(watched->caches, &bparts, &dparts, NULL);
if (!bparts || !dparts) goto finish;
s = (CFArrayGetCount(bparts) == 1) ? "" : "s";
if (hasBoots) {
if (!watched->isBootRoot) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"%s now has Apple_Boot partition%s", volbsdname, s);
watched->isBootRoot = true;
check_rebuild(watched);
if (0 == strcmp(watched->caches->root, "/") && curSDPreferred) {
firstPart = CFArrayGetValueAtIndex(bparts, 0);
if (!firstPart) goto finish;
if (CFStringGetFileSystemRepresentation(firstPart, bsdName,
DEVMAXPATHSIZE)) {
(void)BLSetEFIBootDevice(&blctx, bsdName);
}
}
}
} else { if (watched->isBootRoot) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"%s lost its Apple_Boot partition%s", volbsdname, s);
watched->isBootRoot = false;
(void)updateStamps(watched->caches, kBCStampsUnlinkOnly);
if (0 == strcmp(watched->caches->root, "/") && curSDPreferred &&
CFArrayGetCount(dparts) == 1) {
firstPart = CFArrayGetValueAtIndex(dparts, 0);
if (!firstPart) goto finish;
if (CFStringGetFileSystemRepresentation(firstPart, bsdName,
DEVMAXPATHSIZE)) {
(void)BLSetEFIBootDevice(&blctx, bsdName);
}
}
}
}
finish:
if (dparts) CFRelease(dparts);
if (bparts) CFRelease(bparts);
}
static void vol_changed(DADiskRef disk, CFArrayRef keys, void* ctx)
{
CFIndex i = CFArrayGetCount(keys);
CFDictionaryRef ddesc = NULL;
CFUUIDRef volUUID;
struct watchedVol *watched;
OSKextLogCFString(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
CFSTR("%s(%@, keys[0] = %@)"), __FUNCTION__, disk,
CFArrayGetValueAtIndex(keys,0));
if (!disk) goto finish;
ddesc = DADiskCopyDescription(disk);
if (!ddesc) goto finish;
volUUID = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeUUIDKey);
if (!volUUID) goto finish;
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID);
while (i--) {
CFTypeRef key = CFArrayGetValueAtIndex(keys, i);
if (CFEqual(key, kDADiskDescriptionVolumePathKey)) {
if (watched)
vol_disappeared(disk, ctx); if (CFDictionaryGetValue(ddesc, key))
vol_appeared(disk, ctx); }
else if (CFEqual(key, kDADiskDescriptionMediaContentKey)) {
if (!watched) {
vol_appeared(disk, ctx);
} else {
check_boots_set_nvram(watched);
}
}
else {
OSKextLogCFString(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
CFSTR("vol_changed: %@: uninteresting key"), key);
}
}
finish:
if (ddesc) CFRelease(ddesc);
}
static void vol_disappeared(DADiskRef disk, void* ctx)
{
CFDictionaryRef ddesc = NULL;
CFUUIDRef volUUID;
struct watchedVol *watched;
OSKextLogCFString(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
CFSTR("%s(%@)"), __FUNCTION__, disk);
if (!disk) goto finish;
ddesc = DADiskCopyDescription(disk);
if (!ddesc) goto finish;
volUUID = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeUUIDKey);
if (!volUUID) goto finish;
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID);
if (!watched) goto finish;
if (0 == strcmp(watched->caches->root, "/")) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"[8755513] vol_disappeared() refusing to stop watching '/'");
goto finish;
}
if (watched->lock) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"WARNING: '%s' is locked; abandoning helper process%s",
watched->caches->root, watched->waiters ? "es" : "");
} else {
OSKextLog(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"ending volume watch of %s", watched->caches->root);
}
CFDictionaryRemoveValue(sFsysWatchDict, volUUID);
if (watched->delayer) {
CFRunLoopTimerInvalidate(watched->delayer); watched->delayer = NULL;
}
if (watched->waiters) {
CFIndex i = CFArrayGetCount(watched->waiters);
CFMachPortRef waiter;
while(i--) {
waiter = (CFMachPortRef)CFArrayGetValueAtIndex(watched->waiters,i);
signalWaiter(waiter, ENOENT);
cleanupPort(&waiter);
}
CFRelease(watched->waiters); }
destroy_watchedVol(watched);
finish:
if (ddesc) CFRelease(ddesc);
return;
}
CF_RETURNS_RETAINED
static DADissenterRef
is_dadisk_busy(DADiskRef disk, void *ctx)
{
int result = 0; DADissenterRef rval = NULL;
CFDictionaryRef ddesc = NULL;
CFUUIDRef volUUID;
struct watchedVol *watched;
OSKextLogCFString(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
CFSTR("%s(%@)"), __FUNCTION__, disk);
if (!disk) goto finish;
ddesc = DADiskCopyDescription(disk);
if (!ddesc) goto finish;
volUUID = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeUUIDKey);
if (!volUUID) goto finish;
result = -1;
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID);
if (!watched) {
vol_appeared(disk, NULL);
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID);
}
if (watched) {
if (watched->lock || (watched->isBootRoot && check_vol_busy(watched))) {
if (watched->updterrs == 0)
watched->updterrs++;
if (watched->caches)
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogCacheFlag,
"%s busy; denying eject (%d tries left)", watched->caches->root,
kMaxUpdateAttempts - watched->updtattempts);
rval = DADissenterCreate(nil, kDAReturnBusy, CFSTR("kextmanager busy"));
if (!rval) goto finish;
}
}
result = 0;
finish:
if (result) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"is_dadisk_busy had trouble answering diskarb.");
}
if (ddesc) CFRelease(ddesc);
return rval; }
static Boolean check_vol_busy(struct watchedVol *watched)
{
Boolean busy = (watched->lock != NULL);
if (busy) goto finish;
if (watched->updterrs < kMaxUpdateFailures &&
watched->updtattempts < kMaxUpdateAttempts) {
busy = check_rebuild(watched);
} else {
OSKextLog( NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"%s: giving up; kextcache hit max %s", watched->caches->root,
watched->updterrs >= kMaxUpdateFailures ? "failures" : "update attempts");
}
finish:
return busy;
}
static void
fsys_changed(CFMachPortRef p, void *m, CFIndex size, void *info)
{
uint64_t nstate;
struct watchedVol *watched;
int notify_status = NOTIFY_STATUS_OK;
int token;
mach_msg_empty_rcv_t *msg = (mach_msg_empty_rcv_t*)m;
token = msg->header.msgh_id;
notify_status = notify_get_state(token, &nstate);
if (NOTIFY_STATUS_OK != notify_status) {
OSKextLogCFString( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
CFSTR("notify_get_state() failed for token %d: status %d."),
token, notify_status);
goto finish;
}
watched = (struct watchedVol*)(intptr_t)nstate;
if (!watched) {
OSKextLogCFString( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
CFSTR("NULL entry for notify token %d."),
token);
goto finish;
}
checkScheduleUpdate(watched);
finish:
return;
}
static void
checkScheduleUpdate(struct watchedVol *watched)
{
char path[PATH_MAX];
struct stat sb;
FILE *f;
if (!CFDictionaryGetCountOfValue(sFsysWatchDict, watched)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Invalid volume: %p.", watched);
goto finish;
}
if (strlcpy(path,watched->caches->root,sizeof(path)) >= sizeof(path))
goto finish;
if (strlcat(path, kInstallCommitPath, sizeof(path)) >= sizeof(path))
goto finish;
if ((f = fopen(path, "r"))) {
pid_t pid;
int nmatched = fscanf(f, "%d", &pid);
fclose(f);
if (nmatched == 1) {
if (0 == kill(pid, 0)) {
OSKextLog(NULL,kOSKextLogDetailLevel|kOSKextLogFileAccessFlag,
"%s: installer active, ignoring changes",
watched->caches->root);
goto finish;
}
}
}
if (strlcpy(path,watched->caches->root,sizeof(path)) >= sizeof(path))
goto finish;
if (strlcat(path, kBootCachesPath, sizeof(path)) >= sizeof(path))
goto finish;
if (stat(path, &sb) != 0) {
if (errno != ENOENT) {
OSKextLogCFString( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
CFSTR("stat failed for %s: %s."), path, strerror(errno));
}
goto finish;
}
if (watched->caches->bcTime.tv_sec != sb.st_mtimespec.tv_sec ||
watched->caches->bcTime.tv_nsec !=sb.st_mtimespec.tv_nsec) {
char * volRoot = watched->caches->root;
DADiskRef disk = NULL;
OSKextLog( NULL,
kOSKextLogBasicLevel | kOSKextLogFileAccessFlag,
"%s: bootcaches.plist changed.", volRoot);
disk = createDiskForMount(sDASession, volRoot);
if (!disk) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag | kOSKextLogIPCFlag,
"Unable to create DADisk.");
goto finish;
}
vol_disappeared(disk, NULL); vol_appeared(disk, NULL); CFRelease(disk);
} else if (sAutoUpdateDelayer == NULL) {
CFRunLoopTimerContext tc = { 0, watched, NULL, NULL, NULL };
CFAbsoluteTime firetime = CFAbsoluteTimeGetCurrent() + kWatchSettleTime;
if (watched->delayer) {
CFRunLoopTimerInvalidate(watched->delayer);
}
watched->delayer=CFRunLoopTimerCreate(nil,firetime,0,0,0,check_now,&tc);
if (!watched->delayer) {
OSKextLogCFString( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
CFSTR("fsys_changed() unable to create timer for watched volume."));
goto finish;
}
CFRunLoopAddTimer(CFRunLoopGetCurrent(), watched->delayer,
kCFRunLoopDefaultMode);
CFRelease(watched->delayer); }
finish:
return;
}
void check_now(CFRunLoopTimerRef timer, void *info)
{
struct watchedVol *watched = (struct watchedVol*)info;
if (watched && CFDictionaryGetCountOfValue(sFsysWatchDict, watched)) {
watched->delayer = NULL; (void)check_rebuild(watched); } else {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"%p's timer fired when it should have been Invalid", watched);
}
}
static Boolean check_rebuild(struct watchedVol *watched)
{
Boolean launched = false;
Boolean rebuild = false;
if (watched->delayer) {
CFRunLoopTimerInvalidate(watched->delayer); watched->delayer = NULL;
}
if (watched->updtattempts > kMaxUpdateAttempts) {
OSKextLog( NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"%s: kextcache has had enough tries; not launching any more",
watched->caches->root);
goto finish;
}
if ((rebuild = check_kext_boot_cache_file(watched->caches,
watched->caches->kext_boot_cache_file->rpath,
watched->caches->kernel))) {
goto dorebuild;
}
check_boots_set_nvram(watched);
if (watched->isBootRoot) {
rebuild = check_csfde(watched->caches) ||
check_loccache(watched->caches) ||
needUpdates(watched->caches, NULL, NULL, NULL,
kOSKextLogProgressLevel | kOSKextLogFileAccessFlag);
if (rebuild)
goto dorebuild;
}
dorebuild:
if (rebuild) {
if (launch_rebuild_all(watched->caches->root, false, false) > 0) {
launched = true;
watched->updtattempts++;
} else {
watched->updterrs++;
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Error launching kextcache -u.");
}
}
if (0 == strcmp(watched->caches->root, "/") &&
plistCachesNeedRebuild(gKernelArchInfo)) {
handleSignal(SIGHUP);
}
finish:
return launched;
}
static void check_locked(const void *key, const void *val, void *ctx)
{
struct watchedVol *watched = (struct watchedVol*)val;
char *busyVol = ctx;
if (check_vol_busy(watched)) {
strlcpy(busyVol, watched->caches->root, sizeof(mountpoint_t));
}
}
static CFMachPortRef
createWatchedPort(mach_port_t mport, void *ctx)
{
CFMachPortRef rval = NULL;
int result = ELAST + 1;
CFRunLoopSourceRef invalidator;
CFMachPortContext mp_ctx = { 0, ctx, 0, };
CFRunLoopRef rl = CFRunLoopGetCurrent();
if (!(rval = CFMachPortCreateWithPort(nil, mport, NULL, &mp_ctx, false)))
goto finish;
invalidator = CFMachPortCreateRunLoopSource(nil, rval, 0);
if (!invalidator) goto finish;
CFMachPortSetInvalidationCallBack(rval, port_died);
CFRunLoopAddSource(rl, invalidator, kCFRunLoopDefaultMode);
CFRelease(invalidator);
result = 0;
finish:
if (result && rval) {
CFRelease(rval);
rval = NULL;
}
return rval;
}
static Boolean
checkAllWatched(mountpoint_t busyVol)
{
int result;
busyVol[0] = '\0';
if (sFsysWatchDict) {
CFDictionaryApplyFunction(sFsysWatchDict, check_locked, busyVol);
}
if (busyVol[0] == '\0') {
result = 0; } else {
result = EBUSY;
}
return result;
}
kern_return_t _kextmanager_lock_reboot(mach_port_t p, mach_port_t replyPort,
mach_port_t client, int waitForLock, mountpoint_t busyVol, int *busyStatus)
{
kern_return_t rval = KERN_FAILURE;
int result = ELAST + 1;
if (!busyStatus) {
result = EINVAL;
rval = KERN_SUCCESS; goto finish;
}
if (gClientUID != 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Non-root doesn't need to lock for reboot.");
result = EPERM;
rval = KERN_SUCCESS; goto finish;
}
if (sRebootLock) {
result = EALREADY;
rval = KERN_SUCCESS; OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"Warning: Reboot lock request while reboot in progress.");
goto finish;
}
if (checkAllWatched(busyVol) || reconsiderVolumes(busyVol)) {
result = EBUSY;
rval = KERN_SUCCESS; } else {
if (!(sRebootLock = createWatchedPort(client, &sRebootLock)))
goto finish;
result = 0; rval = KERN_SUCCESS; }
if (waitForLock && result == EBUSY && sRebootWaiter == NULL) {
if (!(sRebootWaiter = createWatchedPort(client, &sRebootLock)))
goto finish;
CFDictionarySetValue(sReplyPorts, sRebootWaiter, (void*)(intptr_t)replyPort);
rval = MIG_NO_REPLY; } else {
rval = KERN_SUCCESS; }
finish:
if (rval == KERN_SUCCESS) {
if (busyStatus) *busyStatus = result;
} else if (rval != MIG_NO_REPLY) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Error %d locking for reboot.", rval);
}
if (result == EBUSY && waitForLock) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogFileAccessFlag | kOSKextLogIPCFlag,
"'%s' updating, delaying reboot.", busyVol);
logMTMessage(kMTBeginShutdownDelay, "failure",
"kext caches need update at shutdown; delaying");
}
return rval;
}
kern_return_t _kextmanager_lock_volume(mach_port_t p, mach_port_t replyPort,
mach_port_t client, uuid_t vol_uuid, int waitForLock, int *lockStatus)
{
kern_return_t rval = KERN_FAILURE;
int result;
CFUUIDBytes uuidBytes;
CFUUIDRef volUUID;
struct watchedVol *watched = NULL;
struct statfs sfs;
Boolean createdLock = false;
OSKextLog( NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"%s(..%d..)...", __FUNCTION__, client);
if (!lockStatus) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"kextmanager_lock_volume requires non-NULL lockStatus.");
return KERN_INVALID_ARGUMENT; }
if (gClientUID != 0 ) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Non-root doesn't need to lock or unlock volumes.");
result = EPERM;
rval = KERN_SUCCESS; goto finish;
}
if (isBootRootActive() && sBootRootCheckedIn == false) {
sBootRootCheckedIn = true;
}
if (!sFsysWatchDict) {
result = EAGAIN;
rval = KERN_SUCCESS; goto finish;
}
if (sRebootLock) {
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"reboot in progress -> denying volume lock request");
result = ENOLCK;
rval = KERN_SUCCESS; goto finish;
}
memcpy(&uuidBytes.byte0, vol_uuid, sizeof(uuid_t));
volUUID = CFUUIDCreateFromUUIDBytes(nil, uuidBytes);
if (!volUUID) {
result = ENOMEM;
goto finish;
}
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID);
if (!watched) {
OSKextLogCFString(NULL, kOSKextLogBasicLevel | kOSKextLogIPCFlag,
CFSTR("not watching %@ -> no volume lock to grant"), volUUID);
CFRelease(volUUID);
result = ENOENT;
rval = KERN_SUCCESS; goto finish;
} else { CFRelease(volUUID);
}
if (watched->lock == NULL) {
if (!(watched->lock = createWatchedPort(client, watched))) {
rval = KERN_FAILURE; goto finish;
}
createdLock = true;
if (statfs(watched->caches->root, &sfs) == 0) {
uint32_t mntgoal = sfs.f_flags;
mntgoal &= ~(MNT_IGNORE_OWNERSHIP);
if (sfs.f_flags != mntgoal) {
(void)updateMount(watched->caches->root, mntgoal); watched->origMntFlags = sfs.f_flags;
}
}
OSKextLog(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"granting lock for %s to %d", watched->caches->root,client);
result = 0; rval = KERN_SUCCESS; } else {
if (waitForLock) {
rval = MIG_NO_REPLY; } else {
result = EBUSY; rval = KERN_SUCCESS; }
}
if (rval == MIG_NO_REPLY) {
CFMachPortRef waiter;
if (!watched->waiters) {
watched->waiters=CFArrayCreateMutable(0,1,&kCFTypeArrayCallBacks);
if (!watched->waiters) {
rval = KERN_FAILURE;
goto finish;
}
}
if (!(waiter = createWatchedPort(client, watched))) {
rval = KERN_FAILURE;
goto finish;
}
CFArrayAppendValue(watched->waiters, waiter);
CFDictionarySetValue(sReplyPorts, waiter, (void*)(intptr_t)replyPort);
}
finish:
if (rval != KERN_SUCCESS && createdLock && watched->lock) {
cleanupPort(&watched->lock);
}
if (rval == KERN_SUCCESS) {
*lockStatus = result;
} else if (rval == MIG_NO_REPLY) {
} else if (watched) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Error %d locking '%s'.", rval, watched->caches->root);
} else {
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Error %d attempting to lock an un-watched volume.", rval);
}
return rval;
}
kern_return_t _kextmanager_unlock_volume(mach_port_t p, mach_port_t client,
uuid_t vol_uuid, int exitstatus)
{
kern_return_t rval = KERN_FAILURE;
CFUUIDRef volUUID = NULL;
struct watchedVol *watched = NULL;
CFUUIDBytes uuidBytes;
if (mach_port_deallocate(mach_task_self(), client)) goto finish;
if (gClientUID != 0 ) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Non-root doesn't need to lock or unlock volumes.");
rval = KERN_SUCCESS;
goto finish;
}
if (!sFsysWatchDict) goto finish;
memcpy(&uuidBytes.byte0, vol_uuid, sizeof(uuid_t));
volUUID = CFUUIDCreateFromUUIDBytes(nil, uuidBytes);
if (!volUUID) goto finish;
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID);
if (!watched) goto finish;
if (!watched->lock) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"'%s' isn't locked.", watched->caches->root);
goto finish;
}
if (client != CFMachPortGetPort(watched->lock)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"%d didn't lock '%s'.", client, watched->caches->root);
goto finish;
}
if (exitstatus == EX_OK) {
logMTMessage(kMTUpdatedCaches, "success",
"kextcache updated kernel boot caches");
if (watched->updterrs > 0) {
OSKextLog(NULL, kOSKextLogDebugLevel | kOSKextLogCacheFlag,
"kextcache (%d) succeeded with '%s'.",
client, watched->caches->root);
watched->updterrs = 0;
watched->updtattempts = 0;
}
} else {
watched->updterrs++;
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogCacheFlag,
"kextcache error while updating %s (error count: %d)",
watched->caches->root, watched->updterrs);
}
OSKextLog(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"%d unlocked %s", client, watched->caches->root);
if (watched->origMntFlags) {
(void)updateMount(watched->caches->root, watched->origMntFlags);
watched->origMntFlags = 0;
}
handleWatchedHandoff(watched);
if (!sRebootLock && sRebootWaiter)
handleRebootHandoff();
rval = KERN_SUCCESS;
finish:
if (volUUID) CFRelease(volUUID);
if (rval && watched) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Couldn't unlock '%s'.", watched->caches->root);
}
return rval;
}
#if 0
{
mach_port_urefs_t refs = 0xff;
OSKextLog( NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag, "DEBUG: mach_port_get_refs(..%d, send..): %x -> %x ref(s).", client,
mach_port_get_refs(mach_task_self(), client, MACH_PORT_RIGHT_SEND, &refs),refs);
}
#endif
static void port_died(CFMachPortRef cfport, void *info)
{
mach_port_t mport = cfport ? CFMachPortGetPort(cfport) : MACH_PORT_NULL;
struct watchedVol* watched;
if (!info || !cfport) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"port_died() fatal error: invalid data.");
goto finish;
}
if (info == &sRebootLock) {
if (cfport == sRebootLock) {
cleanupPort(&sRebootLock);
} else if (cfport == sRebootWaiter) {
cleanupPort(&sRebootWaiter); } else {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Improperly tracked shutdown/reboot process died.");
}
goto finish;
}
watched = (struct watchedVol*)info;
if (CFDictionaryGetCountOfValue(sFsysWatchDict, watched) == 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Warning: missing context for deallocated helper port.");
cleanupPort(&cfport);
goto finish;
}
if (watched->lock && mport == CFMachPortGetPort(watched->lock)) {
if (watched->origMntFlags) {
(void)updateMount(watched->caches->root, watched->origMntFlags);
watched->origMntFlags = 0;
}
if (watched->lock) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"helper pid %d exited without unlocking '%s'.",
CFMachPortGetPort(watched->lock), watched->caches->root);
watched->updterrs++;
handleWatchedHandoff(watched); if (!sRebootLock && sRebootWaiter)
handleRebootHandoff();
}
} else { CFIndex i;
if (!watched->waiters) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"Warning: presumed waiter died, but no waiters.");
goto finish;
}
for (i = CFArrayGetCount(watched->waiters); i-- > 0;) {
CFMachPortRef waiter;
waiter = (CFMachPortRef)CFArrayGetValueAtIndex(watched->waiters,i);
if (mport == CFMachPortGetPort(waiter)) {
cleanupPort(&waiter); CFArrayRemoveValueAtIndex(watched->waiters, i); goto finish; }
}
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"Warning: %s: unknown helper exited.", watched->caches->root);
}
finish:
return;
}
static Boolean reconsiderVolume(mountpoint_t volToCheck)
{
int result;
Boolean rval = false;
DADiskRef disk = NULL;
CFDictionaryRef dadesc = NULL;
CFUUIDRef volUUID;
struct watchedVol *watched;
result = 0;
disk = createDiskForMount(sDASession, volToCheck);
if (!disk) goto finish;
result = -1;
dadesc = DADiskCopyDescription(disk);
if (!dadesc) goto finish;
volUUID = CFDictionaryGetValue(dadesc, kDADiskDescriptionVolumeUUIDKey);
if (!volUUID) goto finish;
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID);
if (!watched) {
vol_appeared(disk, &rval); } else {
const void *objs[1] = { kDADiskDescriptionMediaContentKey };
CFArrayRef keys;
keys = CFArrayCreate(nil,objs,1,&kCFTypeArrayCallBacks);
if (!keys) goto finish;
vol_changed(disk, keys, NULL);
CFRelease(keys);
}
result = 0;
finish:
if (disk) CFRelease(disk);
if (dadesc) CFRelease(dadesc);
if (result) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Error reconsidering volume %s.", volToCheck);
}
return rval;
}
static Boolean
reconsiderVolumes(mountpoint_t busyVol)
{
Boolean rval = false;
char *errmsg = NULL;
int nfsys, i;
size_t bufsz;
struct statfs *mounts = NULL;
if (!sDASession) goto finish;
errmsg = "Error while getting mount list.";
if (-1 == (nfsys = getfsstat(NULL, 0, MNT_NOWAIT))) goto finish;
bufsz = nfsys * sizeof(struct statfs);
if (!(mounts = malloc(bufsz))) goto finish;
if (-1 == getfsstat(mounts, (int)bufsz, MNT_NOWAIT)) goto finish;
errmsg = NULL; for (i = 0; i < nfsys; i++) {
struct statfs *sfs = &mounts[i];
if (sfs->f_flags & MNT_LOCAL && strcmp(sfs->f_fstypename, "devfs")) {
if (reconsiderVolume(sfs->f_mntonname) && !rval) {
strlcpy(busyVol, sfs->f_mntonname, sizeof(mountpoint_t));
rval = true;
}
}
}
errmsg = NULL;
finish:
if (errmsg) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"%s", errmsg);
}
if (mounts) free(mounts);
return rval;
}
#define kBRNormalUpdate false
#define kBRInvalidateStamps true // cf. kBRUForceUpdateHelpers
static void
updateVolForMedia(const void *object, char *mediaDesc, int matchSize,
CFStringRef matchingKeys[], CFTypeRef matchingValues[],
bool invalidateStamps)
{
char * errorMessage = NULL;
CFDictionaryRef matchPropertyDict = NULL;
CFMutableDictionaryRef matchingDict = NULL;
io_service_t theMedia = MACH_PORT_NULL;
DADiskRef dadisk = NULL;
CFDictionaryRef dadesc = NULL;
CFUUIDRef volUUID; struct watchedVol * watched = NULL;
if (!sFsysWatchDict) goto finish;
errorMessage = "change notification missing object.";
if (!object) {
goto finish;
}
errorMessage = "error creating matching dictionary.";
matchPropertyDict = CFDictionaryCreate(kCFAllocatorDefault,
(void*)matchingKeys, matchingValues, matchSize,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!matchPropertyDict) {
goto finish;
}
matchingDict = CFDictionaryCreateMutable(kCFAllocatorDefault,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!matchingDict) {
goto finish;
}
CFDictionarySetValue(matchingDict, CFSTR(kIOPropertyMatchKey),
matchPropertyDict);
errorMessage = NULL; theMedia = IOServiceGetMatchingService(kIOMasterPortDefault,
matchingDict);
matchingDict = NULL; if (!theMedia) {
goto finish;
}
errorMessage = "unable to get DiskArb info.";
dadisk = DADiskCreateFromIOMedia(nil, sDASession, theMedia);
if (!dadisk) goto finish;
dadesc = DADiskCopyDescription(dadisk);
if (!dadesc) goto finish;
volUUID = CFDictionaryGetValue(dadesc, kDADiskDescriptionVolumeUUIDKey);
if (!volUUID) goto finish;
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID);
if (watched) {
if (invalidateStamps) {
char stampsdir[PATH_MAX];
errorMessage = "error unlinking bootstamps";
if (strlcpy(stampsdir,watched->caches->root,PATH_MAX)>=PATH_MAX ||
strlcat(stampsdir, kTSCacheDir, PATH_MAX) >= PATH_MAX) {
goto finish;
}
if (sdeepunlink(watched->caches->cachefd, stampsdir)) {
goto finish;
}
}
checkScheduleUpdate(watched);
}
errorMessage = NULL;
finish:
if (errorMessage) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"%s: %s", mediaDesc, errorMessage);
}
if (dadesc) CFRelease(dadesc);
if (dadisk) CFRelease(dadisk);
if (theMedia) IOObjectRelease(theMedia);
if (matchingDict) CFRelease(matchingDict);
if (matchPropertyDict) CFRelease(matchPropertyDict);
return;
}
#define RAID_MATCH_SIZE (2)
void updateRAIDSet(
CFNotificationCenterRef center,
void * observer,
CFStringRef name,
const void * object,
CFDictionaryRef userInfo)
{
CFStringRef matchingKeys[RAID_MATCH_SIZE] = {
CFSTR("RAID"),
CFSTR("UUID") };
CFTypeRef matchingValues[RAID_MATCH_SIZE] = {
(CFTypeRef)kCFBooleanTrue,
(CFTypeRef)object };
updateVolForMedia(object, "RAID Volume", RAID_MATCH_SIZE, matchingKeys,
matchingValues, kBRInvalidateStamps);
}
#define CSLV_MATCH_SIZE (2)
void updateCoreStorageVolume(
CFNotificationCenterRef center,
void * observer,
CFStringRef name,
const void * object,
CFDictionaryRef userInfo)
{
CFStringRef matchingKeys[CSLV_MATCH_SIZE] = {
CFSTR("CoreStorage"),
CFSTR("UUID") };
CFTypeRef matchingValues[CSLV_MATCH_SIZE] = {
(CFTypeRef)kCFBooleanTrue,
(CFTypeRef)object };
bool invalidateStamps = false;
if (CFEqual(name, CFSTR(kCoreStorageNotificationLVGChanged))) {
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"LVG changed");
invalidateStamps = true;
}
updateVolForMedia(object, "CoreStorage Volume", CSLV_MATCH_SIZE,
matchingKeys, matchingValues, invalidateStamps);
}
static void logMTMessage(char *signature, char *result, char *mfmt, ...)
{
va_list ap;
aslmsg amsg = asl_new(ASL_TYPE_MSG);
if (!amsg) {
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"asl_new() failed; MessageTracer message not logged");
return;
}
asl_set(amsg, kMessageTracerDomainKey, kMTCachesDomain);
asl_set(amsg, kMessageTracerSignatureKey, signature);
asl_set(amsg, kMessageTracerResultKey, result);
va_start(ap, mfmt);
asl_vlog(NULL , amsg, ASL_LEVEL_NOTICE, mfmt, ap);
va_end(ap);
asl_free(amsg);
}