3 * Wilfredo Sanchez | wsanchez@opensource.apple.com
6 * Copyright (c) 1999-2002 Apple Computer, Inc. All rights reserved.
8 * @APPLE_APACHE_LICENSE_HEADER_START@
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * Unless required by applicable law or agreed to in writing, software
17 * distributed under the License is distributed on an "AS IS" BASIS,
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 * See the License for the specific language governing permissions and
20 * limitations under the License.
22 * @APPLE_APACHE_LICENSE_HEADER_END@
25 #include <IOKit/IOKitLib.h>
26 #include <sys/types.h>
27 #include <sys/event.h>
31 #include <crt_externs.h>
35 #include <CoreFoundation/CoreFoundation.h>
36 #include <DiskArbitration/DiskArbitration.h>
37 #include <DiskArbitration/DiskArbitrationPrivate.h>
38 #include <NSSystemDirectories.h>
40 #include "StartupItems.h"
41 #include "SystemStarter.h"
42 #include "SystemStarterIPC.h"
44 bool gDebugFlag
= false;
45 bool gVerboseFlag
= false;
46 bool gNoRunFlag
= false;
48 static void usage(void) __attribute__((noreturn
));
49 static int system_starter(Action anAction
, const char *aService
);
50 static void displayErrorMessages(StartupContext aStartupContext
, Action anAction
);
51 static pid_t
fwexec(const char *cmd
, ...) __attribute__((sentinel
));
52 static void autodiskmount(void);
53 static void dummy_sig(int signo
__attribute__((unused
)))
58 main(int argc
, char *argv
[])
61 Action anAction
= kActionStart
;
62 int ch
, r
, kq
= kqueue();
66 EV_SET(&kev
, SIGTERM
, EVFILT_SIGNAL
, EV_ADD
, 0, 0, 0);
67 r
= kevent(kq
, &kev
, 1, NULL
, 0, NULL
);
69 signal(SIGTERM
, dummy_sig
);
71 while ((ch
= getopt(argc
, argv
, "gvxirdDqn?")) != -1) {
101 openlog(getprogname(), LOG_PID
|LOG_CONS
|(gDebugFlag
? LOG_PERROR
: 0), LOG_DAEMON
);
103 setlogmask(LOG_UPTO(LOG_DEBUG
));
104 } else if (gVerboseFlag
) {
105 setlogmask(LOG_UPTO(LOG_INFO
));
107 setlogmask(LOG_UPTO(LOG_NOTICE
));
110 if (!gNoRunFlag
&& (getuid() != 0)) {
111 syslog(LOG_ERR
, "must be root to run");
116 if (strcmp(argv
[0], "start") == 0) {
117 anAction
= kActionStart
;
118 } else if (strcmp(argv
[0], "stop") == 0) {
119 anAction
= kActionStop
;
120 } else if (strcmp(argv
[0], "restart") == 0) {
121 anAction
= kActionRestart
;
128 exit(system_starter(anAction
, argv
[1]));
133 mach_timespec_t w
= { 600, 0 };
137 * Too many old StartupItems had implicit dependancies on "Network" via
138 * other StartupItems that are now no-ops.
140 * SystemStarter is not on the critical path for boot up, so we'll
141 * stall here to deal with this legacy dependancy problem.
144 if ((kr
= IOKitWaitQuiet(kIOMasterPortDefault
, &w
)) != kIOReturnSuccess
) {
145 syslog(LOG_NOTICE
, "IOKitWaitQuiet: %d\n", kr
);
148 fwexec("/usr/sbin/ipconfig", "waitall", NULL
);
149 autodiskmount(); /* wait for Disk Arbitration to report idle */
151 system_starter(kActionStart
, NULL
);
153 if (StartupItemSecurityCheck("/etc/rc.local")) {
154 fwexec(_PATH_BSHELL
, "/etc/rc.local", NULL
);
157 CFNotificationCenterPostNotificationWithOptions(
158 CFNotificationCenterGetDistributedCenter(),
159 CFSTR("com.apple.startupitems.completed"),
161 kCFNotificationDeliverImmediately
| kCFNotificationPostToAllSessions
);
163 r
= kevent(kq
, NULL
, 0, &kev
, 1, NULL
);
165 assert(kev
.filter
== EVFILT_SIGNAL
&& kev
.ident
== SIGTERM
);
167 if (StartupItemSecurityCheck("/etc/rc.shutdown.local")) {
168 fwexec(_PATH_BSHELL
, "/etc/rc.shutdown.local", NULL
);
171 system_starter(kActionStop
, NULL
);
178 * checkForActivity checks to see if any items have completed since the last invokation.
179 * If not, a message is displayed showing what item(s) are being waited on.
182 checkForActivity(StartupContext aStartupContext
)
184 static CFIndex aLastStatusDictionaryCount
= -1;
185 static CFStringRef aWaitingForString
= NULL
;
187 if (aStartupContext
&& aStartupContext
->aStatusDict
) {
188 CFIndex aCount
= CFDictionaryGetCount(aStartupContext
->aStatusDict
);
190 if (!aWaitingForString
) {
191 aWaitingForString
= CFSTR("Waiting for %@");
193 if (aLastStatusDictionaryCount
== aCount
) {
194 CFArrayRef aRunningList
= StartupItemListCreateFromRunning(aStartupContext
->aWaitingList
);
195 if (aRunningList
&& CFArrayGetCount(aRunningList
) > 0) {
196 CFMutableDictionaryRef anItem
= (CFMutableDictionaryRef
) CFArrayGetValueAtIndex(aRunningList
, 0);
197 CFStringRef anItemDescription
= StartupItemCreateDescription(anItem
);
198 CFStringRef aString
= aWaitingForString
&& anItemDescription
?
199 CFStringCreateWithFormat(NULL
, NULL
, aWaitingForString
, anItemDescription
) : NULL
;
202 CF_syslog(LOG_INFO
, CFSTR("%@"), aString
);
205 if (anItemDescription
)
206 CFRelease(anItemDescription
);
209 CFRelease(aRunningList
);
211 aLastStatusDictionaryCount
= aCount
;
216 * print out any error messages to the log regarding non starting StartupItems
219 displayErrorMessages(StartupContext aStartupContext
, Action anAction
)
221 if (aStartupContext
->aFailedList
&& CFArrayGetCount(aStartupContext
->aFailedList
) > 0) {
222 CFIndex anItemCount
= CFArrayGetCount(aStartupContext
->aFailedList
);
226 syslog(LOG_WARNING
, "The following StartupItems failed to %s properly:", (anAction
== kActionStart
) ? "start" : "stop");
228 for (anItemIndex
= 0; anItemIndex
< anItemCount
; anItemIndex
++) {
229 CFMutableDictionaryRef anItem
= (CFMutableDictionaryRef
) CFArrayGetValueAtIndex(aStartupContext
->aFailedList
, anItemIndex
);
230 CFStringRef anErrorDescription
= CFDictionaryGetValue(anItem
, kErrorKey
);
231 CFStringRef anItemPath
= CFDictionaryGetValue(anItem
, kBundlePathKey
);
234 CF_syslog(LOG_WARNING
, CFSTR("%@"), anItemPath
);
236 if (anErrorDescription
) {
237 CF_syslog(LOG_WARNING
, CFSTR(" - %@"), anErrorDescription
);
239 CF_syslog(LOG_WARNING
, CFSTR(" - %@"), kErrorInternal
);
243 if (CFArrayGetCount(aStartupContext
->aWaitingList
) > 0) {
244 CFIndex anItemCount
= CFArrayGetCount(aStartupContext
->aWaitingList
);
247 syslog(LOG_WARNING
, "The following StartupItems were not attempted due to failure of a required service:");
249 for (anItemIndex
= 0; anItemIndex
< anItemCount
; anItemIndex
++) {
250 CFMutableDictionaryRef anItem
= (CFMutableDictionaryRef
) CFArrayGetValueAtIndex(aStartupContext
->aWaitingList
, anItemIndex
);
251 CFStringRef anItemPath
= CFDictionaryGetValue(anItem
, kBundlePathKey
);
253 CF_syslog(LOG_WARNING
, CFSTR("%@"), anItemPath
);
261 system_starter(Action anAction
, const char *aService_cstr
)
263 CFStringRef aService
= NULL
;
264 NSSearchPathDomainMask aMask
;
267 aService
= CFStringCreateWithCString(kCFAllocatorDefault
, aService_cstr
, kCFStringEncodingUTF8
);
269 StartupContext aStartupContext
= (StartupContext
) malloc(sizeof(struct StartupContextStorage
));
270 if (!aStartupContext
) {
271 syslog(LOG_ERR
, "Not enough memory to allocate startup context");
274 if (gDebugFlag
&& gNoRunFlag
)
278 * Get a list of Startup Items which are in /Local and /System.
279 * We can't search /Network yet because the network isn't up.
281 aMask
= NSSystemDomainMask
| NSLocalDomainMask
;
283 aStartupContext
->aWaitingList
= StartupItemListCreateWithMask(aMask
);
284 aStartupContext
->aFailedList
= NULL
;
285 aStartupContext
->aStatusDict
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
,
286 &kCFTypeDictionaryValueCallBacks
);
287 aStartupContext
->aServicesCount
= 0;
288 aStartupContext
->aRunningCount
= 0;
291 CFMutableArrayRef aDependentsList
= StartupItemListCreateDependentsList(aStartupContext
->aWaitingList
, aService
, anAction
);
293 if (aDependentsList
) {
294 CFRelease(aStartupContext
->aWaitingList
);
295 aStartupContext
->aWaitingList
= aDependentsList
;
297 CF_syslog(LOG_ERR
, CFSTR("Unknown service: %@"), aService
);
301 aStartupContext
->aServicesCount
= StartupItemListCountServices(aStartupContext
->aWaitingList
);
307 CFMutableDictionaryRef anItem
= StartupItemListGetNext(aStartupContext
->aWaitingList
, aStartupContext
->aStatusDict
, anAction
);
310 int err
= StartupItemRun(aStartupContext
->aStatusDict
, anItem
, anAction
);
312 ++aStartupContext
->aRunningCount
;
313 MonitorStartupItem(aStartupContext
, anItem
);
315 /* add item to failed list */
316 AddItemToFailedList(aStartupContext
, anItem
);
318 /* Remove the item from the waiting list. */
319 RemoveItemFromWaitingList(aStartupContext
, anItem
);
323 * If no item was selected to run, and if no items
324 * are running, startup is done.
326 if (aStartupContext
->aRunningCount
== 0) {
327 syslog(LOG_DEBUG
, "none left");
331 * Process incoming IPC messages and item
334 switch (CFRunLoopRunInMode(kCFRunLoopDefaultMode
, 3.0, true)) {
335 case kCFRunLoopRunTimedOut
:
336 checkForActivity(aStartupContext
);
338 case kCFRunLoopRunFinished
:
340 case kCFRunLoopRunStopped
:
342 case kCFRunLoopRunHandledSource
:
345 /* unknown return value */
354 displayErrorMessages(aStartupContext
, anAction
);
357 if (aStartupContext
->aStatusDict
)
358 CFRelease(aStartupContext
->aStatusDict
);
359 if (aStartupContext
->aWaitingList
)
360 CFRelease(aStartupContext
->aWaitingList
);
361 if (aStartupContext
->aFailedList
)
362 CFRelease(aStartupContext
->aFailedList
);
364 free(aStartupContext
);
369 CF_syslog(int level
, CFStringRef message
,...)
372 CFStringRef cooked_msg
;
375 va_start(ap
, message
);
376 cooked_msg
= CFStringCreateWithFormatAndArguments(NULL
, NULL
, message
, ap
);
379 if (CFStringGetCString(cooked_msg
, buf
, sizeof(buf
), kCFStringEncodingUTF8
))
380 syslog(level
, "%s", buf
);
382 CFRelease(cooked_msg
);
388 fprintf(stderr
, "usage: %s [-vdqn?] [ <action> [ <item> ] ]\n"
389 "\t<action>: action to take (start|stop|restart); default is start\n"
390 "\t<item> : name of item to act on; default is all items\n"
392 "\t-v: verbose startup\n"
393 "\t-d: print debugging output\n"
394 "\t-q: be quiet (disable debugging output)\n"
395 "\t-n: don't actually perform action on items (pretend mode)\n"
396 "\t-?: show this help\n",
402 fwexec(const char *cmd
, ...)
404 const char *argv
[100] = { cmd
};
411 argv
[i
] = va_arg(ap
, char *);
415 switch ((p
= fork())) {
419 execvp(argv
[0], (char *const *)argv
);
423 if (waitpid(p
, &wstatus
, 0) == -1) {
425 } else if (WIFEXITED(wstatus
)) {
426 if (WEXITSTATUS(wstatus
) == 0) {
429 syslog(LOG_WARNING
, "%s exit status: %d", argv
[0], WEXITSTATUS(wstatus
));
432 /* must have died due to signal */
433 syslog(LOG_WARNING
, "%s died: %s", argv
[0], strsignal(WTERMSIG(wstatus
)));
442 autodiskmount_idle(void* context
__attribute__((unused
)))
444 CFRunLoopStop(CFRunLoopGetCurrent());
450 DASessionRef session
= DASessionCreate(NULL
);
452 DASessionScheduleWithRunLoop(session
, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode
);
453 DARegisterIdleCallback(session
, autodiskmount_idle
, NULL
);