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