#include <notify.h> // yay notify_monitor_file (well, maybe someday ;)
#include <string.h> // strerror()
#include <sysexits.h>
#include <sys/errno.h>
#include <sys/stat.h>
#include <sys/types.h> // waitpid() (fork/daemon/waitpid)
#include <sys/wait.h> // " "
#include <stdlib.h> // daemon(3)
#include <unistd.h> // e.g. execv
#include <bless.h>
#include <CoreFoundation/CoreFoundation.h>
#include <DiskArbitration/DiskArbitration.h>
#include <IOKit/kext/kextmanager_types.h>
extern uint32_t notify_monitor_file(int token, const char *name, int flags);
#ifdef MAC_OS_X_VERSION_10_5
extern uint32_t notify_set_state(int token, uint64_t state);
extern uint32_t notify_get_state(int token, uint64_t *state);
#else
extern uint32_t notify_set_state(int token, int state);
extern uint32_t notify_get_state(int token, int *state);
#endif
#include "watchvol.h" // kextd_watch_volumes
#include "bootroot.h" // watchedVol
#include "logging.h"
#include "globals.h" // gClientUID
#define kWatchKeyBase "com.apple.system.kextd.fswatch"
#define kWatchSettleTime 5
struct watchedVol {
CFRunLoopTimerRef delayer; CFMachPortRef lock; int errcount; Boolean disableOwners;
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 CFMachPortRef sRebootLock = NULL;
static struct watchedVol* create_watchedVol(CFURLRef volURL);
static void destroy_watchedVol(struct watchedVol *watched);
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*, Boolean force);
static void lock_died(CFMachPortRef p, void *info);
static Boolean reconsiderVolumes(dev_path_t busyDev);
static Boolean reconsiderVolume(dev_path_t volDev);
static void toggleOwners(dev_path_t disk, Boolean enableOwners);
#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 = ELAST + 1;
char *errmsg;
CFRunLoopRef rl;
errmsg = "already watching volumes?!";
if (sFsysWatchDict) goto finish;
errmsg = "couldn't create data structures";
sFsysWatchDict = CFDictionaryCreateMutable(nil, 0,
&kCFTypeDictionaryKeyCallBacks, NULL); if (!sFsysWatchDict) goto finish;
errmsg = "trouble 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);
errmsg = NULL;
rval = 0;
finish:
if (rval) {
kextd_error_log("kextd_watch_volumes: %s", errmsg);
kextd_stop_volwatch();
}
return rval;
}
int kextd_giveup_volwatch()
{
int rval = ENOMEM;
sFsysWatchDict = CFDictionaryCreateMutable(nil, 0,
&kCFTypeDictionaryKeyCallBacks, NULL);
if (sFsysWatchDict) {
rval = 0;
} else {
kextd_error_log("giveup_volwatch(): allocation failure");
}
return rval;
}
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;
if (watched->tokens) {
ntokens = CFArrayGetCount(watched->tokens);
while(ntokens--) {
token = (int)CFArrayGetValueAtIndex(watched->tokens,ntokens);
if ( notify_cancel(token))
kextd_error_log("destroy_watchedVol: "
"trouble canceling notification?");
}
CFRelease(watched->tokens);
}
if (watched->caches) destroyCaches(watched->caches);
}
static struct watchedVol* create_watchedVol(CFURLRef volURL)
{
struct watchedVol *watched, *rval = NULL;
char *errmsg = NULL;
char rootpath[PATH_MAX] = { '\0' };
Boolean isGPT = false;
char *bsdname;
Boolean ownersIgnored = false;
struct statfs sfs;
errmsg = "allocation error";
watched = calloc(1, sizeof(*watched));
if (!watched) goto finish;
if (!CFURLGetFileSystemRepresentation(volURL, false, (UInt8*)rootpath, PATH_MAX)) goto finish;
if (!isBootRoot(rootpath, &isGPT) || !isGPT) {
errmsg = NULL;
goto finish;
}
watched->tokens = CFArrayCreateMutable(nil, 0, NULL);
if (!watched->tokens) goto finish;
if (statfs(rootpath, &sfs)) goto finish;
if ((bsdname = strstr(sfs.f_mntfromname, "disk"))) {
ownersIgnored = ((sfs.f_flags & MNT_IGNORE_OWNERSHIP) != 0);
if (ownersIgnored) toggleOwners(bsdname, true);
}
errmsg = NULL; watched->caches = readCaches(rootpath);
if (!watched->caches) goto finish;
rval = watched;
finish:
if (ownersIgnored) toggleOwners(bsdname, false); if (errmsg) {
if (rootpath[0]) {
kextd_error_log("%s: %s", rootpath, errmsg);
} else {
kextd_error_log("create_watchedVol(): %s", errmsg);
}
}
if (!rval && watched) {
destroy_watchedVol(watched);
}
return rval;
}
#define cleanupLock(/*CFMachPortRef*/ lock) \
{ \
mach_port_t lport; \
\
if (lock) { \
lport = CFMachPortGetPort(lock); \
CFRelease(lock); \
lock = NULL; \
mach_port_deallocate(mach_task_self(), lport); \
} \
}
static int watch_path(char *path, mach_port_t port, struct watchedVol* watched)
{
int rval = ELAST + 1; char key[PATH_MAX];
int token = 0;
#ifdef MAC_OS_X_VERSION_10_5
uint64_t state;
#else
int state;
#endif
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*)token);
rval = 0;
finish:
if (rval && token != -1 && notify_cancel(token))
kextd_error_log("watch_path: trouble canceling token?");
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 *ctx)
{
int result = 0; mach_port_t fsPort;
CFDictionaryRef ddesc = NULL;
CFBooleanRef traitVal;
CFURLRef volURL;
CFStringRef bsdName;
struct watchedVol *watched = NULL;
struct bootCaches *caches;
int i;
char path[PATH_MAX];
ddesc = DADiskCopyDescription(disk);
if (!ddesc) goto finish;
volURL = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumePathKey);
if (!volURL || CFGetTypeID(volURL) != CFURLGetTypeID()) goto finish;
bsdName = CFDictionaryGetValue(ddesc, kDADiskDescriptionMediaBSDNameKey);
if (!bsdName || CFGetTypeID(bsdName) != CFStringGetTypeID()) goto finish;
if (CFDictionaryGetValue(sFsysWatchDict, bsdName)) {
kextd_error_log("refreshing watch of volume already in watch table?");
vol_disappeared(disk, ctx); }
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(volURL))) 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, bsdName, watched);
(void)check_rebuild(watched, false);
result = 0;
finish:
if (ddesc) CFRelease(ddesc);
if (result) {
if (watched) {
kextd_error_log("trouble 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);
CFStringRef bsdName = NULL;
if (!ddesc) goto finish;
bsdName = CFDictionaryGetValue(ddesc,kDADiskDescriptionMediaBSDNameKey);
if (!bsdName) goto finish;
while (i--)
if ((key = CFArrayGetValueAtIndex(keys, i)) &&
CFEqual(key, kDADiskDescriptionVolumePathKey)) {
if (CFDictionaryGetValue(sFsysWatchDict, bsdName))
vol_disappeared(disk, ctx);
if (CFDictionaryGetValue(ddesc, key))
vol_appeared(disk, ctx);
} else {
kextd_log("vol_changed: ignoring update: no mountpoint change");
}
finish:
if (ddesc) CFRelease(ddesc);
}
static void vol_disappeared(DADiskRef disk, void* ctx)
{
int result = 0; CFDictionaryRef ddesc = NULL;
CFStringRef bsdName;
struct watchedVol *watched;
ddesc = DADiskCopyDescription(disk);
if (!ddesc) goto finish;
bsdName = CFDictionaryGetValue(ddesc, kDADiskDescriptionMediaBSDNameKey);
if (!bsdName || CFGetTypeID(bsdName) != CFStringGetTypeID()) goto finish;
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, bsdName);
if (!watched) goto finish;
CFDictionaryRemoveValue(sFsysWatchDict, bsdName);
if (watched->delayer) {
CFRunLoopTimerInvalidate(watched->delayer); watched->delayer = NULL;
}
cleanupLock(watched->lock);
destroy_watchedVol(watched);
result = 0;
finish:
if (result)
kextd_error_log("vol_disappeared: unexpected error");
if (ddesc) CFRelease(ddesc);
return;
}
static DADissenterRef is_dadisk_busy(DADiskRef disk, void *ctx)
{
int result = 0; DADissenterRef rval = NULL;
CFDictionaryRef ddesc = NULL;
CFStringRef bsdName = NULL;
struct watchedVol *watched;
ddesc = DADiskCopyDescription(disk);
if (!ddesc) goto finish;
bsdName = CFDictionaryGetValue(ddesc, kDADiskDescriptionMediaBSDNameKey);
if (!bsdName || CFGetTypeID(bsdName) != CFStringGetTypeID()) goto finish;
result = -1;
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, bsdName);
if (watched && check_vol_busy(watched)) {
rval = DADissenterCreate(nil, kDAReturnBusy, CFSTR("kextmanager busy"));
if (!rval) goto finish;
}
result = 0;
finish:
if (result) kextd_error_log("is_dadisk_busy had trouble answering diskarb");
if (ddesc) CFRelease(ddesc);
return rval; }
static Boolean check_vol_busy(struct watchedVol *watched)
{
Boolean rval = (watched->lock != NULL);
if (!rval)
rval = check_rebuild(watched, false);
return rval;
}
static void fsys_changed(CFMachPortRef p, void *m, CFIndex size, void *info)
{
int result = -1;
#ifdef MAC_OS_X_VERSION_10_5
uint64_t nstate;
#else
int nstate;
#endif
struct watchedVol *watched;
int token;
mach_msg_empty_rcv_t *msg = (mach_msg_empty_rcv_t*)m;
token = msg->header.msgh_id;
if (notify_get_state(token, &nstate)) goto finish;
watched = (struct watchedVol*)(intptr_t)nstate;
if (!watched) goto finish;
if (CFDictionaryGetCountOfValue(sFsysWatchDict, watched)) {
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) goto finish;
CFRunLoopAddTimer(CFRunLoopGetCurrent(), watched->delayer,
kCFRunLoopDefaultMode);
CFRelease(watched->delayer); } else
kextd_error_log("invalid token/volume: %d, %p", token, watched);
result = 0;
finish:
if (result)
kextd_error_log("couldn't respond to filesystem change notification!");
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, false); }
}
static int rebuild_boot(struct bootCaches *caches, Boolean force)
{
int rval = ELAST + 1;
pid_t pid;
int argc, argi = 0;
char **kcargs;
argc = 1 + 1 + 1 + 1;
kcargs = malloc(argc * sizeof(char*));
if (!kcargs) goto finish;
kcargs[argi++] = "kextcache";
if (force) {
kcargs[argi++] = "-f";
}
kcargs[argi++] = "-u";
kcargs[argi++] = caches->root;
kcargs[argi] = NULL;
rval = 0;
pid = fork_kextcache(caches->root, kcargs, false);
finish:
if (rval) {
kextd_error_log("data error before rebuilding boot partition");
} else if (pid < 0) {
rval = pid;
}
return rval;
}
static Boolean check_rebuild(struct watchedVol *watched, Boolean force)
{
Boolean launched = false;
Boolean rebuildmkext = force;
if (watched->delayer) {
CFRunLoopTimerInvalidate(watched->delayer); watched->delayer = NULL;
}
#if 0 // no one calls us with force right now
if (force) {
char bspath[PATH_MAX];
if (snprintf(bspath, PATH_MAX, "%s/%s", watched->caches->root,
kTSCacheDir) < PATH_MAX) {
if (sdeepunlink(watched->caches->cachefd, bspath)) {
kextd_error_log("couldn't nuke bootstamps: %d", errno);
}
} else {
kextd_error_log("couldn't build Extensions path");
}
}
#endif
if (!rebuildmkext) rebuildmkext = check_mkext(watched->caches);
if (rebuildmkext) {
if (rebuild_mkext(watched->caches, false )) { watched->errcount++; } else {
launched = true;
}
} else {
char bsdname[DEVMAXPATHSIZE];
struct stat sb;
CFDictionaryRef binfo = NULL;
Boolean hasBoots, isGPT;
if (fstat(watched->caches->cachefd, &sb) == 0 &&
devname_r(sb.st_dev, S_IFBLK, bsdname, DEVMAXPATHSIZE) &&
BLCreateBooterInformationDictionary(NULL,bsdname,&binfo) == 0) {
CFArrayRef ar;
ar = CFDictionaryGetValue(binfo, kBLAuxiliaryPartitionsKey);
hasBoots = (ar && CFArrayGetCount(ar) > 0);
ar = CFDictionaryGetValue(binfo, kBLSystemPartitionsKey);
isGPT = (ar && CFArrayGetCount(ar) > 0);
if (hasBoots && isGPT) {
Boolean anyOutOfDate = true;
if (needUpdates(watched->caches,&anyOutOfDate,NULL,NULL,NULL)) {
anyOutOfDate = true;
}
if (force || anyOutOfDate) {
launched = (rebuild_boot(watched->caches, force) == 0);
}
}
}
if (binfo) CFRelease(binfo);
}
return launched;
}
static int lock_vol(struct watchedVol *watched, mach_port_t client)
{
int rval = ENOMEM;
CFRunLoopSourceRef invalidator;
CFMachPortContext mp_ctx = { 0, watched, NULL, NULL, NULL };
CFRunLoopRef rl = CFRunLoopGetCurrent();
if (!rl) goto finish;
watched->lock = CFMachPortCreateWithPort(nil,client,NULL,&mp_ctx,false);
if (!watched->lock) goto finish;
CFMachPortSetInvalidationCallBack(watched->lock, lock_died);
invalidator = CFMachPortCreateRunLoopSource(nil, watched->lock, 0);
if (!invalidator) goto finish;
CFRunLoopAddSource(rl, invalidator, kCFRunLoopDefaultMode);
CFRelease(invalidator);
rval = 0;
finish:
return rval;
}
#define GIVEUPTHRESH 5
static void check_locked(const void *key, const void *val, void *ctx)
{
struct watchedVol *watched = (struct watchedVol*)val;
const void **bsdName = ctx;
if (watched->lock ||
(watched->errcount < GIVEUPTHRESH && check_rebuild(watched, false)))
*bsdName = key;
}
kern_return_t _kextmanager_lock_reboot(mach_port_t p, mach_port_t client,
dev_path_t busyDev, int *busyStatus)
{
kern_return_t rval = KERN_FAILURE;
int result = ELAST + 1;
CFStringRef bsdName = NULL;
if (!busyDev || !busyStatus) {
rval = KERN_SUCCESS;
result = EINVAL;
goto finish;
}
if (gClientUID != 0) {
kextd_error_log("non-root doesn't need to lock or unlock volumes");
rval = KERN_SUCCESS;
result = EPERM;
goto finish;
}
if (sRebootLock) {
rval = KERN_SUCCESS; result = EBUSY; busyDev[0] = '\0';
goto finish;
}
if (reconsiderVolumes(busyDev)) {
rval = KERN_SUCCESS; result = EBUSY; goto finish;
}
if (sFsysWatchDict) {
CFDictionaryApplyFunction(sFsysWatchDict, check_locked, &bsdName);
}
if (bsdName == NULL) {
CFRunLoopSourceRef invalidator;
CFMachPortContext mp_ctx = { 0, &sRebootLock, 0, };
CFRunLoopRef rl = CFRunLoopGetCurrent();
if (!rl) goto finish;
sRebootLock = CFMachPortCreateWithPort(nil,client,NULL,&mp_ctx,false);
if (!sRebootLock) goto finish;
CFMachPortSetInvalidationCallBack(sRebootLock, lock_died);
invalidator = CFMachPortCreateRunLoopSource(nil, sRebootLock, 0);
if (!invalidator) goto finish;
CFRunLoopAddSource(rl, invalidator, kCFRunLoopDefaultMode);
CFRelease(invalidator);
result = 0; } else {
result = EBUSY;
if(!CFStringGetFileSystemRepresentation(bsdName, busyDev,
DEVMAXPATHSIZE)) busyDev[0] = '\0';
}
rval = KERN_SUCCESS;
finish:
if (rval == KERN_SUCCESS) {
*busyStatus = result;
} else {
kextd_error_log("error locking for reboot");
}
if (result == EBUSY && bsdName)
kextd_log("%s was busy, preventing lock for reboot", busyDev);
return rval;
}
kern_return_t _kextmanager_lock_volume(mach_port_t p, mach_port_t client,
dev_path_t volDev, int *lockstatus)
{
kern_return_t rval = KERN_FAILURE;
int result;
CFStringRef bsdName = NULL;
struct watchedVol *watched = NULL;
struct statfs sfs;
if (!lockstatus) {
kextd_error_log("kextmanager_lock_volume requires lockstatus != NULL");
rval = KERN_SUCCESS;
result = EINVAL;
}
if (gClientUID != 0 ) {
kextd_error_log("non-root doesn't need to lock or unlock volumes");
rval = KERN_SUCCESS;
result = EPERM;
goto finish;
}
if (!sFsysWatchDict || sRebootLock) {
rval = KERN_SUCCESS; result = EBUSY; goto finish;
}
result = ENOMEM;
bsdName = CFStringCreateWithFileSystemRepresentation(nil, volDev);
if (!bsdName) goto finish;
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, bsdName);
if (!watched) {
rval = KERN_SUCCESS;
result = ENOENT;
goto finish;
}
if (watched->lock) {
result = EBUSY;
} else {
if (lock_vol(watched, client)) goto finish;
result = 0;
}
if (statfs(watched->caches->root, &sfs) == 0 &&
(sfs.f_flags & MNT_IGNORE_OWNERSHIP)) {
toggleOwners(volDev, true);
watched->disableOwners = true;
}
rval = KERN_SUCCESS;
finish:
if (bsdName) CFRelease(bsdName);
if (rval) {
if (gClientUID == 0)
kextd_error_log("trouble while locking %s", volDev);
cleanupLock(watched->lock);
} else {
*lockstatus = result; }
return rval;
}
kern_return_t _kextmanager_unlock_volume(mach_port_t p, mach_port_t client,
dev_path_t volDev, int exitstatus)
{
kern_return_t rval = KERN_FAILURE;
CFStringRef bsdName = NULL;
struct watchedVol *watched = NULL;
if (mach_port_deallocate(mach_task_self(), client)) goto finish;
if (gClientUID != 0 ) {
kextd_error_log("non-root doesn't need to lock or unlock volumes");
rval = KERN_SUCCESS;
goto finish;
}
if (!sFsysWatchDict) goto finish;
bsdName = CFStringCreateWithFileSystemRepresentation(nil, volDev);
if (!bsdName) goto finish;
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, bsdName);
if (!watched) goto finish;
if (!watched->lock) {
kextd_error_log("%s isn't locked", watched->caches->root);
goto finish;
}
if (client != CFMachPortGetPort(watched->lock)) {
kextd_error_log("%p not used to lock %s", client,
watched->caches->root);
goto finish;
}
if (exitstatus) {
if (exitstatus == EX_TEMPFAIL) {
} else {
kextd_log("kextcache reported a problem updating %s", volDev);
watched->errcount++;
}
} else if (watched->errcount) {
kextd_log("kextcache succeeded with %s (previously failed)", volDev);
watched->errcount = 0;
}
if (watched->disableOwners) {
toggleOwners(volDev, false);
watched->disableOwners = false;
}
cleanupLock(watched->lock);
rval = KERN_SUCCESS;
finish:
if (bsdName) CFRelease(bsdName);
if (rval && watched) {
kextd_error_log("couldn't unlock %s", watched->caches->root);
}
return rval;
}
static void lock_died(CFMachPortRef p, void *info)
{
struct watchedVol* watched = (struct watchedVol*)info;
if (info == &sRebootLock) {
kextd_log("reboot/shutdown should have rebooted instead of dying");
cleanupLock(sRebootLock); } else if (!watched) {
kextd_error_log("lock_died: NULL info??");
} else if (CFDictionaryGetCountOfValue(sFsysWatchDict, watched) == 0) {
} else if (watched->lock) {
kextd_error_log("child exited w/o releasing lock on %s",
watched->caches->root);
if (watched->disableOwners) {
struct statfs sfs;
char *bsdname;
if (statfs(watched->caches->root, &sfs) == 0 &&
(bsdname = strstr(sfs.f_mntfromname, "disk"))) {
toggleOwners(bsdname, false);
watched->disableOwners = false;
}
}
cleanupLock(watched->lock);
}
}
static Boolean reconsiderVolume(dev_path_t volDev)
{
int result = -1;
Boolean rval = false;
CFStringRef bsdName = NULL;
struct watchedVol *watched;
DADiskRef disk = NULL;
bsdName = CFStringCreateWithCString(nil, volDev, kCFStringEncodingASCII);
if (!bsdName) goto finish;
if (!CFDictionaryGetValue(sFsysWatchDict, bsdName)) {
if (!(disk = DADiskCreateFromBSDName(nil, sDASession, volDev)))
goto finish;
vol_appeared(disk, NULL);
if ((watched = (void*)CFDictionaryGetValue(sFsysWatchDict, bsdName))) {
rval = check_rebuild(watched, false); }
}
result = 0;
finish:
if (disk) CFRelease(disk);
if (bsdName) CFRelease(bsdName);
if (result) {
kextd_error_log("error reconsidering volume %d");
}
return rval;
}
static Boolean reconsiderVolumes(dev_path_t busyDev)
{
Boolean rval = false;
char *errmsg = NULL;
int nfsys, i;
size_t bufsz;
struct statfs *mounts;
char *bsdname;
if (!sDASession) goto finish;
errmsg = "error while getting mount list";
if (-1 == (nfsys = getfsstat(NULL, 0, 0))) 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 &&
(bsdname = strstr(sfs->f_mntfromname, "disk"))) {
if (reconsiderVolume(bsdname)) {
rval = true;
strlcpy(busyDev, bsdname, DEVMAXPATHSIZE);
}
}
}
errmsg = NULL;
finish:
if (errmsg) kextd_error_log(errmsg);
return rval;
}
static void toggleOwners(dev_path_t volDev, Boolean enableOwners)
{
int result = ELAST + 1;
DASessionRef session = NULL;
CFStringRef toggleMode = CFSTR("toggleOwnersMode");
DADiskRef disk = NULL;
DADissenterRef dis = (void*)kCFNull;
CFStringRef mountargs[] = { CFSTR("update"), NULL, NULL };
if (enableOwners) {
mountargs[1] = CFSTR("perm");
} else {
mountargs[1] = CFSTR("noperm");
}
if (!(session = DASessionCreate(nil))) goto finish;
DASessionScheduleWithRunLoop(session, CFRunLoopGetCurrent(), toggleMode);
if (!(disk = DADiskCreateFromBSDName(nil, session, volDev))) 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)
kextd_log("WARNING: couldn't %s owners for %s",
enableOwners ? "enable":"disable", volDev);
}
#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;
CFStringRef bsdName = NULL;
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 = "Missing BSD Name for updated RAID set.";
bsdName = IORegistryEntryCreateCFProperty(theRAIDSet,
CFSTR("BSD Name"),
kCFAllocatorDefault,
0);
if (!bsdName) {
goto finish;
}
watched = (void*)CFDictionaryGetValue(sFsysWatchDict, bsdName);
if (watched) {
(void)rebuild_boot(watched->caches, true );
}
errorMessage = NULL;
finish:
if (errorMessage) {
kextd_error_log(errorMessage);
}
if (matchPropertyDict) CFRelease(matchPropertyDict);
if (matchingDict) CFRelease(matchingDict);
if (theRAIDSet) IOObjectRelease(theRAIDSet);
if (bsdName) CFRelease(bsdName);
return;
}