#include <asl.h>
#include <notify.h> // yay notify_monitor_file (well, someday in the header ;)
#include <string.h> // strerror()
#include <sysexits.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h> // waitpid() (fork/daemon/waitpid)
#include <sys/wait.h> // " "
#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/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 "fork_program.h"
#include "kext_tools_util.h" // fork_program()
#include "kextd_main.h" // handleSignal()
#include "kextd_watchvol.h" // kextd_watch_volumes
#include "bootcaches.h" // struct bootCaches
#include "kextd_globals.h" // gClientUID
#include "kextd_usernotification.h" // kextd_raise_notification
#include "kextmanager_async.h" // lock_*_reply()
#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; Boolean disableOwners; 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);
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 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 toggleOwners(mountpoint_t mount, Boolean enableOwners);
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;
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);
DARegisterDiskDescriptionChangedCallback(sDASession,
kDADiskDescriptionMatchVolumeMountable,
kDADiskDescriptionWatchVolumePath, vol_changed, NULL);
DARegisterDiskDisappearedCallback(sDASession,
kDADiskDescriptionMatchVolumeMountable, vol_disappeared, NULL);
DASessionScheduleWithRunLoop(sDASession, rl, kCFRunLoopDefaultMode);
sAutoUpdateDelayer = CFRunLoopTimerCreate(kCFAllocatorDefault,
CFAbsoluteTimeGetCurrent() + kAutoUpdateDelay, 0,
0, sourcePriority++, checkAutoUpdate, NULL);
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();
}
return rval;
}
static void checkAutoUpdate(CFRunLoopTimerRef timer, void *ctx)
{
mountpoint_t ignore;
if (isBootRootActive() && sBootRootCheckedIn == false) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"WARNING: BootRoot active but kextcache -U never checked in");
}
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 = readCaches(disk); if (!watched->caches) {
goto finish;
}
watched->isBootRoot = hasBootRootBoots(watched->caches, NULL, NULL);
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;
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);
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, 1)) 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 makerootpath(caches, dst, path) do { \
if (strlcpy(dst, caches->root, PATH_MAX) >= PATH_MAX) goto finish; \
if (strlcat(dst, path, PATH_MAX) >= PATH_MAX) goto finish; \
} while(0)
static void vol_appeared(DADiskRef disk, void *launchCtx)
{
int result = 0; mach_port_t fsPort;
CFDictionaryRef ddesc = NULL;
CFBooleanRef traitVal;
CFUUIDRef volUUID;
struct watchedVol *watched = NULL;
Boolean launched = false;
struct bootCaches *caches;
int i;
char path[PATH_MAX];
ddesc = DADiskCopyDescription(disk);
if (!ddesc) goto finish;
volUUID = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeUUIDKey);
if (!volUUID || CFGetTypeID(volUUID) != CFUUIDGetTypeID()) goto finish;
if ((watched = (void *)CFDictionaryGetValue(sFsysWatchDict, volUUID))) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogIPCFlag,
"Warning: removing pre-existing '%s' from watch table.",
watched->caches->root);
vol_disappeared(disk, NULL);
}
if (!CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumePathKey))
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;
makerootpath(caches, path, caches->exts);
if (watch_path(path, fsPort, watched)) goto finish;
for (i = 0; i < caches->nrps; i++) {
makerootpath(caches, path, caches->rpspaths[i].rpath);
if (watch_path(path, fsPort, watched)) goto finish;
}
if (caches->efibooter.rpath[0]) {
makerootpath(caches, path, caches->efibooter.rpath);
if (watch_path(path, fsPort, watched)) goto finish;
}
if (caches->ofbooter.rpath[0]) {
makerootpath(caches, path, caches->ofbooter.rpath);
if (watch_path(path, fsPort, watched)) goto finish;
}
for (i = 0; i < caches->nmisc; i++) {
makerootpath(caches, path, caches->miscpaths[i].rpath);
if (watch_path(path, fsPort, watched)) goto finish;
}
CFDictionarySetValue(sFsysWatchDict, volUUID, watched);
if (sAutoUpdateDelayer == NULL) {
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;
}
static void vol_changed(DADiskRef disk, CFArrayRef keys, void* ctx)
{
CFIndex i = CFArrayGetCount(keys);
CFTypeRef key;
CFDictionaryRef ddesc = DADiskCopyDescription(disk);
CFUUIDRef volUUID;
if (!ddesc) goto finish;
volUUID = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeUUIDKey);
if (!volUUID) goto finish;
while (i--)
if ((key = CFArrayGetValueAtIndex(keys, i)) &&
CFEqual(key, kDADiskDescriptionVolumePathKey)) {
if (CFDictionaryGetValue(sFsysWatchDict, volUUID))
vol_disappeared(disk, ctx);
if (CFDictionaryGetValue(ddesc, key))
vol_appeared(disk, ctx);
} else {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
"vol_changed: ignoring update: no mountpoint change.");
}
finish:
if (ddesc) CFRelease(ddesc);
}
static void vol_disappeared(DADiskRef disk, void* ctx)
{
CFDictionaryRef ddesc = NULL;
CFUUIDRef volUUID;
struct watchedVol *watched;
ddesc = DADiskCopyDescription(disk);
if (!ddesc) goto finish;
volUUID = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeUUIDKey);
if (!volUUID) goto finish;
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID);
if (!watched) goto finish;
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;
}
static DADissenterRef is_dadisk_busy(DADiskRef disk, void *ctx)
{
int result = 0; DADissenterRef rval = NULL;
CFDictionaryRef ddesc = NULL;
CFUUIDRef volUUID;
struct watchedVol *watched;
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)
{
int result = 0;
uint64_t nstate;
struct watchedVol *watched;
int token;
mach_msg_empty_rcv_t *msg = (mach_msg_empty_rcv_t*)m;
char bcPath[PATH_MAX];
struct stat sb;
token = msg->header.msgh_id;
if (notify_get_state(token, &nstate)) goto finish;
result = -1;
watched = (struct watchedVol*)(intptr_t)nstate;
if (!watched) goto finish;
if (!CFDictionaryGetCountOfValue(sFsysWatchDict, watched)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Invalid token/volume: %d, %p.", token, watched);
goto finish;
}
makerootpath(watched->caches, bcPath, kBootCachesPath);
if (stat(bcPath, &sb) != 0) {
if (errno == ENOENT) {
result = 0;
}
goto finish;
}
if (watched->caches->sb.st_mtimespec.tv_sec != sb.st_mtimespec.tv_sec ||
watched->caches->sb.st_mtimespec.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) {
vol_disappeared(disk, NULL); vol_appeared(disk, NULL); CFRelease(disk);
} else {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag | kOSKextLogIPCFlag,
"Unable to create DADisk.");
}
} else {
CFRunLoopTimerContext tc = { 0, watched, NULL, NULL, NULL };
CFAbsoluteTime firetime = CFAbsoluteTimeGetCurrent() + kWatchSettleTime;
if (watched->delayer) {
CFRunLoopTimerInvalidate(watched->delayer);
}
if (sAutoUpdateDelayer == NULL) {
watched->delayer=CFRunLoopTimerCreate(nil,firetime,0,0,0,check_now,&tc);
if (!watched->delayer) goto finish;
CFRunLoopAddTimer(CFRunLoopGetCurrent(), watched->delayer,
kCFRunLoopDefaultMode);
CFRelease(watched->delayer); }
}
result = 0;
finish:
if (result) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag | kOSKextLogIPCFlag,
"Warning: fsys_changed() failed.");
}
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 pid_t rebuild_all(struct bootCaches *caches, Boolean force)
{
pid_t rval = -1;
int argc, argi = 0;
char **kcargs = NULL;
argc = 1 + 1 + 1 + 1 + (force == true) + 1;
kcargs = malloc(argc * sizeof(char*));
if (!kcargs) goto finish;
kcargs[argi++] = "/usr/sbin/kextcache";
kcargs[argi++] = "-F"; if (force)
kcargs[argi++] = "-f";
kcargs[argi++] = "-u";
kcargs[argi++] = caches->root;
kcargs[argi] = NULL;
rval = fork_program(kcargs[0], kcargs, false );
finish:
if (kcargs) free(kcargs);
if (rval < 0)
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Error launching kextcache -u.");
return rval;
}
static Boolean check_rebuild(struct watchedVol *watched)
{
Boolean launched = false;
Boolean rebuild;
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;
}
rebuild = check_mkext(watched->caches);
if (!rebuild && watched->isBootRoot) {
if (needUpdates(watched->caches, &rebuild, NULL, NULL, NULL)) {
rebuild = true;
}
}
if (rebuild) {
if (rebuild_all(watched->caches, 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, MNAMELEN);
}
}
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) {
*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 = NULL;
struct watchedVol *watched = NULL;
struct statfs sfs;
if (!lockStatus) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"kextmanager_lock_volume requires lockStatus != NULL.");
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) {
result = ENOLCK;
rval = KERN_SUCCESS; goto finish;
}
result = ENOMEM;
memcpy(&uuidBytes.byte0, vol_uuid, sizeof(uuid_t));
volUUID = CFUUIDCreateFromUUIDBytes(nil, uuidBytes);
if (!volUUID) goto finish;
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID);
if (!watched) {
result = ENOENT;
rval = KERN_SUCCESS; goto finish;
}
if (watched->lock == NULL) {
if (!(watched->lock = createWatchedPort(client, watched))) {
goto finish;
}
if (statfs(watched->caches->root, &sfs) == 0 &&
(sfs.f_flags & MNT_IGNORE_OWNERSHIP)) {
toggleOwners(watched->caches->root, true); watched->disableOwners = true;
}
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 (volUUID) CFRelease(volUUID);
if (rval == KERN_SUCCESS) {
*lockStatus = result;
} else if (rval != MIG_NO_REPLY && result != EPERM) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Error %d locking '%s'.", rval, watched->caches->root);
if (watched->lock)
cleanupPort(&watched->lock);
}
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",
"updated kernel boot caches");
if (watched->updterrs > 0) {
OSKextLog( NULL, kOSKextLogWarningLevel | kOSKextLogCacheFlag,
"kextcache succeeded with '%s'.",
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);
}
if (watched->disableOwners) {
toggleOwners(watched->caches->root, false); watched->disableOwners = false;
}
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->disableOwners) {
toggleOwners(watched->caches->root, false); watched->disableOwners = false;
}
if (watched->lock) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"%p (kextcache) exited without unlocking '%s'.",
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 = 0;
Boolean rval = false;
DADiskRef disk = NULL;
CFDictionaryRef dadesc = NULL;
CFUUIDRef volUUID;
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;
if (!CFDictionaryGetValue(sFsysWatchDict, volUUID)) {
vol_appeared(disk, &rval);
}
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, 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, MNAMELEN);
rval = true;
}
}
}
errmsg = NULL;
finish:
if (errmsg) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"%s", errmsg);
}
if (mounts) free(mounts);
return rval;
}
static void toggleOwners(mountpoint_t mount, Boolean enableOwners)
{
int result = ELAST + 1;
DASessionRef session = NULL;
CFStringRef toggleMode = CFSTR("toggleOwnersMode");
CFURLRef volURL;
DADiskRef disk = NULL;
DADissenterRef dis = (void*)kCFNull;
CFStringRef mountargs[] = { CFSTR("update"), NULL, NULL };
if (enableOwners) {
mountargs[1] = CFSTR("owners");
} else {
mountargs[1] = CFSTR("noowners");
}
if (!(session = DASessionCreate(nil))) goto finish;
DASessionScheduleWithRunLoop(session, CFRunLoopGetCurrent(), toggleMode);
volURL=CFURLCreateFromFileSystemRepresentation(nil,(void*)mount,MNAMELEN,1);
if (!(volURL)) goto finish;
if (!(disk = DADiskCreateFromVolumePath(nil, session, volURL))) goto finish;
DADiskMountWithArguments(disk, NULL, kDADiskMountOptionDefault, _daDone,
&dis, mountargs);
while (dis == (void*)kCFNull) {
CFRunLoopRunInMode(toggleMode, 0, true); }
if (dis) goto finish;
result = 0;
finish:
if (dis && dis != (void*)kCFNull) CFRelease(dis);
if (disk) CFRelease(disk);
if (session) CFRelease(session);
if (result) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
"Warning: couldn't %s owners for %s.",
enableOwners ? "enable":"disable", mount);
}
}
#define RAID_MATCH_SIZE (2)
void updateRAIDSet(
CFNotificationCenterRef center,
void * observer,
CFStringRef name,
const void * object,
CFDictionaryRef userInfo)
{
char * errorMessage = NULL;
CFStringRef matchingKeys[RAID_MATCH_SIZE] = {
CFSTR("RAID"),
CFSTR("UUID") };
CFTypeRef matchingValues[RAID_MATCH_SIZE] = {
(CFTypeRef)kCFBooleanTrue,
(CFTypeRef)object };
CFDictionaryRef matchPropertyDict = NULL;
CFMutableDictionaryRef matchingDict = NULL;
io_service_t theRAIDSet = MACH_PORT_NULL;
DADiskRef dadisk = NULL;
CFDictionaryRef dadesc = NULL;
CFUUIDRef volUUID; struct watchedVol * watched = NULL;
if (!sFsysWatchDict) goto finish;
errorMessage = "No RAID set named in RAID set changed notification.";
if (!object) {
goto finish;
}
errorMessage = "Unable to create matching dictionary for RAID set.";
matchPropertyDict = CFDictionaryCreate(kCFAllocatorDefault,
(const void **)&matchingKeys,
(const void **)&matchingValues,
RAID_MATCH_SIZE,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!matchPropertyDict) {
goto finish;
}
matchingDict = CFDictionaryCreateMutable(kCFAllocatorDefault,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!matchingDict) {
goto finish;
}
CFDictionarySetValue(matchingDict, CFSTR(kIOPropertyMatchKey),
matchPropertyDict);
errorMessage = NULL; theRAIDSet = IOServiceGetMatchingService(kIOMasterPortDefault,
matchingDict);
matchingDict = NULL; if (!theRAIDSet) {
goto finish;
}
errorMessage = "Unable to get DiskArb info for raid set object.";
dadisk = DADiskCreateFromIOMedia(nil, sDASession, theRAIDSet);
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) {
(void)rebuild_all(watched->caches, true );
}
errorMessage = NULL;
finish:
if (errorMessage) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"%s", errorMessage);
}
if (dadesc) CFRelease(dadesc);
if (dadisk) CFRelease(dadisk);
if (theRAIDSet) IOObjectRelease(theRAIDSet);
if (matchingDict) CFRelease(matchingDict);
if (matchPropertyDict) CFRelease(matchPropertyDict);
return;
}
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);
}