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