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 <NSSystemDirectories.h>
38 #include "StartupItems.h"
39 #include "SystemStarter.h"
40 #include "SystemStarterIPC.h"
42 bool gDebugFlag
= false;
43 bool gVerboseFlag
= false;
44 bool gNoRunFlag
= false;
46 static void usage(void) __attribute__((noreturn
));
47 static int system_starter(Action anAction
, const char *aService
);
48 static void displayErrorMessages(StartupContext aStartupContext
);
49 static pid_t
fwexec(const char *cmd
, ...) __attribute__((sentinel
));
50 static void dummy_sig(int signo
__attribute__((unused
)))
55 main(int argc
, char *argv
[])
58 Action anAction
= kActionStart
;
59 int ch
, r
, kq
= kqueue();
63 EV_SET(&kev
, SIGTERM
, EVFILT_SIGNAL
, EV_ADD
, 0, 0, 0);
64 r
= kevent(kq
, &kev
, 1, NULL
, 0, NULL
);
66 signal(SIGTERM
, dummy_sig
);
68 while ((ch
= getopt(argc
, argv
, "gvxirdDqn?")) != -1) {
98 openlog(getprogname(), LOG_PID
|LOG_CONS
|(gDebugFlag
? LOG_PERROR
: 0), LOG_DAEMON
);
100 setlogmask(LOG_UPTO(LOG_DEBUG
));
101 } else if (gVerboseFlag
) {
102 setlogmask(LOG_UPTO(LOG_INFO
));
104 setlogmask(LOG_UPTO(LOG_NOTICE
));
107 if (!gNoRunFlag
&& (getuid() != 0)) {
108 syslog(LOG_ERR
, "must be root to run");
113 if (strcmp(argv
[0], "start") == 0) {
114 anAction
= kActionStart
;
115 } else if (strcmp(argv
[0], "stop") == 0) {
116 anAction
= kActionStop
;
117 } else if (strcmp(argv
[0], "restart") == 0) {
118 anAction
= kActionRestart
;
125 exit(system_starter(anAction
, argv
[1]));
130 mach_timespec_t w
= { 600, 0 };
135 * Too many old StartupItems had implicit dependancies on "Network" via
136 * other StartupItems that are now no-ops.
138 * SystemStarter is not on the critical path for boot up, so we'll
139 * stall here to deal with this legacy dependancy problem.
142 if ((kr
= IOKitWaitQuiet(kIOMasterPortDefault
, &w
)) != kIOReturnSuccess
) {
143 syslog(LOG_NOTICE
, "IOKitWaitQuiet: %d\n", kr
);
146 fwexec("/usr/sbin/ipconfig", "waitall", NULL
);
147 fwexec("/sbin/autodiskmount", "-va", NULL
);
149 system_starter(kActionStart
, NULL
);
151 if (stat("/etc/rc.local", &sb
) != -1) {
152 fwexec(_PATH_BSHELL
, "/etc/rc.local", NULL
);
155 CFNotificationCenterPostNotificationWithOptions(
156 CFNotificationCenterGetDistributedCenter(),
157 CFSTR("com.apple.startupitems.completed"),
159 kCFNotificationDeliverImmediately
| kCFNotificationPostToAllSessions
);
161 r
= kevent(kq
, NULL
, 0, &kev
, 1, NULL
);
163 assert(kev
.filter
== EVFILT_SIGNAL
&& kev
.ident
== SIGTERM
);
165 if (stat("/etc/rc.shutdown.local", &sb
) != -1) {
166 fwexec(_PATH_BSHELL
, "/etc/rc.shutdown.local", NULL
);
169 system_starter(kActionStop
, NULL
);
176 * checkForActivity checks to see if any items have completed since the last invokation.
177 * If not, a message is displayed showing what item(s) are being waited on.
180 checkForActivity(StartupContext aStartupContext
)
182 static CFIndex aLastStatusDictionaryCount
= -1;
183 static CFStringRef aWaitingForString
= NULL
;
185 if (aStartupContext
&& aStartupContext
->aStatusDict
) {
186 CFIndex aCount
= CFDictionaryGetCount(aStartupContext
->aStatusDict
);
188 if (!aWaitingForString
) {
189 aWaitingForString
= CFSTR("Waiting for %@");
191 if (aLastStatusDictionaryCount
== aCount
) {
192 CFArrayRef aRunningList
= StartupItemListGetRunning(aStartupContext
->aWaitingList
);
193 if (aRunningList
&& CFArrayGetCount(aRunningList
) > 0) {
194 CFMutableDictionaryRef anItem
= (CFMutableDictionaryRef
) CFArrayGetValueAtIndex(aRunningList
, 0);
195 CFStringRef anItemDescription
= StartupItemGetDescription(anItem
);
196 CFStringRef aString
= aWaitingForString
&& anItemDescription
?
197 CFStringCreateWithFormat(NULL
, NULL
, aWaitingForString
, anItemDescription
) : NULL
;
200 CF_syslog(LOG_INFO
, CFSTR("%@"), aString
);
203 if (anItemDescription
)
204 CFRelease(anItemDescription
);
207 CFRelease(aRunningList
);
209 aLastStatusDictionaryCount
= aCount
;
214 * print out any error messages to the log regarding non starting StartupItems
217 displayErrorMessages(StartupContext aStartupContext
)
219 if (aStartupContext
->aFailedList
&& CFArrayGetCount(aStartupContext
->aFailedList
) > 0) {
220 CFIndex anItemCount
= CFArrayGetCount(aStartupContext
->aFailedList
);
224 syslog(LOG_WARNING
, "The following StartupItems failed to properly start:");
226 for (anItemIndex
= 0; anItemIndex
< anItemCount
; anItemIndex
++) {
227 CFMutableDictionaryRef anItem
= (CFMutableDictionaryRef
) CFArrayGetValueAtIndex(aStartupContext
->aFailedList
, anItemIndex
);
228 CFStringRef anErrorDescription
= CFDictionaryGetValue(anItem
, kErrorKey
);
229 CFStringRef anItemPath
= CFDictionaryGetValue(anItem
, kBundlePathKey
);
232 CF_syslog(LOG_WARNING
, CFSTR("%@"), anItemPath
);
234 if (anErrorDescription
) {
235 CF_syslog(LOG_WARNING
, CFSTR(" - %@"), anErrorDescription
);
237 CF_syslog(LOG_WARNING
, CFSTR(" - %@"), kErrorInternal
);
241 if (CFArrayGetCount(aStartupContext
->aWaitingList
) > 0) {
242 CFIndex anItemCount
= CFArrayGetCount(aStartupContext
->aWaitingList
);
245 syslog(LOG_WARNING
, "The following StartupItems were not attempted due to failure of a required service:");
247 for (anItemIndex
= 0; anItemIndex
< anItemCount
; anItemIndex
++) {
248 CFMutableDictionaryRef anItem
= (CFMutableDictionaryRef
) CFArrayGetValueAtIndex(aStartupContext
->aWaitingList
, anItemIndex
);
249 CFStringRef anItemPath
= CFDictionaryGetValue(anItem
, kBundlePathKey
);
251 CF_syslog(LOG_WARNING
, CFSTR("%@"), anItemPath
);
259 system_starter(Action anAction
, const char *aService_cstr
)
261 CFStringRef aService
= NULL
;
262 NSSearchPathDomainMask aMask
;
265 aService
= CFStringCreateWithCString(kCFAllocatorDefault
, aService_cstr
, kCFStringEncodingUTF8
);
267 StartupContext aStartupContext
= (StartupContext
) malloc(sizeof(struct StartupContextStorage
));
268 if (!aStartupContext
) {
269 syslog(LOG_ERR
, "Not enough memory to allocate startup context");
272 if (gDebugFlag
&& gNoRunFlag
)
276 * Get a list of Startup Items which are in /Local and /System.
277 * We can't search /Network yet because the network isn't up.
279 aMask
= NSSystemDomainMask
| NSLocalDomainMask
;
281 aStartupContext
->aWaitingList
= StartupItemListCreateWithMask(aMask
);
282 aStartupContext
->aFailedList
= NULL
;
283 aStartupContext
->aStatusDict
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
,
284 &kCFTypeDictionaryValueCallBacks
);
285 aStartupContext
->aServicesCount
= 0;
286 aStartupContext
->aRunningCount
= 0;
289 CFMutableArrayRef aDependentsList
= StartupItemListCreateDependentsList(aStartupContext
->aWaitingList
, aService
, anAction
);
291 if (aDependentsList
) {
292 CFRelease(aStartupContext
->aWaitingList
);
293 aStartupContext
->aWaitingList
= aDependentsList
;
295 CF_syslog(LOG_ERR
, CFSTR("Unknown service: %@"), aService
);
299 aStartupContext
->aServicesCount
= StartupItemListCountServices(aStartupContext
->aWaitingList
);
305 CFMutableDictionaryRef anItem
= StartupItemListGetNext(aStartupContext
->aWaitingList
, aStartupContext
->aStatusDict
, anAction
);
308 int err
= StartupItemRun(aStartupContext
->aStatusDict
, anItem
, anAction
);
310 ++aStartupContext
->aRunningCount
;
311 MonitorStartupItem(aStartupContext
, anItem
);
313 /* add item to failed list */
314 AddItemToFailedList(aStartupContext
, anItem
);
316 /* Remove the item from the waiting list. */
317 RemoveItemFromWaitingList(aStartupContext
, anItem
);
321 * If no item was selected to run, and if no items
322 * are running, startup is done.
324 if (aStartupContext
->aRunningCount
== 0) {
325 syslog(LOG_DEBUG
, "none left");
329 * Process incoming IPC messages and item
332 switch (CFRunLoopRunInMode(kCFRunLoopDefaultMode
, 3.0, true)) {
333 case kCFRunLoopRunTimedOut
:
334 checkForActivity(aStartupContext
);
336 case kCFRunLoopRunFinished
:
338 case kCFRunLoopRunStopped
:
340 case kCFRunLoopRunHandledSource
:
343 /* unknown return value */
352 displayErrorMessages(aStartupContext
);
355 if (aStartupContext
->aStatusDict
)
356 CFRelease(aStartupContext
->aStatusDict
);
357 if (aStartupContext
->aWaitingList
)
358 CFRelease(aStartupContext
->aWaitingList
);
359 if (aStartupContext
->aFailedList
)
360 CFRelease(aStartupContext
->aFailedList
);
362 free(aStartupContext
);
367 CF_syslog(int level
, CFStringRef message
,...)
370 CFStringRef cooked_msg
;
373 va_start(ap
, message
);
374 cooked_msg
= CFStringCreateWithFormatAndArguments(NULL
, NULL
, message
, ap
);
377 if (CFStringGetCString(cooked_msg
, buf
, sizeof(buf
), kCFStringEncodingUTF8
))
380 CFRelease(cooked_msg
);
386 fprintf(stderr
, "usage: %s [-vdqn?] [ <action> [ <item> ] ]\n"
387 "\t<action>: action to take (start|stop|restart); default is start\n"
388 "\t<item> : name of item to act on; default is all items\n"
390 "\t-v: verbose startup\n"
391 "\t-d: print debugging output\n"
392 "\t-q: be quiet (disable debugging output)\n"
393 "\t-n: don't actually perform action on items (pretend mode)\n"
394 "\t-?: show this help\n",
400 fwexec(const char *cmd
, ...)
402 const char *argv
[100] = { cmd
};
409 argv
[i
] = va_arg(ap
, char *);
413 switch ((p
= fork())) {
417 execvp(argv
[0], (char *const *)argv
);
421 if (waitpid(p
, &wstatus
, 0) == -1) {
423 } else if (WIFEXITED(wstatus
)) {
424 if (WEXITSTATUS(wstatus
) == 0) {
427 syslog(LOG_WARNING
, "%s exit status: %d", argv
[0], WEXITSTATUS(wstatus
));
430 /* must have died due to signal */
431 syslog(LOG_WARNING
, "%s died: %s", argv
[0], strsignal(WTERMSIG(wstatus
)));