1/* 2 * Copyright (c) 2007 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23/* 24 * FILE: watchvol.c 25 * AUTH: Soren Spies (sspies) 26 * DATE: 5 March 2006 27 * DESC: watch volumes as they come, go, or are changed, fire rebuilds 28 * NOTE: everything in this file should happen on the main thread 29 * 30 * Here's roughly how it all works. 31 * 1. sign up for diskarb notifications 32 * 2. generate a data structure for each incoming comprehensible OS volume 33 * 2a. set up notifications for all relevant paths on said volume 34 * [notifications <-> structures] 35 * (2) uses bootcaches.plist to describe what caches a system needs. 36 * All top-level keys are assumed required (which means the mkext could 37 * get fancier in the future if an old-fashioned mkext was still okay). 38 * If keys exist that can't be understood or don't parse correctly, 39 * we bail on watching that volume. 40 * 41 * 3. intelligently respond to notifications 42 * 3a. set up a timer to fire so the system has time to settle 43 * 3b. upon lazy firing, rebuild caches OR copy files to Apple_Boot 44 * 3c. if someone tries to unmount a BootRoot volume, cancel any timer and check 45 * 3d. if a locker unlocks happily, cancel any timer and check (TODO) 46 * 3e. if a locker unlocks unhappily, need to force a check of non-caches? (???) 47 * 3f. we don't care if the volume is locked; additional kextcaches wait 48 * (3d) has the effect that the first kextcache effectively triggers the 49 * second one which copies caches down. It also allows us ... to be 50 * smart about things like forcing reboots if we booted from staleness. 51 * 52 * 4. arbitrate kextcache locks 53 * 4a. keep a Mach send right to a receive right in the locker 54 * 4b. detect crashes via CFMachPortInvalidaton callback 55 * 4c. take success information on unlock 56 * 4d. if a lock was lost, force a rebuild (XX)? 57 * 58 * 5. keep structures up to date 59 * 5a. clean up when a volume goes away 60 * 5b. disappear/appear whenever there's a change 61 * 62 * 6. reboot stuff: take a big lock; free it only if locker dies 63 * 64 * given that we read bootcaches.plist, we don't trust anything in it 65 * ... but we push the checking off to kextcache, which ensures 66 * (via dev_t/safecalls) that it is only operating on a single volume and 67 * not being redirected to other volumes, etc. We have had Security review. 68 * 69 * To make sure mkexts have the correct owners, kextd enables owners 70 * for the duration of kextcache's locks. 71 * 72 */ 73 74// system includes 75#include <asl.h> 76#include <notify.h> // note notify_monitor_file below 77#include <string.h> // strerror() 78#include <sysexits.h> 79#include <errno.h> 80#include <sys/stat.h> 81#include <sys/wait.h> // waitpid(2) 82#include <stdlib.h> // daemon(3) 83#include <signal.h> 84#include <unistd.h> // e.g. execv 85 86#include <bless.h> 87#include <CoreFoundation/CoreFoundation.h> 88#include <DiskArbitration/DiskArbitration.h> 89#include <DiskArbitration/DiskArbitrationPrivate.h> 90#include <IOKit/kext/kextmanager_types.h> 91#include <IOKit/storage/CoreStorage/CoreStorageUserLib.h> // LVGChanged 92 93#include <IOKit/kext/OSKextPrivate.h> 94#ifndef kOSKextLogCacheFlag 95 #define kOSKextLogCacheFlag kOSKextLogArchiveFlag 96#endif // no kOSKextLogCacheFlag 97 98 99// notifyd SPI 100extern uint32_t notify_monitor_file(int token, const char *name, int flags); 101extern uint32_t notify_get_event(int token, int *ev, char *buf, int *len); 102 103// project includes 104#include "kextd_main.h" // handleSignal() 105#include "kextd_watchvol.h" // kextd_watch_volumes 106#include "kextd_globals.h" // gClientUID 107#include "kextd_usernotification.h" // kextd_raise_notification 108#include "bootcaches.h" // struct bootCaches 109#include "bootroot.h" // BRBLLogFunc (only, for now) 110#include "kextmanager_async.h" // lock_*_reply() 111#include "safecalls.h" // sdeepunlink 112 113 114// constants 115#define kAutoUpdateDelay 300 116#define kWatchKeyBase "com.apple.system.kextd.fswatch" 117#define kWatchSettleTime 5 118#define kMaxUpdateFailures 5 // consecutive failures 119#define kMaxUpdateAttempts 25 // reset after failure->success 120// XXX after 25 successful updates, touching /S/L/E becomes lame 121// 6227955 dictates the failure->success reset metric 122// should instead detect 25 back-to-back attempts 123 124// 6775045 / 5350761: basic MessageTracer logging 125#define kMessageTracerDomainKey "com.apple.message.domain" 126#define kMessageTracerSignatureKey "com.apple.message.signature" 127#define kMessageTracerResultKey "com.apple.message.result" 128#define kMTCachesDomain "com.apple.kext.caches" 129#define kMTUpdatedCaches "updatedCaches" 130#define kMTBeginShutdownDelay "beginShutdownDelay" 131#define kMTEndShutdownDelay "endShutdownDelay" 132 133 134// the type: struct watchedVol's (struct bootCaches in bootcaches.h) 135// created/destroyed with volumes coming/going; stored in sFsysWatchDict 136// use notify_set_state on our notifications to point to these objects 137struct watchedVol { 138 // CFUUIDRef volUUID; // DA id (is the key in sFsysWatchDict) 139 CFRunLoopTimerRef delayer; // non-NULL if something is scheduled 140 CFMachPortRef lock; // send right to locker's port 141 CFMutableArrayRef waiters; // reply ports awaiting this volume 142 int updterrs; // bump when locker reports an error 143 int updtattempts; // # times kextcache launched (w/o err->noerr) 144 uint32_t origMntFlags; // mount flags to restore if owners were off 145 Boolean isBootRoot; // should we try to update helpers? 146 147 CFMutableArrayRef tokens; // notify(3) tokens 148 struct bootCaches *caches; // parsed version of bootcaches.plist 149 150 // XX should track the PID of any launched kextcache (5736801) 151}; 152 153// module-wide data 154static DASessionRef sDASession = NULL; // learn about volumes 155static DAApprovalSessionRef sDAApproval = NULL; // retain volumes 156static CFMachPortRef sFsysChangedPort = NULL; // let us know 157static CFRunLoopSourceRef sFsysChangedSource = NULL; // on the runloop 158static CFMutableDictionaryRef sFsysWatchDict = NULL; // disk ids -> wstruct*s 159static CFMutableDictionaryRef sReplyPorts = NULL; // cfports -> replyPorts 160static CFMachPortRef sRebootLock = NULL; // sys lock for reboot 161static CFMachPortRef sRebootWaiter = NULL; // only need one 162static CFRunLoopTimerRef sAutoUpdateDelayer = NULL; // avoid boot / movie 163 164/* 165 * For historical reasons (avoiding first boot movie, old kextcache -U 166 * race), kextd waits five minutes before performing automatic updates. 167 * Once the timer has expiered, vol_appeared() and fsys_changed() will 168 * trigger updates as needed. 6276173 tracks removal of the fixed delay. 169 * 170 * Updates are always performed at shutdown. 171 */ 172 173 174 175// function declarations (kextd_watch_volumes, _stop in watchvol.h) 176 177// ctor/dtors 178static struct watchedVol* create_watchedVol(DADiskRef disk); 179static void destroy_watchedVol(struct watchedVol *watched); 180static CFMachPortRef createWatchedPort(mach_port_t mport, void *ctx); 181 182// volume state 183static void vol_appeared(DADiskRef disk, void *ctx); 184static void vol_changed(DADiskRef, CFArrayRef keys, void* ctx); 185static void vol_disappeared(DADiskRef, void* ctx); 186CF_RETURNS_RETAINED static DADissenterRef is_dadisk_busy(DADiskRef, void *ctx); 187static Boolean check_vol_busy(struct watchedVol *watched); 188 189// notification processing delay scheme 190static void fsys_changed(CFMachPortRef p, void *msg, CFIndex size, void *info); 191static void checkScheduleUpdate(struct watchedVol *watched); 192static void check_now(CFRunLoopTimerRef timer, void *ctx); // notify timer cb 193 194// check and act 195static Boolean check_rebuild(struct watchedVol*); // true if launched 196 197// CFMachPort invalidation callback 198static void port_died(CFMachPortRef p, void *info); 199 200// helpers for volume and reboot locking routines 201static void checkAutoUpdate(CFRunLoopTimerRef t, void *ctx); 202static Boolean reconsiderVolumes(mountpoint_t busyVol); 203static Boolean checkAllWatched(mountpoint_t busyVol); // true => work to do 204 205// logging 206static void logMTMessage(char *signature, char *result, char *mfmt, ...); 207 208// additional "local" helpers are declared/defined just before use 209 210 211// utility macros 212#define CFRELEASE(x) if (x) { CFRelease(x); x = NULL; } 213 214 215#if 0 216// for testing 217#define twrite(msg) write(STDERR_FILENO, msg, sizeof(msg)) 218static void debug_chld(int signum) __attribute__((unused)) 219{ 220 int olderrno = errno; 221 int status; 222 pid_t childpid; 223 224 if (signum != SIGCHLD) 225 twrite("debug_chld not registered for signal\n"); 226 else 227 if ((childpid = waitpid(-1, &status, WNOHANG)) == -1) 228 twrite("DEBUG: SIGCHLD received, but no children available?\n"); 229 else 230 if (!WIFEXITED(status)) 231 twrite("DEBUG: child quit on signal?\n"); 232 else 233 if (WEXITSTATUS(status)) 234 twrite("DEBUG: child exited with unhappy status\n"); 235 else 236 twrite("DEBUG: child exited with happy status\n"); 237 238 errno = olderrno; 239} 240#endif 241 242/****************************************************************************** 243 * kextd_watch_volumes sets everything up (on the current runloop) 244 * cleanup of the file-glabal static objects is done in kextd_stop_volwatch() 245 *****************************************************************************/ 246int kextd_watch_volumes(int sourcePriority) 247{ 248 int rval; 249 char *errmsg; 250 CFRunLoopRef rl; 251 const void *keyobjs[2] = { kDADiskDescriptionMediaContentKey, 252 kDADiskDescriptionVolumePathKey }; 253 CFArrayRef dakeys = NULL; 254 255 rval = EEXIST; 256 errmsg = "kextd_watch_volumes() already initialized"; 257 if (sFsysWatchDict) goto finish; 258 259 rval = ELAST + 1; 260 // the callbacks will want to go digging in here, so set it up first 261 errmsg = "couldn't create data structures"; 262 // sFsysWatchDict keeps track of watched volumes with UUIDs as keys 263 sFsysWatchDict = CFDictionaryCreateMutable(nil, 0, 264 &kCFTypeDictionaryKeyCallBacks, NULL); // storing watchedVol*'s 265 if (!sFsysWatchDict) goto finish; 266 // We keep two ports for a client; one to for death tracking and one to 267 // reply when the time comes. sReplyPorts maps between the CF wrapper 268 // for the death-tracking port and the mach_port_t replyPort. 269 sReplyPorts = CFDictionaryCreateMutable(nil, 0, 270 &kCFTypeDictionaryKeyCallBacks, NULL); // storing mach_port_t's 271 if (!sReplyPorts) goto finish; 272 273 errmsg = "error setting up ports and sources"; 274 rl = CFRunLoopGetCurrent(); 275 if (!rl) goto finish; 276 277 // change notifications will eventually come in through this port/source 278 sFsysChangedPort = CFMachPortCreate(nil, fsys_changed, NULL, NULL); 279 // we have to keep these objects so we can unschedule them later? 280 if (!sFsysChangedPort) goto finish; 281 sFsysChangedSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, 282 sFsysChangedPort, sourcePriority++); 283 if (!sFsysChangedSource) goto finish; 284 CFRunLoopAddSource(rl, sFsysChangedSource, kCFRunLoopDefaultMode); 285 286 // in general, being on the runloop means we could be called ... 287 // and we are thus careful about our ordering. In practice, however, 288 // we're adding to the current runloop, which means nothing can happen 289 // until this routine exits (we're on the one and only thread). 290 291 /* 292 * XX need to set up a better match dictionary 293 * kDADiskDescriptionMediaWritableKey = true 294 * kDADiskDescriptionVolumeNetworkKey != true 295 */ 296 297 // make sure we have a chance to block unmounts 298 errmsg = "couldn't set up diskarb sessions"; 299 sDAApproval = DAApprovalSessionCreate(nil); 300 if (!sDAApproval) goto finish; 301 DARegisterDiskUnmountApprovalCallback(sDAApproval, 302 kDADiskDescriptionMatchVolumeMountable, is_dadisk_busy, NULL); 303 DAApprovalSessionScheduleWithRunLoop(sDAApproval, rl,kCFRunLoopDefaultMode); 304 305 // set up the disk arrival session & callbacks 306 // XX need to investigate custom match dictionaries 307 sDASession = DASessionCreate(nil); 308 if (!sDASession) goto finish; 309 DARegisterDiskAppearedCallback(sDASession, 310 kDADiskDescriptionMatchVolumeMountable, vol_appeared, NULL); 311 if (!(dakeys = CFArrayCreate(nil, keyobjs, 2, &kCFTypeArrayCallBacks))) 312 goto finish; 313 DARegisterDiskDescriptionChangedCallback(sDASession, 314 kDADiskDescriptionMatchVolumeMountable, dakeys, vol_changed, NULL); 315 DARegisterDiskDisappearedCallback(sDASession, 316 kDADiskDescriptionMatchVolumeMountable, vol_disappeared, NULL); 317 318 // we're ready to rumble! 319 DASessionScheduleWithRunLoop(sDASession, rl, kCFRunLoopDefaultMode); 320 321 // 5519500: schedule a timer to re-enable autobuilds and reconsider volumes 322 // should sign up for IOSystemLoadAdvisory() once it can avoid the movie 323 sAutoUpdateDelayer = CFRunLoopTimerCreate(kCFAllocatorDefault, 324 CFAbsoluteTimeGetCurrent() + kAutoUpdateDelay, 0, 325 0, sourcePriority++, checkAutoUpdate, NULL); 326#pragma unused(sourcePriority) 327 if (!sAutoUpdateDelayer) { 328 goto finish; 329 } 330 CFRunLoopAddTimer(CFRunLoopGetCurrent(), sAutoUpdateDelayer, 331 kCFRunLoopDefaultMode); 332 CFRelease(sAutoUpdateDelayer); // later self-invalidation will free 333 334 // if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) goto finish; 335 // errmsg = "couldn't set debug signal handler"; 336 // if (signal(SIGCHLD, debug_chld) == SIG_ERR) goto finish; 337 338 errmsg = NULL; 339 rval = 0; 340 341 // volume notifications should start coming in shortly 342 343finish: 344 if (rval) { 345 OSKextLog(/* kext */ NULL, 346 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 347 "kextd_watch_volumes: %s.", errmsg); 348 if (rval != EEXIST) kextd_stop_volwatch(); 349 } 350 351 if (dakeys) CFRelease(dakeys); 352 353 return rval; 354} 355 356/****************************************************************************** 357 * 5519500: if appropriate, checkAutoUpdate() enables event-driven updates 358 * and checks all watched volumes to see if they need updating. 359 * It doesn't yet use IOSystemLoadAdvisory() since that doesn't avoid movies. 360 *****************************************************************************/ 361static void checkAutoUpdate(CFRunLoopTimerRef timer, void *ctx) 362{ 363 // assert(timer == sAutoUpdateDelayer) 364 mountpoint_t ignore; 365 366 // one-shot timer self-invalidates, so clear out our reference 367 // Also, vol_appeared() won't call check_rebuild() until this is clear. 368 sAutoUpdateDelayer = NULL; 369 370 // and check all of the watched volumes for updates 371 (void)checkAllWatched(ignore); 372} 373 374/****************************************************************************** 375 * kextd_stop_volwatch unregisters from everything and cleans up 376 * - called from watch_volumes to handle partial cleanup 377 *****************************************************************************/ 378// to help clear out sFsysWatch 379static void free_dict_item(const void* key, const void *val, void *c) 380{ 381 destroy_watchedVol((struct watchedVol*)val); 382} 383 384// public entry point to this module 385void kextd_stop_volwatch() 386{ 387 CFRunLoopRef rl; 388 389 // runloop cleanup 390 rl = CFRunLoopGetCurrent(); 391 if (rl && sDASession) DASessionUnscheduleFromRunLoop(sDASession, rl, 392 kCFRunLoopDefaultMode); 393 if (rl && sDAApproval) DAApprovalSessionUnscheduleFromRunLoop(sDAApproval, 394 rl, kCFRunLoopDefaultMode); 395 396 // use CFRELEASE to nullify cfrefs in case watch_volumes called again 397 if (sDASession) { 398 DAUnregisterCallback(sDASession, vol_disappeared, NULL); 399 DAUnregisterCallback(sDASession, vol_changed, NULL); 400 DAUnregisterCallback(sDASession, vol_appeared, NULL); 401 CFRELEASE(sDASession); 402 } 403 404 if (sDAApproval) { 405 DAUnregisterApprovalCallback(sDAApproval, is_dadisk_busy, NULL); 406 CFRELEASE(sDAApproval); 407 } 408 409 if (rl && sFsysChangedSource) CFRunLoopRemoveSource(rl, sFsysChangedSource, 410 kCFRunLoopDefaultMode); 411 CFRELEASE(sFsysChangedSource); 412 CFRELEASE(sFsysChangedPort); 413 414 if (sFsysWatchDict) { 415 CFDictionaryApplyFunction(sFsysWatchDict, free_dict_item, NULL); 416 CFRELEASE(sFsysWatchDict); 417 } 418 419 return; 420} 421 422/****************************************************************************** 423* destroy_watchedVol unregisters any notification tokens and frees 424* pieces created in create_watchedVol 425******************************************************************************/ 426static void destroy_watchedVol(struct watchedVol *watched) 427{ 428 CFIndex ntokens; 429 int token; 430 int errnum; 431 432 // assert that ->delayer, and ->lock have already been cleaned up 433 if (watched->tokens) { 434 ntokens = CFArrayGetCount(watched->tokens); 435 while(ntokens--) { 436 token = (int)(intptr_t)CFArrayGetValueAtIndex(watched->tokens,ntokens); 437 // XX should take (hacky) steps to insure token is never zero? 438 if (/* !token || */ (errnum = notify_cancel(token))) 439 OSKextLog(/* kext */ NULL, 440 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 441 "destroy_watchedVol: error %d canceling notification.", errnum); 442 } 443 CFRelease(watched->tokens); 444 } 445 if (watched->caches) destroyCaches(watched->caches); 446 free(watched); 447} 448 449/****************************************************************************** 450* create_watchedVol calls readCaches and creates watch-specific necessities 451******************************************************************************/ 452static struct watchedVol* create_watchedVol(DADiskRef disk) 453{ 454 struct watchedVol *watched, *rval = NULL; 455 char *errmsg; 456 457 errmsg = "allocation error"; 458 watched = calloc(1, sizeof(*watched)); 459 if (!watched) goto finish; 460 461 errmsg = NULL; // no bootcaches.plist, no problem 462 463 watched->caches = readBootCachesForDADisk(disk); // logs errors 464 if (!watched->caches) { 465 goto finish; 466 } 467 468 watched->isBootRoot = hasBootRootBoots(watched->caches, NULL, NULL, NULL); 469 470 // get rid of any old bootstamps 471 if (!watched->isBootRoot) { 472 (void)updateStamps(watched->caches, kBCStampsUnlinkOnly); 473 } 474 475 // There will be RPS paths, booters, "misc" paths, and the exts folder. 476 // For now, we'll just set the array size to 0 and let it grow. 477 errmsg = "allocation error"; 478 watched->tokens = CFArrayCreateMutable(nil, 0, NULL); 479 if (!watched->tokens) goto finish; 480 481 errmsg = NULL; 482 rval = watched; // success! 483 484finish: 485 if (errmsg) { 486 if (watched && watched->caches && watched->caches->root[0]) { 487 OSKextLog(/* kext */ NULL, 488 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, 489 "%s: %s.", watched->caches->root, errmsg); 490 } else { 491 OSKextLog(/* kext */ NULL, 492 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, 493 "create_watchedVol(): %s.", errmsg); 494 } 495 } 496 if (!rval && watched) { 497 destroy_watchedVol(watched); 498 } 499 500 return rval; 501} 502 503 504// helper: caller must remove port from other structures (e.g. waiters queue) 505// The CFRelease() is releasing a CFMachPort created by createWatchedPort(). 506// While the volume lock waiters queue is a CFArray (which takes its own 507// reference), every element has an extra retain count so that we can 508// release the non-CF-contained objects here. The analyzer doesn't know 509// that and thinks we shouldn't be releasing those references in the case 510// where *port = (CFMachPortRef)CFArrayGetValueAtIndex(watched->waiters,i); 511static int 512cleanupPort(CFMachPortRef *port) 513{ 514 mach_port_t lport; 515 516 if (sReplyPorts) 517 CFDictionaryRemoveValue(sReplyPorts, *port); // stop tracking replyPort 518 CFMachPortSetInvalidationCallBack(*port, NULL); // else port_died called 519 lport = CFMachPortGetPort(*port); 520 CFRelease(*port); 521 *port = NULL; 522 523 return mach_port_deallocate(mach_task_self(), lport); 524} 525 526// caller responsibile for setting up the lock and cleaning up the waiter 527static int signalWaiter(CFMachPortRef waiter, int status) 528{ 529 int rval = KERN_FAILURE; 530 mach_port_t replyPort; 531 532 // extract this client's reply port and reply 533 replyPort=(mach_port_t)(intptr_t)CFDictionaryGetValue(sReplyPorts,waiter); 534 CFDictionaryRemoveValue(sReplyPorts, waiter); 535 536 if (replyPort != MACH_PORT_NULL) { 537 if (waiter == sRebootWaiter) { 538 mountpoint_t empty; 539 rval = lock_reboot_reply(replyPort, KERN_SUCCESS, empty, status); 540 } else { 541 rval = lock_volume_reply(replyPort, KERN_SUCCESS, status); 542 } 543 } 544 545 if (rval) { 546 OSKextLog(/* kext */ NULL, 547 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 548 "signalWaiter failed: %s.", 549 safe_mach_error_string(rval)); 550 } 551 return rval; 552} 553 554// sRebootWaiter must be set; sRebootLock is released if held 555static void handleRebootHandoff() 556{ 557 mountpoint_t busyVol; 558 559 // make sure everything is clean 560 // (checkAllWatched() will initiate updates if needed) 561 if (checkAllWatched(busyVol) == EBUSY) { 562 OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag, 563 "%s is still busy, delaying reboot.", busyVol); 564 goto finish; 565 } 566 567 // signal the waiter 568 if (signalWaiter(sRebootWaiter, KERN_SUCCESS) == 0) { 569 // on success, make the waiter the locker 570 sRebootLock = sRebootWaiter; 571 OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag, 572 "%s up to date; unblocking reboot.", busyVol); 573 logMTMessage(kMTEndShutdownDelay, "success", "unblocking shutdown"); 574 } 575 sRebootWaiter = NULL; 576 577finish: 578 return; 579} 580 581// cleans up watched->lock, checks for waiters, assigns the lock, and signals 582static void handleWatchedHandoff(struct watchedVol *watched) 583{ 584 CFMachPortRef waiter = NULL; 585 586 // release existing lock 587 if (watched->lock) 588 cleanupPort(&watched->lock); 589 590 // see if we have any waiters 591 // XX should have a loop that can try multiple waiters 592 if (watched->waiters && CFArrayGetCount(watched->waiters)) { 593 waiter = (CFMachPortRef)CFArrayGetValueAtIndex(watched->waiters, 0); 594 595 // move waiter into the pole position and remove from the array 596 OSKextLog(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag, 597 "granting lock for %s to waiter %d", watched->caches->root, 598 CFMachPortGetPort(waiter)); 599 watched->lock = waiter; // context already set to 'watched' 600 CFArrayRemoveValueAtIndex(watched->waiters, 0); 601 602 // signal the waiter, cleaning up on failure 603 if (signalWaiter(waiter, KERN_SUCCESS)) { 604 // XX should loop back to try next waiter if this one failed 605 cleanupPort(&watched->lock); // deallocates former waiter 606 } 607 } 608} 609 610/****************************************************************************** 611 * vol_appeared checks whether a volume is interesting 612 * (note: the first time we see a volume, it's probably not mounted yet) 613 * (we rely on vol_changed to call us when the mountpoint actually appears) 614 * - signs up for notifications -> creates new entries in our structures 615 * - initiates an initial volume check 616 *****************************************************************************/ 617// set up notifications for a single path 618static int watch_path(char *path, mach_port_t port, struct watchedVol* watched) 619{ 620 int rval = ELAST + 1; // cheesy 621 char key[PATH_MAX]; 622 int token = 0; 623 int errnum; 624 uint64_t state; 625 626 // generate key, register for token, monitor, record pointer in token 627 if (strlcpy(key, kWatchKeyBase, PATH_MAX) >= PATH_MAX) goto finish; 628 if (strlcat(key, path, PATH_MAX) >= PATH_MAX) goto finish; 629 if (notify_register_mach_port(key, &port, NOTIFY_REUSE, &token)) 630 goto finish; 631 state = (intptr_t)watched; 632 if (notify_set_state(token, state)) goto finish; 633 if (notify_monitor_file(token, path, /* flags; 0 means "all" */ 0)) goto finish; 634 635 CFArrayAppendValue(watched->tokens, (void*)(intptr_t)token); 636 637 rval = 0; 638 639finish: 640 if (rval && token != -1 && (errnum = notify_cancel(token))) 641 OSKextLog(/* kext */ NULL, 642 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 643 "watch_path: error %d canceling token.", errnum); 644 645 return rval; 646} 647 648#define WATCH(watched, fullp, relpath, fsPort) do { \ 649 /* MAKEROOTPATH */ \ 650 COMPILE_TIME_ASSERT(sizeof(fullp) == PATH_MAX); \ 651 if (strlcpy(fullp, watched->caches->root, PATH_MAX) >= PATH_MAX) \ 652 goto finish; \ 653 if (strlcat(fullp, relpath, PATH_MAX) >= PATH_MAX) \ 654 goto finish; \ 655 \ 656 if (watch_path(fullp, fsPort, watched)) \ 657 goto finish; \ 658 } while(0) 659#define kInstallCommitPath "/private/var/run/installd.commit.pid" 660 661#if DEV_KERNEL_SUPPORT 662/****************************************************************************** 663 * watch_kernels gets the parent of the kernel file then enumerates the given 664 * directory and calls watch_path for each "valid" kernel file in that 665 * directory. NOTE - "valid" currently means files named "kernel.SUFFIX" 666 *****************************************************************************/ 667static void 668watch_kernels( 669 struct watchedVol * watched, 670 const char * kernelPath, 671 mach_port_t fsPort ) 672{ 673 CFURLRef kernelURL = NULL; // must release 674 CFURLRef kernelsDirURL = NULL; // must release 675 CFURLEnumeratorRef myEnumerator = NULL; // must release 676 CFURLRef enumURL = NULL; // do not release 677 CFStringRef tempCFString = NULL; // must release 678 CFArrayRef resultArray = NULL; // must release 679 char tempPath[PATH_MAX]; 680 681 if (kernelPath == NULL || watched == NULL || watched->caches == NULL) { 682 goto finish; 683 } 684 685 /* back up to the parent directory and enumerate from there */ 686 if (strlcpy(tempPath, 687 watched->caches->root, 688 sizeof(tempPath)) >= sizeof(tempPath)) 689 goto finish; 690 if (strlcat(tempPath, 691 kernelPath, 692 sizeof(tempPath)) >= sizeof(tempPath)) 693 goto finish; 694 695 kernelURL = CFURLCreateFromFileSystemRepresentation( 696 NULL, 697 (const UInt8 *)tempPath, 698 strlen(tempPath), 699 true ); 700 if (kernelURL == NULL) { 701 goto finish; 702 } 703 704 kernelsDirURL = CFURLCreateCopyDeletingLastPathComponent(NULL, kernelURL ); 705 if (kernelsDirURL == NULL) { 706 goto finish; 707 } 708 709 // watch /System/Library/Kernels directory 710 if (CFURLGetFileSystemRepresentation(kernelsDirURL, 711 true /*resolve*/, 712 (UInt8*)tempPath, 713 sizeof(tempPath)) ) { 714 watch_path(tempPath, fsPort, watched); 715 } 716 717 myEnumerator = CFURLEnumeratorCreateForDirectoryURL( 718 NULL, 719 kernelsDirURL, 720 kCFURLEnumeratorDefaultBehavior, 721 NULL ); 722 if (myEnumerator == NULL) { 723 goto finish; 724 } 725 726 while (CFURLEnumeratorGetNextURL(myEnumerator, 727 &enumURL, 728 NULL) == kCFURLEnumeratorSuccess) { 729 // make sure we have a last component and it begins with "kernel." 730 // NOTE - we are already watching "/S/L/Kernels/kernel" 731 SAFE_RELEASE_NULL(tempCFString); 732 tempCFString = CFURLCopyLastPathComponent(enumURL); 733 if (tempCFString == NULL || 734 CFStringHasPrefix(tempCFString, CFSTR("kernel.")) == false) { 735 continue; 736 } 737 738 // skip any kernels with more than 1 '.' character. 739 // For example: kernel.foo.bar is not a valid kernel name 740 SAFE_RELEASE_NULL(resultArray); 741 resultArray = CFStringCreateArrayWithFindResults( 742 NULL, 743 tempCFString, 744 CFSTR("."), 745 CFRangeMake(0, CFStringGetLength(tempCFString)), 746 0); 747 if (resultArray && CFArrayGetCount(resultArray) > 1) { 748 continue; 749 } 750 751 if (CFURLGetFileSystemRepresentation(enumURL, 752 true /*resolve*/, 753 (UInt8*)tempPath, 754 sizeof(tempPath)) ) { 755 watch_path(tempPath, fsPort, watched); 756 } 757 } // while loop 758 759finish: 760 SAFE_RELEASE(kernelURL); 761 SAFE_RELEASE(kernelsDirURL); 762 SAFE_RELEASE(myEnumerator); 763 SAFE_RELEASE(tempCFString); 764 SAFE_RELEASE(resultArray); 765 766 return; 767} 768#endif 769 770 771static void vol_appeared(DADiskRef disk, void *launchCtx) 772{ 773 int result = 0; // for now, ignore inability to get basic data (4528851) 774 mach_port_t fsPort; 775 CFDictionaryRef ddesc = NULL; 776 CFURLRef volURL; 777 CFBooleanRef traitVal; 778 CFUUIDRef volUUID; 779 struct watchedVol *watched = NULL; 780 Boolean launched = false; 781 782 struct bootCaches *caches; 783 int i; 784 char path[PATH_MAX]; 785 786 OSKextLogCFString(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag, 787 CFSTR("%s(%@)"), __FUNCTION__, disk); 788 789 // get description so we can see if the disk is writable, etc 790 if (!disk) goto finish; 791 ddesc = DADiskCopyDescription(disk); 792 if (!ddesc) goto finish; 793 794 // volUUID is the key in the dictionary (might we already be watching?) 795 volUUID = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeUUIDKey); 796 if (!volUUID || CFGetTypeID(volUUID) != CFUUIDGetTypeID()) goto finish; 797 if ((watched = (void *)CFDictionaryGetValue(sFsysWatchDict, volUUID))) { 798 // until we can use a real vol_changed() [4620558] to update 799 // 'watched', avoid losing critical data for '/' 800 if (0 == strcmp(watched->caches->root, "/")) { 801 OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag, 802 "'/' re-appeared; refusing to lose existing state (8755513) " 803 "at the risk of missing minor changes (4620558)"); 804 goto finish; 805 } else { 806 // X vol_changed() has already handled removing it if needed? 807 OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag, 808 "WARNING: vol_appeared() removing pre-existing '%s' from" 809 " watch table.", watched->caches->root); 810 vol_disappeared(disk, NULL); 811 } 812 } 813 814 // XX when DA calls vol_appeared(), the volume will always be unmounted ?? 815 volURL = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumePathKey); 816 if (!volURL) goto finish; // ignore unmounted volumes 817 818 // 7628429: ignore read-only filesystems (which might be on writable media) 819 if (CFURLGetFileSystemRepresentation(volURL,false,(UInt8*)path,PATH_MAX)) { 820 struct statfs sfs; 821 if (statfs(path, &sfs) == -1) goto finish; 822 if (sfs.f_flags & MNT_RDONLY) goto finish; // ignore r-only 823 if (sfs.f_flags & MNT_DONTBROWSE) goto finish; // respect nobrowse 824 } else { 825 // couldn't get a path, we don't care about this one 826 goto finish; 827 } 828 829 // check description traits (XX need custom match dict - 4528851) 830 // with fix for 7628429, not clear we need to check this any more 831 traitVal = CFDictionaryGetValue(ddesc, kDADiskDescriptionMediaWritableKey); 832 if (!traitVal || CFGetTypeID(traitVal) != CFBooleanGetTypeID()) goto finish; 833 if (CFEqual(traitVal, kCFBooleanFalse)) goto finish; 834 835 traitVal = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeNetworkKey); 836 if (!traitVal || CFGetTypeID(traitVal) != CFBooleanGetTypeID()) goto finish; 837 if (CFEqual(traitVal, kCFBooleanTrue)) goto finish; 838 839 840 // does it have a usable bootcaches.plist? (if not, ignore) 841 if (!(watched = create_watchedVol(disk))) goto finish; 842 843 result = -1; // anything after this is an error 844 caches = watched->caches; 845 // set up notifications on the change port 846 fsPort = CFMachPortGetPort(sFsysChangedPort); 847 if (fsPort == MACH_PORT_NULL) goto finish; 848 849 /* for path in { bootcaches.plist, installd.commit.pid, exts, kernel, 850 * locSrcs, rpspaths[], booters, miscpaths[] } 851 * rpspaths contains mkext, bootconfig; miscpaths the label file 852 * cache paths are relative; WATCH() makes absolute */ 853 WATCH(watched, path, kBootCachesPath, fsPort); 854 WATCH(watched, path, kInstallCommitPath, fsPort); 855 856 /* support multiple extensions directories - 11860417 */ 857 char *bufptr; 858 bufptr = caches->exts; 859 for (i = 0; i < caches->nexts; i++) { 860 WATCH(watched, path, bufptr, fsPort); 861 bufptr += (strlen(bufptr) + 1); 862 } 863 864 865 // newer systems kernelpath is /System/Library/Kernels/kernel 866 // older systems kernelpath is /mach_kernel 867 WATCH(watched, path, caches->kernelpath, fsPort); 868#if DEV_KERNEL_SUPPORT 869 // look for other kernels and watch them too. 870 if (caches->kernelsCount > 0) { 871 watch_kernels(watched, caches->kernelpath, fsPort); 872 873 // watch any other kernelcache files (kernelcache.SUFFIX) 874 if (watched->caches->extraKernelCachePaths) { 875 for (i = 0; i < watched->caches->nekcp; i++) { 876 WATCH(watched, path, caches->extraKernelCachePaths[i].rpath, fsPort); 877 } 878 } 879 } 880#endif 881 882 WATCH(watched, path, caches->locSource, fsPort); 883 WATCH(watched, path, caches->bgImage, fsPort); 884 // XXX commenting out until 9498428 makes watching this file 885 // more efficient (and probably replaces locPref with an array). 886 // WATCH(watched, path, caches->locPref, fsPort); 887 888 // loop over RPS paths 889 for (i = 0; i < caches->nrps; i++) { 890 WATCH(watched, path, caches->rpspaths[i].rpath, fsPort); 891 } 892 893 if (caches->efibooter.rpath[0]) { 894 WATCH(watched, path, caches->efibooter.rpath, fsPort); 895 } 896 if (caches->ofbooter.rpath[0]) { 897 WATCH(watched, path, caches->ofbooter.rpath, fsPort); 898 } 899 900 // loop over misc paths 901 for (i = 0; i < caches->nmisc; i++) { 902 WATCH(watched, path, caches->miscpaths[i].rpath, fsPort); 903 } 904 905 // we handled any pre-existing entry for volUUID above 906 CFDictionarySetValue(sFsysWatchDict, volUUID, watched); 907 908 // if startup delay hasn't yet passed, skip the usual checks & updates 909 if (sAutoUpdateDelayer) { 910 OSKextLog(NULL, kOSKextLogDetailLevel | kOSKextLogDirectoryScanFlag, 911 "ignoring '%s' until boot is complete", 912 watched->caches->root); 913 } else { 914 launched = check_rebuild(watched); 915 } 916 917 // reconsiderVolume() uses launchCtx to get its return value 918 if (launchCtx) { 919 Boolean *didLaunch = launchCtx; 920 *didLaunch = launched; 921 } 922 923 result = 0; // we made it 924 925finish: 926 if (ddesc) CFRelease(ddesc); 927 928 if (result) { 929 if (watched) { 930 OSKextLog(/* kext */ NULL, 931 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, 932 "Error setting up notifications on '%s'.", 933 watched->caches->root); 934 destroy_watchedVol(watched); 935 } 936 } 937 938 return; 939} 940 941 942#undef WATCH 943 944/****************************************************************************** 945 * vol_changed updates our structures if the mountpoint changed 946 * - includes the initial mount after a device appears 947 * - thus we only call appeared and disappeared as appropriate 948 * 949 * Multiple notifications fire multiple times as disks come and go. 950 * Depending on how predictable it is, we might be able to get away 951 * with just using vol_changed to see when the volume is mounted. 952 *****************************************************************************/ 953 954static void 955check_boots_set_nvram(struct watchedVol *watched) 956{ 957 char *volbsdname = watched->caches->bsdname; 958 CFArrayRef dparts, bparts; 959 Boolean hasBoots; 960 Boolean curSDPreferred = true; // XXX 8011916 961 char *s; 962 CFStringRef firstPart; 963 char bsdName[DEVMAXPATHSIZE]; 964 BLContext blctx = { 0, BRBLLogFunc, NULL }; 965 966 // logs errors 967 hasBoots = hasBootRootBoots(watched->caches, &bparts, &dparts, NULL); 968 if (!bparts || !dparts) goto finish; 969 s = (CFArrayGetCount(bparts) == 1) ? "" : "s"; 970 971 if (hasBoots) { 972 if (!watched->isBootRoot) { 973 // gained Apple_Boot partition(s) 974 OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogGeneralFlag, 975 "%s now has Apple_Boot partition%s", volbsdname, s); 976 watched->isBootRoot = true; 977 check_rebuild(watched); 978 979 // and if it was the root volume, re-point NVRAM 980 if (0 == strcmp(watched->caches->root, "/") && curSDPreferred) { 981 firstPart = CFArrayGetValueAtIndex(bparts, 0); 982 if (!firstPart) goto finish; 983 if (CFStringGetFileSystemRepresentation(firstPart, bsdName, 984 DEVMAXPATHSIZE)) { 985 (void)BLSetEFIBootDevice(&blctx, bsdName); 986 } 987 } 988 } 989 } else { // !hasBoots 990 if (watched->isBootRoot) { 991 // lost Apple_Boot partitions 992 OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogGeneralFlag, 993 "%s lost its Apple_Boot partition%s", volbsdname, s); 994 995 watched->isBootRoot = false; 996 (void)updateStamps(watched->caches, kBCStampsUnlinkOnly); 997 // nothing new to rebuild 998 999 // if there was only one data partition, make it the boot partition 1000 if (0 == strcmp(watched->caches->root, "/") && curSDPreferred && 1001 CFArrayGetCount(dparts) == 1) { 1002 firstPart = CFArrayGetValueAtIndex(dparts, 0); 1003 if (!firstPart) goto finish; 1004 if (CFStringGetFileSystemRepresentation(firstPart, bsdName, 1005 DEVMAXPATHSIZE)) { 1006 (void)BLSetEFIBootDevice(&blctx, bsdName); 1007 } 1008 } 1009 } 1010 } 1011 1012finish: 1013 if (dparts) CFRelease(dparts); 1014 if (bparts) CFRelease(bparts); 1015} 1016 1017 1018/****************************************************************************** 1019* diskarb sends lots of notifications about random stuff? 1020* All mounts & unmounts appear to come through here so we might be 1021* able to stop registering vol_appeared() and vol_disappeared() with DA: 1022* vol_appeared() -> consider_vol(), vol_disappeared() -> vol_unmounted() 1023* 1024* <rdar://problem/4620558> Boot != Root hardening: real vol_changed() so we don't lose state across volume change 1025* tracks *updating* a watch instead of destroying it & recreating it. 1026* 1027* XX for unmount, is this called before or after the unmount(2)? 1028* XX need to investigate custom match dictionary for change notifications 1029*****************************************************************************/ 1030static void vol_changed(DADiskRef disk, CFArrayRef keys, void* ctx) 1031{ 1032 CFIndex i = CFArrayGetCount(keys); 1033 CFDictionaryRef ddesc = NULL; 1034 CFUUIDRef volUUID; 1035 struct watchedVol *watched; 1036 1037 OSKextLogCFString(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag, 1038 CFSTR("%s(%@, keys[0] = %@)"), __FUNCTION__, disk, 1039 CFArrayGetValueAtIndex(keys,0)); 1040 1041 if (!disk) goto finish; 1042 ddesc = DADiskCopyDescription(disk); 1043 if (!ddesc) goto finish; 1044 1045 // if it doesn't have a UUID, we can't have or set up a watch for it 1046 volUUID = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeUUIDKey); 1047 if (!volUUID) goto finish; 1048 1049 // only already-mounted OS will be watched 1050 watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID); 1051 1052 // walk through the properties that have changed 1053 while (i--) { 1054 CFTypeRef key = CFArrayGetValueAtIndex(keys, i); 1055 1056 // check for mount point changes (coming, going, getting a new name) 1057 if (CFEqual(key, kDADiskDescriptionVolumePathKey)) { 1058 // until 4620558 is fixed, mountpoint changes = disappear/appear 1059 // X we could ignore updates that didn't change the value? 1060 if (watched) 1061 vol_disappeared(disk, ctx); // errors to waiters, etc 1062 if (CFDictionaryGetValue(ddesc, key)) 1063 vol_appeared(disk, ctx); // already resists changing '/' 1064 } 1065 1066 // check for media changes => Apple_Boot -> NVRAM updates 1067 else if (CFEqual(key, kDADiskDescriptionMediaContentKey)) { 1068 if (!watched) { 1069 // maybe it should be; vol_appeared() checks current state 1070 vol_appeared(disk, ctx); 1071 } else { 1072 // updates isBootRoot, sets NVRAM to a new boot device if needed 1073 check_boots_set_nvram(watched); 1074 } 1075 } 1076 1077 // something else 1078 else { 1079 OSKextLogCFString(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag, 1080 CFSTR("vol_changed: %@: uninteresting key"), key); 1081 } 1082 } 1083 1084finish: 1085 if (ddesc) CFRelease(ddesc); 1086} 1087 1088/****************************************************************************** 1089 * vol_disappeared removes entries from the relevant structures 1090 * - handles forced removal by invalidating the lock 1091 * - vol_disappeared() called by vol_changed() for unmounts/changes 1092 *****************************************************************************/ 1093static void vol_disappeared(DADiskRef disk, void* ctx) 1094{ 1095 // we used to report errors, but we got weird requests (4528851) 1096 CFDictionaryRef ddesc = NULL; 1097 CFUUIDRef volUUID; 1098 struct watchedVol *watched; 1099 1100 OSKextLogCFString(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag, 1101 CFSTR("%s(%@)"), __FUNCTION__, disk); 1102 1103 // if it's not a disk we're watching, we don't need to stop watching it 1104 if (!disk) goto finish; 1105 ddesc = DADiskCopyDescription(disk); 1106 if (!ddesc) goto finish; 1107 volUUID = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeUUIDKey); 1108 if (!volUUID) goto finish; 1109 watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID); 1110 if (!watched) goto finish; 1111 1112 if (0 == strcmp(watched->caches->root, "/")) { 1113 OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag, 1114 "[8755513] vol_disappeared() refusing to stop watching '/'"); 1115 goto finish; 1116 } 1117 1118 // if the volume is locked, log a warning 1119 if (watched->lock) { 1120 OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag, 1121 "WARNING: '%s' is locked; abandoning helper process%s", 1122 watched->caches->root, watched->waiters ? "es" : ""); 1123 } else { 1124 OSKextLog(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag, 1125 "ending volume watch of %s", watched->caches->root); 1126 } 1127 1128 // take it off the watch list 1129 CFDictionaryRemoveValue(sFsysWatchDict, volUUID); 1130 1131 // and in case some action was in progress 1132 if (watched->delayer) { 1133 CFRunLoopTimerInvalidate(watched->delayer); // refcount->0 1134 watched->delayer = NULL; 1135 } 1136 // see if any lockers are waiting 1137 // (off the list of watched vols so no new requests can come in) 1138 if (watched->waiters) { 1139 CFIndex i = CFArrayGetCount(watched->waiters); 1140 CFMachPortRef waiter; 1141 1142 while(i--) { 1143 waiter = (CFMachPortRef)CFArrayGetValueAtIndex(watched->waiters,i); 1144 signalWaiter(waiter, ENOENT); 1145 cleanupPort(&waiter); 1146 } 1147 CFRelease(watched->waiters); // should remove all elements 1148 } 1149 1150 // no need to toggle owners since the volume is gone 1151 1152 destroy_watchedVol(watched); // cancels notifications 1153 1154finish: 1155 if (ddesc) CFRelease(ddesc); 1156 1157 return; 1158} 1159 1160/****************************************************************************** 1161 * is_dadisk_busy lets diskarb know if we'd rather nothing changed 1162 * note: dissenter callback is called when root initiates an unmount, 1163 * but the result is ignored. 1164 *****************************************************************************/ 1165CF_RETURNS_RETAINED 1166static DADissenterRef 1167is_dadisk_busy(DADiskRef disk, void *ctx) 1168{ 1169 int result = 0; // ignore weird requests for now (4528851) 1170 DADissenterRef rval = NULL; 1171 CFDictionaryRef ddesc = NULL; 1172 CFUUIDRef volUUID; 1173 struct watchedVol *watched; 1174 1175 // XX change to kOSKextLogDetailLevel 1176 OSKextLogCFString(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag, 1177 CFSTR("%s(%@)"), __FUNCTION__, disk); 1178 1179 if (!disk) goto finish; 1180 ddesc = DADiskCopyDescription(disk); 1181 if (!ddesc) goto finish; 1182 volUUID = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeUUIDKey); 1183 if (!volUUID) goto finish; 1184 1185 result = -1; 1186 watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID); 1187 if (!watched) { 1188 // it might have become worth watching while we weren't :? 1189 vol_appeared(disk, NULL); 1190 watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID); 1191 } 1192 // 5537105 don't prevent unlocked non-BootRoot volumes from unmounting 1193 if (watched) { 1194 // once 5736801 is fixed, we'll be able to kill kextcache 1195 if (watched->lock || (watched->isBootRoot && check_vol_busy(watched))) { 1196 // since we log this, count it as an error so future successes are logged 1197 if (watched->updterrs == 0) 1198 watched->updterrs++; 1199 if (watched->caches) 1200 OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogCacheFlag, 1201 "%s busy; denying eject (%d tries left)", watched->caches->root, 1202 kMaxUpdateAttempts - watched->updtattempts); 1203 rval = DADissenterCreate(nil, kDAReturnBusy, CFSTR("kextmanager busy")); 1204 if (!rval) goto finish; 1205 } 1206 // Would it be polite to cancel our watch here? 1207 // We'd need a chance to start again if the eject is ultimately denied. 1208 // It would open a window when we weren't watching but it is similar 1209 // to the window before we start watching? 1210 } 1211 1212 result = 0; 1213 1214finish: 1215 if (result) { 1216 OSKextLog(/* kext */ NULL, 1217 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1218 "is_dadisk_busy had trouble answering diskarb."); 1219 } 1220 // else OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag, "returning dissenter %p", rval); 1221 if (ddesc) CFRelease(ddesc); 1222 1223 return rval; // caller releases dissenter if non-null 1224} 1225 1226/****************************************************************************** 1227 * check_vol_busy 1228 * - busy if locked 1229 * - check_rebuild to check once more (return code indicates if it did anything) 1230 *****************************************************************************/ 1231static Boolean check_vol_busy(struct watchedVol *watched) 1232{ 1233 Boolean busy = (watched->lock != NULL); 1234 1235 if (busy) goto finish; 1236 1237 // if not locked and not over error limits, call check_rebuild 1238 if (watched->updterrs < kMaxUpdateFailures && 1239 watched->updtattempts < kMaxUpdateAttempts) { 1240 busy = check_rebuild(watched); 1241 } else { 1242 // over limits; log an error 1243 OSKextLog(/* kext */ NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag, 1244 "%s: giving up; kextcache hit max %s", watched->caches->root, 1245 watched->updterrs >= kMaxUpdateFailures ? "failures" : "update attempts"); 1246 } 1247 1248finish: 1249 return busy; 1250} 1251 1252 1253/****************************************************************************** 1254 * fsys_changed gets the mach messages from notifyd 1255 * - schedule a timer (urgency detected elsewhere calls direct, canceling timer) 1256 *****************************************************************************/ 1257static void 1258fsys_changed(CFMachPortRef p, void *m, CFIndex size, void *info) 1259{ 1260 uint64_t nstate; 1261 struct watchedVol *watched; 1262 int notify_status = NOTIFY_STATUS_OK; 1263 int token; 1264 mach_msg_empty_rcv_t *msg = (mach_msg_empty_rcv_t*)m; 1265 1266 // msg_id==token -> notify_get_state() -> watchedVol* 1267 // XX if (token == 0, perhaps a force-rebuild message?) 1268 token = msg->header.msgh_id; 1269 notify_status = notify_get_state(token, &nstate); 1270 if (NOTIFY_STATUS_OK != notify_status) { 1271 OSKextLogCFString(/* kext */ NULL, 1272 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 1273 CFSTR("notify_get_state() failed for token %d: status %d."), 1274 token, notify_status); 1275 goto finish; 1276 } 1277 1278 // XX should call notify_get_event() here to consume events? 1279 // how to know when this notification's events have been consumed? 1280 // filter out useless (generally spurious) events (esp on unmount :P) 1281 watched = (struct watchedVol*)(intptr_t)nstate; 1282 if (!watched) { 1283 OSKextLogCFString(/* kext */ NULL, 1284 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 1285 CFSTR("NULL entry for notify token %d."), 1286 token); 1287 goto finish; 1288 } 1289 1290 checkScheduleUpdate(watched); 1291 1292finish: 1293 return; 1294} 1295 1296static void 1297checkScheduleUpdate(struct watchedVol *watched) 1298{ 1299 char path[PATH_MAX]; 1300 struct stat sb; 1301 FILE *f; 1302 1303 // ignore unwatched volumes (notification should have been canceled?) 1304 if (!CFDictionaryGetCountOfValue(sFsysWatchDict, watched)) { 1305 OSKextLog(/* kext */ NULL, 1306 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 1307 "Invalid volume: %p.", watched); 1308 goto finish; 1309 } 1310 1311 // The goal is to schedule a timer to do the update later, but if 1312 // the installer is installing, then we'll ignore this change. 1313 // Changes to the .pid file will call us again. 1314 if (strlcpy(path,watched->caches->root,sizeof(path)) >= sizeof(path)) 1315 goto finish; 1316 if (strlcat(path, kInstallCommitPath, sizeof(path)) >= sizeof(path)) 1317 goto finish; 1318 if ((f = fopen(path, "r"))) { 1319 pid_t pid; 1320 int nmatched = fscanf(f, "%d", &pid); 1321 fclose(f); 1322 if (nmatched == 1) { 1323 if (0 == kill(pid, 0)) { 1324 OSKextLog(NULL,kOSKextLogDetailLevel|kOSKextLogFileAccessFlag, 1325 "%s: installer active, ignoring changes", 1326 watched->caches->root); 1327 goto finish; 1328 } 1329 } 1330 } 1331 1332 // Check for new bootcaches.plist content 1333 if (strlcpy(path,watched->caches->root,sizeof(path)) >= sizeof(path)) 1334 goto finish; 1335 if (strlcat(path, kBootCachesPath, sizeof(path)) >= sizeof(path)) 1336 goto finish; 1337 if (stat(path, &sb) != 0) { 1338 if (errno != ENOENT) { 1339 OSKextLogCFString(/* kext */ NULL, 1340 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, 1341 CFSTR("stat failed for %s: %s."), path, strerror(errno)); 1342 } 1343 // no longer a bootcaches.plist, bail (w/o canceling watch) 1344 goto finish; 1345 } 1346 1347 if (watched->caches->bcTime.tv_sec != sb.st_mtimespec.tv_sec || 1348 watched->caches->bcTime.tv_nsec !=sb.st_mtimespec.tv_nsec) { 1349 1350 // change is afoot in bootcaches.plist 1351 char * volRoot = watched->caches->root; 1352 DADiskRef disk = NULL; 1353 1354 OSKextLog(/* kext */ NULL, 1355 kOSKextLogBasicLevel | kOSKextLogFileAccessFlag, 1356 "%s: bootcaches.plist changed.", volRoot); 1357 1358 // invalidate current watched information 1359 disk = createDiskForMount(sDASession, volRoot); 1360 if (!disk) { 1361 OSKextLog(/* kext */ NULL, 1362 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag | kOSKextLogIPCFlag, 1363 "Unable to create DADisk."); 1364 goto finish; 1365 } 1366 1367 // X: doesn't work for '/', but users' bootcaches.plist shouldn't change 1368 // except from the InstallOS with their '/' as /Volumes/Mac HD. :) 1369 vol_disappeared(disk, NULL); // destroys watched (incl sb) 1370 vol_appeared(disk, NULL); // checks if rebuild needed 1371 CFRelease(disk); 1372 1373 } else if (sAutoUpdateDelayer == NULL) { 1374 // check whether the startup delay has passed 1375 // (XX could let 'touch' work before the startup delay has passed?) 1376 // and prepare to call check_now in a few seconds 1377 CFRunLoopTimerContext tc = { 0, watched, NULL, NULL, NULL }; 1378 CFAbsoluteTime firetime = CFAbsoluteTimeGetCurrent() + kWatchSettleTime; 1379 1380 // cancel any existing timer 1381 if (watched->delayer) { 1382 CFRunLoopTimerInvalidate(watched->delayer); 1383 } 1384 1385 watched->delayer=CFRunLoopTimerCreate(nil,firetime,0,0,0,check_now,&tc); 1386 if (!watched->delayer) { 1387 OSKextLogCFString(/* kext */ NULL, 1388 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 1389 CFSTR("fsys_changed() unable to create timer for watched volume.")); 1390 goto finish; 1391 } 1392 1393 CFRunLoopAddTimer(CFRunLoopGetCurrent(), watched->delayer, 1394 kCFRunLoopDefaultMode); 1395 CFRelease(watched->delayer); // later auto-invalidation will free 1396 } 1397 1398finish: 1399 return; 1400} 1401 1402/****************************************************************************** 1403 * check_now, called after the timer expires, calls check_rebuild() 1404 * It does not look at updterrs because if something changed, we're willing 1405 * to look at it again. 1406 *****************************************************************************/ 1407void check_now(CFRunLoopTimerRef timer, void *info) 1408{ 1409 struct watchedVol *watched = (struct watchedVol*)info; 1410 // OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogGeneralFlag, "DEBUG: check_now(%p): entry", info); 1411 1412 // is the volume still being watched? 1413 if (watched && CFDictionaryGetCountOfValue(sFsysWatchDict, watched)) { 1414 watched->delayer = NULL; // timer is no longer pending 1415 (void)check_rebuild(watched); // don't care what it did 1416 } else { 1417 OSKextLog(/* kext */ NULL, 1418 kOSKextLogWarningLevel | kOSKextLogGeneralFlag, 1419 "%p's timer fired when it should have been Invalid", watched); 1420 } 1421} 1422 1423/****************************************************************************** 1424 * check_rebuild uses needUpdates() to stat everything -> rebuilds as necessary 1425 * - kextcache -u used to do all the work (rebuild mkext, boot's etc) 1426 * 1427 * XX if kextcache is broken (e.g. a copy of 'false'), updterrs is never 1428 * incremented and an an infinite reboot stall could result. updtattempts 1429 * caps the total number of automatic update attempts. The locking mechanism 1430 * serializes (and effectively throttles) the kextcache processes which should 1431 * prevent a transient failure condition from preventing multiple meaningful 1432 * attempts to update the volume. 1433 *****************************************************************************/ 1434static Boolean check_rebuild(struct watchedVol *watched) 1435{ 1436 Boolean launched = false; 1437 Boolean rebuild = false; 1438#if DEV_KERNEL_SUPPORT 1439 char * suffixPtr = NULL; // must free 1440 char * tmpKernelPath = NULL; // must free 1441#endif 1442 1443 // if we came in some other way and there's a timer pending, cancel it 1444 if (watched->delayer) { 1445 CFRunLoopTimerInvalidate(watched->delayer); // runloop holds last ref 1446 watched->delayer = NULL; 1447 } 1448 1449 // make sure this volume isn't out of control with updates 1450 if (watched->updtattempts > kMaxUpdateAttempts) { 1451 OSKextLog(/* kext */ NULL, kOSKextLogWarningLevel | kOSKextLogIPCFlag, 1452 "%s: kextcache has had enough tries; not launching any more", 1453 watched->caches->root); 1454 goto finish; 1455 } 1456 1457 // stat stuff to see if a rebuild is needed 1458 // (it was 'goto' or ever-deeper nesting) 1459 if ((rebuild = check_kext_boot_cache_file(watched->caches, 1460 watched->caches->kext_boot_cache_file->rpath, 1461 watched->caches->kernelpath))) { 1462 1463 goto dorebuild; 1464 } 1465 1466#if DEV_KERNEL_SUPPORT 1467 if (watched->caches->extraKernelCachePaths) { 1468 int i; 1469 cachedPath * cp; 1470 1471 tmpKernelPath = malloc(PATH_MAX); 1472 1473 for (i = 0; i < watched->caches->nekcp; i++) { 1474 1475 cp = &watched->caches->extraKernelCachePaths[i]; 1476 SAFE_FREE_NULL(suffixPtr); 1477 1478 suffixPtr = getPathExtension(cp->rpath); 1479 if (suffixPtr == NULL) 1480 continue; 1481 if (strlcpy(tmpKernelPath, watched->caches->kernelpath, PATH_MAX) >= PATH_MAX) 1482 continue; 1483 if (strlcat(tmpKernelPath, ".", PATH_MAX) >= PATH_MAX) 1484 continue; 1485 if (strlcat(tmpKernelPath, suffixPtr, PATH_MAX) >= PATH_MAX) 1486 continue; 1487 rebuild = check_kext_boot_cache_file(watched->caches, 1488 cp->rpath, 1489 tmpKernelPath); 1490 if (rebuild) goto dorebuild; 1491 } 1492 } 1493#endif 1494 1495 1496 // updates isBootRoot and modifies NVRAM if needed 1497 check_boots_set_nvram(watched); 1498 1499 // Boot!=Root-specific caches 1500 // 16513211 tracks making this more lenient at shutdown 1501 if (watched->isBootRoot) { 1502 rebuild = check_csfde(watched->caches) || 1503 check_loccache(watched->caches) || 1504 needUpdates(watched->caches, kBRUCachesAnyRoot, 1505 NULL, NULL, NULL, // use return aggregate 1506 kOSKextLogProgressLevel | kOSKextLogFileAccessFlag); 1507 1508 if (rebuild) 1509 goto dorebuild; 1510 } 1511 1512dorebuild: 1513 if (rebuild) { 1514 if (launch_rebuild_all(watched->caches->root, false, false) > 0) { 1515 launched = true; 1516 watched->updtattempts++; 1517 } else { 1518 watched->updterrs++; 1519 OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1520 "Error launching kextcache -u."); 1521 } 1522 } 1523 1524 if (0 == strcmp(watched->caches->root, "/") && 1525 plistCachesNeedRebuild(gKernelArchInfo)) { 1526 1527 handleSignal(SIGHUP); 1528 // xxx - why are you not just calling rescanExtensions()? 1529 // X someday SIGHUP may call back to rebuild_caches() to force update 1530 } 1531 1532finish: 1533#if DEV_KERNEL_SUPPORT 1534 SAFE_FREE(tmpKernelPath); 1535 SAFE_FREE(suffixPtr); 1536#endif 1537 return launched; 1538} 1539 1540 1541// ---- locking services (prototyped via MiG and kextmanager[_mig].defs) ---- 1542 1543/****************************************************************************** 1544 * kextmanager_lock_reboot ensures "all clean" (used by shutdown(8), reboot(8)) 1545 *****************************************************************************/ 1546// iterator helper locking for locked or should-be-locked volumes 1547static void check_locked(const void *key, const void *val, void *ctx) 1548{ 1549 struct watchedVol *watched = (struct watchedVol*)val; 1550 // pointer to the mountpoint_t at the other end 1551 char *busyVol = ctx; 1552 1553 // report this one if: 1554 // it's already locked or if it needs a rebuild 1555 // check_vol_busy() ensures checks for excessive errors 1556 if (check_vol_busy(watched)) { 1557 strlcpy(busyVol, watched->caches->root, sizeof(mountpoint_t)); 1558 } 1559} 1560 1561// create a CFMachPort with invalidation -> port_died 1562static CFMachPortRef 1563createWatchedPort(mach_port_t mport, void *ctx) 1564{ 1565 CFMachPortRef rval = NULL; 1566 int result = ELAST + 1; 1567 CFRunLoopSourceRef invalidator; 1568 CFMachPortContext mp_ctx = { 0, ctx, 0, }; 1569 CFRunLoopRef rl = CFRunLoopGetCurrent(); 1570 1571 if (!(rval = CFMachPortCreateWithPort(nil, mport, NULL, &mp_ctx, false))) 1572 goto finish; 1573 invalidator = CFMachPortCreateRunLoopSource(nil, rval, 0); 1574 if (!invalidator) goto finish; 1575 CFMachPortSetInvalidationCallBack(rval, port_died); 1576 CFRunLoopAddSource(rl, invalidator, kCFRunLoopDefaultMode); 1577 CFRelease(invalidator); // owned by the runloop now 1578 1579 result = 0; 1580 1581finish: 1582 if (result && rval) { 1583 CFRelease(rval); 1584 rval = NULL; 1585 } 1586 1587 return rval; 1588} 1589 1590static Boolean 1591checkAllWatched(mountpoint_t busyVol) 1592{ 1593 int result; 1594 1595 // if we've contacted diskarb, scan the dictionary for locked items 1596 busyVol[0] = '\0'; 1597 if (sFsysWatchDict) { 1598 CFDictionaryApplyFunction(sFsysWatchDict, check_locked, busyVol); 1599 } 1600 if (busyVol[0] == '\0') { 1601 result = 0; // you got it! 1602 } else { 1603 // busyVol (at least) was locked, try again later 1604 result = EBUSY; 1605 } 1606 1607 return result; 1608} 1609 1610kern_return_t _kextmanager_lock_reboot(mach_port_t p, mach_port_t replyPort, 1611 mach_port_t client, int waitForLock, mountpoint_t busyVol, int *busyStatus) 1612{ 1613 kern_return_t rval = KERN_FAILURE; 1614 int result = ELAST + 1; 1615 // OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogGeneralFlag, "DEBUG: _lock_reboot(..%d/%d..)...", client, replyPort); 1616 1617 if (!busyStatus) { 1618 result = EINVAL; 1619 rval = KERN_SUCCESS; // for MiG 1620 goto finish; 1621 } 1622 1623 if (gClientUID != 0) { 1624 OSKextLog(/* kext */ NULL, 1625 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1626 "Non-root doesn't need to lock for reboot."); 1627 result = EPERM; 1628 rval = KERN_SUCCESS; // for MiG 1629 goto finish; 1630 } 1631 1632 // shutdown/reboot proceed on result == EALREADY 1633 if (sRebootLock) { 1634 result = EALREADY; 1635 rval = KERN_SUCCESS; // for MiG 1636 OSKextLog(/* kext */ NULL, 1637 kOSKextLogWarningLevel | kOSKextLogIPCFlag, 1638 "Warning: Reboot lock request while reboot in progress."); 1639 goto finish; 1640 } 1641 1642 // check all the volumes we are watching and 1643 // if any new volumes have become eligible 1644 if (checkAllWatched(busyVol) || reconsiderVolumes(busyVol)) { 1645 result = EBUSY; 1646 rval = KERN_SUCCESS; // for MiG 1647 } else { 1648 // great, this guy gets to take the uber reboot lock 1649 if (!(sRebootLock = createWatchedPort(client, &sRebootLock))) 1650 goto finish; 1651 result = 0; // success 1652 rval = KERN_SUCCESS; // for MiG 1653 } 1654 1655 // should we reply now or later? 1656 if (waitForLock && result == EBUSY && sRebootWaiter == NULL) { 1657 // client will block until we reply with lock or failure 1658 // [&sRebootLock is context for all interested in the reboot lock] 1659 if (!(sRebootWaiter = createWatchedPort(client, &sRebootLock))) 1660 goto finish; 1661 1662 // stash reply port so we can get it later (X someday dual-use port?) 1663 CFDictionarySetValue(sReplyPorts, sRebootWaiter, (void*)(intptr_t)replyPort); 1664 rval = MIG_NO_REPLY; // for MiG; no result 1665 } else { 1666 // we reply to the client if: a. failure other than EBUSY, 1667 // b. the client won't wait, or c. we've no way to track him. 1668 rval = KERN_SUCCESS; // MiG will return to the caller 1669 } 1670 1671finish: 1672 if (rval == KERN_SUCCESS) { 1673 if (busyStatus) *busyStatus = result; 1674 } else if (rval != MIG_NO_REPLY) { 1675 OSKextLog(/* kext */ NULL, 1676 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1677 "Error %d locking for reboot.", rval); 1678 } 1679 1680 // pop up a dialog if reboot is going to stall 1681 if (result == EBUSY && waitForLock) { 1682 OSKextLog(/* kext */ NULL, 1683 kOSKextLogWarningLevel | kOSKextLogFileAccessFlag | kOSKextLogIPCFlag, 1684 "'%s' updating, delaying reboot.", busyVol); 1685 1686 // 6775045 / 5350761: basic MessageTracer logging 1687 logMTMessage(kMTBeginShutdownDelay, "failure", 1688 "kext caches need update at shutdown; delaying"); 1689 } 1690 1691 return rval; 1692} 1693 1694/****************************************************************************** 1695 * _kextmanager_lock_volume locks volumes for kextcache 1696 * - vol_uuid is in CFUUIDBytes 1697 *****************************************************************************/ 1698kern_return_t _kextmanager_lock_volume(mach_port_t p, mach_port_t replyPort, 1699 mach_port_t client, uuid_t vol_uuid, int waitForLock, int *lockStatus) 1700{ 1701 kern_return_t rval = KERN_FAILURE; 1702 int result; 1703 CFUUIDBytes uuidBytes; 1704 CFUUIDRef volUUID; 1705 struct watchedVol *watched = NULL; 1706 struct statfs sfs; 1707 Boolean createdLock = false; 1708 1709 OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag, 1710 "%s(..%d..)...", __FUNCTION__, client); 1711 1712 if (!lockStatus) { 1713 OSKextLog(/* kext */ NULL, 1714 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1715 "kextmanager_lock_volume requires non-NULL lockStatus."); 1716 return KERN_INVALID_ARGUMENT; // just return 1717 } 1718 1719 if (gClientUID != 0 /*watched->fsinfo->f_owner ?*/) { 1720 OSKextLog(/* kext */ NULL, 1721 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1722 "Non-root doesn't need to lock or unlock volumes."); 1723 result = EPERM; 1724 rval = KERN_SUCCESS; // for MiG 1725 goto finish; 1726 } 1727 1728 // still initializing, sorry (XX someday could allow client to wait) 1729 if (!sFsysWatchDict) { 1730 result = EAGAIN; 1731 rval = KERN_SUCCESS; // for MiG 1732 goto finish; 1733 } 1734 1735 // rebooting -> no more update locks! 1736 if (sRebootLock) { 1737 OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1738 "reboot in progress -> denying volume lock request"); 1739 result = ENOLCK; 1740 rval = KERN_SUCCESS; // for MiG 1741 goto finish; 1742 } 1743 1744 memcpy(&uuidBytes.byte0, vol_uuid, sizeof(uuid_t)); 1745 volUUID = CFUUIDCreateFromUUIDBytes(nil, uuidBytes); 1746 if (!volUUID) { 1747 result = ENOMEM; 1748 goto finish; 1749 } 1750 watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID); 1751 // would release volUUID here except we want to log it ... 1752 // if kextd isn't watching, locking is less critical (->Basic) 1753 if (!watched) { 1754 OSKextLogCFString(NULL, kOSKextLogBasicLevel | kOSKextLogIPCFlag, 1755 CFSTR("not watching %@ -> no volume lock to grant"), volUUID); 1756 CFRelease(volUUID); 1757 result = ENOENT; 1758 rval = KERN_SUCCESS; // for MiG 1759 goto finish; 1760 } else { // clarify no double release 1761 CFRelease(volUUID); 1762 } 1763 1764 // if not locked, grant the lock 1765 if (watched->lock == NULL) { 1766 // create lock 1767 if (!(watched->lock = createWatchedPort(client, watched))) { 1768 rval = KERN_FAILURE; // also the default above 1769 goto finish; 1770 } 1771 createdLock = true; 1772 // try to enable owners if not currently honored (XX ignores failure) 1773 if (statfs(watched->caches->root, &sfs) == 0) { 1774 uint32_t mntgoal = sfs.f_flags; 1775 mntgoal &= ~(MNT_IGNORE_OWNERSHIP); 1776 if (sfs.f_flags != mntgoal) { 1777 (void)updateMount(watched->caches->root, mntgoal); // logs 1778 watched->origMntFlags = sfs.f_flags; 1779 } 1780 } 1781 OSKextLog(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag, 1782 "granting lock for %s to %d", watched->caches->root,client); 1783 result = 0; // success; lock granted 1784 rval = KERN_SUCCESS; // for MiG 1785 } else { 1786 // lock can't be granted; let the client wait if willing 1787 if (waitForLock) { 1788 rval = MIG_NO_REPLY; // for MiG; no result 1789 } else { 1790 result = EBUSY; // for client 1791 rval = KERN_SUCCESS; // for MiG 1792 } 1793 } 1794 1795 // if we're not replying yet, add client to the wait queue 1796 if (rval == MIG_NO_REPLY) { 1797 CFMachPortRef waiter; 1798 1799 // create waiter array (of CFMachPortRefs) if needed 1800 if (!watched->waiters) { 1801 watched->waiters=CFArrayCreateMutable(0,1,&kCFTypeArrayCallBacks); 1802 if (!watched->waiters) { 1803 rval = KERN_FAILURE; 1804 goto finish; 1805 } 1806 } 1807 1808 // create waiter and insert into array 1809 if (!(waiter = createWatchedPort(client, watched))) { 1810 rval = KERN_FAILURE; 1811 goto finish; 1812 } 1813 1814 // store waiter, replyPort 1815 // cleanupPort() CFRelease()s all waiter objects 1816 CFArrayAppendValue(watched->waiters, waiter); 1817 CFDictionarySetValue(sReplyPorts, waiter, (void*)(intptr_t)replyPort); 1818 1819 // success: rval remains MIG_NO_REPLY 1820 } 1821 1822 1823finish: 1824 // circa 8679674, creating the lock => rval = success 1825 // but this ensures future code won't destroy an existing lock on error 1826 if (rval != KERN_SUCCESS && createdLock && watched->lock) { 1827 cleanupPort(&watched->lock); 1828 } 1829 1830 if (rval == KERN_SUCCESS) { 1831 *lockStatus = result; 1832 } else if (rval == MIG_NO_REPLY) { 1833 // not replying yet 1834 } else if (watched) { 1835 OSKextLog(/* kext */ NULL, 1836 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1837 "Error %d locking '%s'.", rval, watched->caches->root); 1838 } else { 1839 OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1840 "Error %d attempting to lock an un-watched volume.", rval); 1841 } 1842 1843 return rval; 1844} 1845 1846/****************************************************************************** 1847 * _kextmanager_unlock_volume unlocks for clients (i.e. kextcache) 1848 *****************************************************************************/ 1849kern_return_t _kextmanager_unlock_volume(mach_port_t p, mach_port_t client, 1850 uuid_t vol_uuid, int exitstatus) 1851{ 1852 kern_return_t rval = KERN_FAILURE; 1853 CFUUIDRef volUUID = NULL; 1854 struct watchedVol *watched = NULL; 1855 CFUUIDBytes uuidBytes; 1856 // OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogGeneralFlag, "DEBUG: _kextmanager_unlock_volume()..."); 1857 1858 // since we don't need the extra send right added by MiG (XX why?) 1859 if (mach_port_deallocate(mach_task_self(), client)) goto finish; 1860 1861 if (gClientUID != 0 /*watched->fsinfo->f_owner ?*/) { 1862 OSKextLog(/* kext */ NULL, 1863 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1864 "Non-root doesn't need to lock or unlock volumes."); 1865 rval = KERN_SUCCESS; 1866 goto finish; 1867 } 1868 1869 // make sure we're set up 1870 if (!sFsysWatchDict) goto finish; 1871 1872 memcpy(&uuidBytes.byte0, vol_uuid, sizeof(uuid_t)); 1873 volUUID = CFUUIDCreateFromUUIDBytes(nil, uuidBytes); 1874 if (!volUUID) goto finish; 1875 watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID); 1876 if (!watched) goto finish; 1877 1878 if (!watched->lock) { 1879 OSKextLog(/* kext */ NULL, 1880 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1881 "'%s' isn't locked.", watched->caches->root); 1882 goto finish; 1883 } 1884 1885 if (client != CFMachPortGetPort(watched->lock)) { 1886 OSKextLog(/* kext */ NULL, 1887 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1888 "%d didn't lock '%s'.", client, watched->caches->root); 1889 goto finish; 1890 } 1891 1892 // update error accounting 1893 if (exitstatus == EX_OK) { 1894 logMTMessage(kMTUpdatedCaches, "success", 1895 "kextcache updated kernel boot caches"); 1896 if (watched->updterrs > 0) { 1897 // previous error logs -> put a reassuring message in the log 1898 OSKextLog(NULL, kOSKextLogDebugLevel | kOSKextLogCacheFlag, 1899 "kextcache (%d) succeeded with '%s'.", 1900 client, watched->caches->root); 1901 watched->updterrs = 0; 1902 watched->updtattempts = 0; 1903 } 1904 } else { 1905 // not okay 1906 watched->updterrs++; 1907 OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogCacheFlag, 1908 "kextcache error while updating %s (error count: %d)", 1909 watched->caches->root, watched->updterrs); 1910 } 1911 1912 OSKextLog(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag, 1913 "%d unlocked %s", client, watched->caches->root); 1914 1915 // disable owners if we enabled them for the locker (updateMount() logs) 1916 if (watched->origMntFlags) { 1917 (void)updateMount(watched->caches->root, watched->origMntFlags); 1918 watched->origMntFlags = 0; 1919 } 1920 1921 // if kextcache failed, handleRebootHandoff() will fire off another 1922 // but only if updterrs is less than the failure limit 1923 handleWatchedHandoff(watched); 1924 if (!sRebootLock && sRebootWaiter) 1925 handleRebootHandoff(); 1926 1927 // once upon a time, we thought we could save five seconds here 1928 // instead, we will just call kextcache -u and it will build the mkext 1929 1930 rval = KERN_SUCCESS; 1931 1932finish: 1933 if (volUUID) CFRelease(volUUID); 1934 if (rval && watched) { 1935 OSKextLog(/* kext */ NULL, 1936 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1937 "Couldn't unlock '%s'.", watched->caches->root); 1938 } 1939 1940 return rval; 1941} 1942 1943#if 0 1944{ 1945mach_port_urefs_t refs = 0xff; 1946OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag, "DEBUG: mach_port_get_refs(..%d, send..): %x -> %x ref(s).", client, 1947mach_port_get_refs(mach_task_self(), client, MACH_PORT_RIGHT_SEND, &refs),refs); 1948} 1949#endif 1950 1951/****************************************************************************** 1952* port_died() tells us when a tracked send right goes away. 1953* We track send rights (on the client ports passed to us) as long as we 1954* have resources allocated to those clients. If they die, we get notified 1955* that the send right went away and then we clean up the associated resource. 1956* 1957* This function should only be called when shutdown/reboot exits before kextd 1958* or when a kextcache process is terminated against its will. 1959* 1960* If the client explicitly deallocates its *receive* right / port while we are 1961* tracking the corresponding send right, port_died() is also called, though 1962* kextcache should unlock the volume before doing that. 1963*****************************************************************************/ 1964static void port_died(CFMachPortRef cfport, void *info) 1965{ 1966 mach_port_t mport = cfport ? CFMachPortGetPort(cfport) : MACH_PORT_NULL; 1967 struct watchedVol* watched; 1968 // OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag, "DEBUG: port_died(%p/%d): entry.", cfport, mport); 1969 1970 // all watched-associated ports should have context 1971 if (!info || !cfport) { 1972 OSKextLog(/* kext */ NULL, 1973 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1974 "port_died() fatal error: invalid data."); 1975 goto finish; 1976 } 1977 1978 1979 // only means of release for reboot lock 1980 if (info == &sRebootLock) { 1981 if (cfport == sRebootLock) { 1982 // reboot/shutdown happened to exit before kextd 1983 // XX start timer now or when reboot lock granted? 1984 cleanupPort(&sRebootLock); 1985 } else if (cfport == sRebootWaiter) { 1986 cleanupPort(&sRebootWaiter); // gave up waiting 1987 } else { 1988 OSKextLog(/* kext */ NULL, 1989 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 1990 "Improperly tracked shutdown/reboot process died."); 1991 } 1992 goto finish; 1993 } 1994 1995 1996 // else ... handle volume lockers and waiters 1997 1998 watched = (struct watchedVol*)info; 1999 2000 // vol_disappeared() removes the volume from sFsysWatchDict before 2001 // cleaning up all the waiters, so unless the runloop somehow jumped 2002 // over here from the midst of that callout, no ports affiliated 2003 // with missing watchVol*'s should be dying. Even multiple 2004 // reboot waiters would all have the same context and the mach 2005 // port check above would catch them. 2006 if (CFDictionaryGetCountOfValue(sFsysWatchDict, watched) == 0) { 2007 OSKextLog(/* kext */ NULL, 2008 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 2009 "Warning: missing context for deallocated helper port."); 2010 cleanupPort(&cfport); 2011 goto finish; 2012 } 2013 2014 // watched points to valid data ... was it the locker? 2015 if (watched->lock && mport == CFMachPortGetPort(watched->lock)) { 2016 // try to disable owners if we enabled them for the locker 2017 if (watched->origMntFlags) { 2018 (void)updateMount(watched->caches->root, watched->origMntFlags); 2019 watched->origMntFlags = 0; 2020 } 2021 2022 // if locked, is anyone waiting? 2023 if (watched->lock) { 2024 OSKextLog(/* kext */ NULL, 2025 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 2026 "helper pid %d exited without unlocking '%s'.", 2027 CFMachPortGetPort(watched->lock), watched->caches->root); 2028 watched->updterrs++; 2029 handleWatchedHandoff(watched); // cleans up watched->lock 2030 if (!sRebootLock && sRebootWaiter) 2031 handleRebootHandoff(); 2032 } 2033 // if we were storing the worker pid, we might clean it up here 2034 // see also handleSignalInRunloop() over in kextd_main.c 2035 2036 } else { // it must have been a waiter 2037 CFIndex i; 2038 if (!watched->waiters) { 2039 OSKextLog(/* kext */ NULL, 2040 kOSKextLogWarningLevel | kOSKextLogIPCFlag, 2041 "Warning: presumed waiter died, but no waiters."); 2042 goto finish; 2043 } 2044 2045 for (i = CFArrayGetCount(watched->waiters); i-- > 0;) { 2046 CFMachPortRef waiter; 2047 2048 waiter = (CFMachPortRef)CFArrayGetValueAtIndex(watched->waiters,i); 2049 if (mport == CFMachPortGetPort(waiter)) { 2050 cleanupPort(&waiter); // --retainCount 2051 // (dropping the "extra" reference left by createWatchedPort() in 2052 // _kextmanager_lock_volume(). CFArray doesn't require the extra 2053 // reference, but we also use this for watched->lock, sReboot*, etc. 2054 CFArrayRemoveValueAtIndex(watched->waiters, i); // release 2055 goto finish; // success 2056 } 2057 } 2058 2059 OSKextLog(/* kext */ NULL, 2060 kOSKextLogWarningLevel | kOSKextLogIPCFlag, 2061 "Warning: %s: unknown helper exited.", watched->caches->root); 2062 } 2063 2064finish: 2065 return; 2066} 2067 2068 2069/****************************************************************************** 2070 * reconsiderVolumes() iterates the mount list, checking to see if any 2071 * local mounts have become interesting. It uses 2072 * reconsiderVolume() which calls vol_appeared on any we aren't yet watching. 2073 * If any newly added one needed an update, busyVol is set to its mountpoint. 2074 * Used after kAutoUpdateDelay to initialize things and to detect OS copies 2075 * at shutdown time. 2076 *****************************************************************************/ 2077static Boolean reconsiderVolume(mountpoint_t volToCheck) 2078{ 2079 int result; 2080 Boolean rval = false; 2081 DADiskRef disk = NULL; 2082 CFDictionaryRef dadesc = NULL; 2083 CFUUIDRef volUUID; 2084 struct watchedVol *watched; 2085 2086 // if it's unknown to diskarb, we can't do much with it (no error) 2087 result = 0; 2088 disk = createDiskForMount(sDASession, volToCheck); 2089 if (!disk) goto finish; 2090 2091 result = -1; 2092 dadesc = DADiskCopyDescription(disk); 2093 if (!dadesc) goto finish; 2094 volUUID = CFDictionaryGetValue(dadesc, kDADiskDescriptionVolumeUUIDKey); 2095 if (!volUUID) goto finish; 2096 2097 watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID); 2098 if (!watched) { 2099 // if not watched, check to see if it now has a known OS 2100 vol_appeared(disk, &rval); // handles any updates, etc 2101 } else { 2102 // if watched, let vol_changed check on its Apple_Boot status 2103 const void *objs[1] = { kDADiskDescriptionMediaContentKey }; 2104 CFArrayRef keys; 2105 2106 keys = CFArrayCreate(nil,objs,1,&kCFTypeArrayCallBacks); 2107 if (!keys) goto finish; 2108 2109 vol_changed(disk, keys, NULL); 2110 CFRelease(keys); 2111 } 2112 2113 result = 0; // boolean rval is set by call to vol_appeared() 2114 2115finish: 2116 if (disk) CFRelease(disk); 2117 if (dadesc) CFRelease(dadesc); 2118 if (result) { 2119 OSKextLog(/* kext */ NULL, 2120 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, 2121 "Error reconsidering volume %s.", volToCheck); 2122 } 2123 2124 return rval; 2125} 2126 2127static Boolean 2128reconsiderVolumes(mountpoint_t busyVol) 2129{ 2130 Boolean rval = false; 2131 char *errmsg = NULL; 2132 int nfsys, i; 2133 size_t bufsz; 2134 struct statfs *mounts = NULL; 2135 2136 // if not set up ... 2137 if (!sDASession) goto finish; 2138 2139 errmsg = "Error while getting mount list."; 2140 if (-1 == (nfsys = getfsstat(NULL, 0, MNT_NOWAIT))) goto finish; 2141 bufsz = nfsys * sizeof(struct statfs); 2142 if (!(mounts = malloc(bufsz))) goto finish; 2143 if (-1 == getfsstat(mounts, (int)bufsz, MNT_NOWAIT)) goto finish; 2144 2145 errmsg = NULL; // let reconsiderVolume() take it from here 2146 for (i = 0; i < nfsys; i++) { 2147 struct statfs *sfs = &mounts[i]; 2148 2149 if (sfs->f_flags & MNT_LOCAL && strcmp(sfs->f_fstypename, "devfs")) { 2150 if (reconsiderVolume(sfs->f_mntonname) && !rval) { 2151 // only capture first volume, but check them all 2152 strlcpy(busyVol, sfs->f_mntonname, sizeof(mountpoint_t)); 2153 rval = true; 2154 } 2155 } 2156 } 2157 2158 errmsg = NULL; 2159 2160finish: 2161 if (errmsg) { 2162 OSKextLog(/* kext */ NULL, 2163 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, 2164 "%s", errmsg); 2165 } 2166 if (mounts) free(mounts); 2167 2168 return rval; 2169} 2170 2171/* 2172 * helpers to deal with I/O Kit notifications, bootstamps removal 2173 */ 2174#define kBRNormalUpdate false 2175#define kBRInvalidateStamps true // cf. kBRUForceUpdateHelpers 2176static void 2177updateVolForMedia(const void *object, char *mediaDesc, int matchSize, 2178 CFStringRef matchingKeys[], CFTypeRef matchingValues[], 2179 bool invalidateStamps) 2180{ 2181 char * errorMessage = NULL; 2182 2183 CFDictionaryRef matchPropertyDict = NULL; 2184 CFMutableDictionaryRef matchingDict = NULL; 2185 io_service_t theMedia = MACH_PORT_NULL; 2186 DADiskRef dadisk = NULL; 2187 CFDictionaryRef dadesc = NULL; 2188 CFUUIDRef volUUID; // part of dadesc; not released 2189 struct watchedVol * watched = NULL; // do not free 2190 2191 // nothing to do if we're not watching yet 2192 if (!sFsysWatchDict) goto finish; 2193 2194 errorMessage = "change notification missing object."; 2195 if (!object) { 2196 goto finish; 2197 } 2198 2199 errorMessage = "error creating matching dictionary."; 2200 matchPropertyDict = CFDictionaryCreate(kCFAllocatorDefault, 2201 (void*)matchingKeys, matchingValues, matchSize, 2202 &kCFTypeDictionaryKeyCallBacks, 2203 &kCFTypeDictionaryValueCallBacks); 2204 if (!matchPropertyDict) { 2205 goto finish; 2206 } 2207 2208 matchingDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 2209 0, 2210 &kCFTypeDictionaryKeyCallBacks, 2211 &kCFTypeDictionaryValueCallBacks); 2212 if (!matchingDict) { 2213 goto finish; 2214 } 2215 CFDictionarySetValue(matchingDict, CFSTR(kIOPropertyMatchKey), 2216 matchPropertyDict); 2217 2218 errorMessage = NULL; // it might have gone away 2219 theMedia = IOServiceGetMatchingService(kIOMasterPortDefault, 2220 matchingDict); 2221 matchingDict = NULL; // IOServiceGetMatchingService() consumes reference! 2222 if (!theMedia) { 2223 goto finish; 2224 } 2225 2226 errorMessage = "unable to get DiskArb info."; 2227 dadisk = DADiskCreateFromIOMedia(nil, sDASession, theMedia); 2228 if (!dadisk) goto finish; 2229 dadesc = DADiskCopyDescription(dadisk); 2230 if (!dadesc) goto finish; 2231 volUUID = CFDictionaryGetValue(dadesc, kDADiskDescriptionVolumeUUIDKey); 2232 if (!volUUID) goto finish; 2233 2234 watched = (void*)CFDictionaryGetValue(sFsysWatchDict, volUUID); 2235 if (watched) { 2236 // instead of passing -f, directly invalidate the caches 2237 if (invalidateStamps) { 2238 char stampsdir[PATH_MAX]; 2239 errorMessage = "error unlinking bootstamps"; 2240 if (strlcpy(stampsdir,watched->caches->root,PATH_MAX)>=PATH_MAX || 2241 strlcat(stampsdir, kTSCacheDir, PATH_MAX) >= PATH_MAX) { 2242 goto finish; 2243 } 2244 if (sdeepunlink(watched->caches->cachefd, stampsdir)) { 2245 goto finish; 2246 } 2247 } 2248 2249 // might decide not to do the update right now 2250 checkScheduleUpdate(watched); 2251 } 2252 2253 errorMessage = NULL; 2254 2255finish: 2256 if (errorMessage) { 2257 OSKextLog(/* kext */ NULL, 2258 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 2259 "%s: %s", mediaDesc, errorMessage); 2260 } 2261 if (dadesc) CFRelease(dadesc); 2262 if (dadisk) CFRelease(dadisk); 2263 if (theMedia) IOObjectRelease(theMedia); 2264 if (matchingDict) CFRelease(matchingDict); 2265 if (matchPropertyDict) CFRelease(matchPropertyDict); 2266 2267 return; 2268} 2269 2270/******************************************************************************* 2271* updateRAIDSet() -- Something on a RAID set has changed, so we update all 2272* Apple_Boot partitions. 2273*******************************************************************************/ 2274#define RAID_MATCH_SIZE (2) 2275void updateRAIDSet( 2276 CFNotificationCenterRef center, 2277 void * observer, 2278 CFStringRef name, 2279 const void * object, 2280 CFDictionaryRef userInfo) 2281{ 2282 CFStringRef matchingKeys[RAID_MATCH_SIZE] = { 2283 CFSTR("RAID"), 2284 CFSTR("UUID") }; 2285 CFTypeRef matchingValues[RAID_MATCH_SIZE] = { 2286 (CFTypeRef)kCFBooleanTrue, 2287 (CFTypeRef)object }; 2288 2289 updateVolForMedia(object, "RAID Volume", RAID_MATCH_SIZE, matchingKeys, 2290 matchingValues, kBRInvalidateStamps); 2291} 2292 2293/******************************************************************************* 2294* updateCoreStorageVolume() -- Something on a CoreStorage logical volume has 2295* changed, so we may need to update its boot partition info. 2296*******************************************************************************/ 2297#define CSLV_MATCH_SIZE (2) 2298void updateCoreStorageVolume( 2299 CFNotificationCenterRef center, 2300 void * observer, 2301 CFStringRef name, 2302 const void * object, 2303 CFDictionaryRef userInfo) 2304{ 2305 CFStringRef matchingKeys[CSLV_MATCH_SIZE] = { 2306 CFSTR("CoreStorage"), 2307 CFSTR("UUID") }; 2308 CFTypeRef matchingValues[CSLV_MATCH_SIZE] = { 2309 (CFTypeRef)kCFBooleanTrue, 2310 (CFTypeRef)object }; 2311 bool invalidateStamps = false; 2312 2313 if (CFEqual(name, CFSTR(kCoreStorageNotificationLVGChanged))) { 2314OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 2315"LVG changed"); 2316 invalidateStamps = true; 2317 } 2318 updateVolForMedia(object, "CoreStorage Volume", CSLV_MATCH_SIZE, 2319 matchingKeys, matchingValues, invalidateStamps); 2320} 2321 2322 2323/******************************************************************************* 2324* logMTMessage() - log MessageTracer message 2325*******************************************************************************/ 2326static void logMTMessage(char *signature, char *result, char *mfmt, ...) 2327{ 2328 va_list ap; 2329 aslmsg amsg = asl_new(ASL_TYPE_MSG); 2330 2331 if (!amsg) { 2332 OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 2333 "asl_new() failed; MessageTracer message not logged"); 2334 return; 2335 } 2336 2337 asl_set(amsg, kMessageTracerDomainKey, kMTCachesDomain); 2338 asl_set(amsg, kMessageTracerSignatureKey, signature); 2339 // note: MT 'result' defaults to failure 2340 asl_set(amsg, kMessageTracerResultKey, result); 2341 2342 // send it 2343 va_start(ap, mfmt); 2344 asl_vlog(NULL /* default handle */, amsg, ASL_LEVEL_NOTICE, mfmt, ap); 2345 va_end(ap); 2346 2347 asl_free(amsg); 2348} 2349