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