]> git.saurik.com Git - apple/launchd.git/blame - launchd/src/SystemStarter.c
launchd-329.3.1.tar.gz
[apple/launchd.git] / launchd / src / SystemStarter.c
CommitLineData
e91b9f68
A
1/**
2 * System Starter main
3 * Wilfredo Sanchez | wsanchez@opensource.apple.com
4 * $Apple$
5 **
6 * Copyright (c) 1999-2002 Apple Computer, Inc. All rights reserved.
7 *
ed34e3c3
A
8 * @APPLE_APACHE_LICENSE_HEADER_START@
9 *
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
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
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.
21 *
22 * @APPLE_APACHE_LICENSE_HEADER_END@
e91b9f68
A
23 **/
24
5b0a4722
A
25#include <IOKit/IOKitLib.h>
26#include <sys/types.h>
27#include <sys/event.h>
28#include <sys/stat.h>
29#include <paths.h>
e91b9f68
A
30#include <unistd.h>
31#include <crt_externs.h>
32#include <fcntl.h>
33#include <syslog.h>
5b0a4722 34#include <assert.h>
e91b9f68 35#include <CoreFoundation/CoreFoundation.h>
ddbbfbc1
A
36#include <DiskArbitration/DiskArbitration.h>
37#include <DiskArbitration/DiskArbitrationPrivate.h>
e91b9f68
A
38#include <NSSystemDirectories.h>
39#include "IPC.h"
40#include "StartupItems.h"
41#include "SystemStarter.h"
42#include "SystemStarterIPC.h"
43
44bool gDebugFlag = false;
45bool gVerboseFlag = false;
46bool gNoRunFlag = false;
47
48static void usage(void) __attribute__((noreturn));
49static int system_starter(Action anAction, const char *aService);
ddbbfbc1 50static void displayErrorMessages(StartupContext aStartupContext, Action anAction);
5b0a4722 51static pid_t fwexec(const char *cmd, ...) __attribute__((sentinel));
ddbbfbc1 52static void autodiskmount(void);
5b0a4722
A
53static void dummy_sig(int signo __attribute__((unused)))
54{
55}
e91b9f68
A
56
57int
58main(int argc, char *argv[])
59{
5b0a4722 60 struct kevent kev;
e91b9f68 61 Action anAction = kActionStart;
5b0a4722
A
62 int ch, r, kq = kqueue();
63
64 assert(kq != -1);
65
66 EV_SET(&kev, SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, 0);
67 r = kevent(kq, &kev, 1, NULL, 0, NULL);
68 assert(r != -1);
69 signal(SIGTERM, dummy_sig);
e91b9f68
A
70
71 while ((ch = getopt(argc, argv, "gvxirdDqn?")) != -1) {
72 switch (ch) {
73 case 'v':
74 gVerboseFlag = true;
75 break;
76 case 'x':
77 case 'g':
78 case 'r':
79 case 'q':
80 break;
81 case 'd':
82 case 'D':
83 gDebugFlag = true;
84 break;
85 case 'n':
86 gNoRunFlag = true;
87 break;
88 case '?':
89 default:
90 usage();
91 break;
92 }
93 }
94 argc -= optind;
95 argv += optind;
96
5b0a4722 97 if (argc > 2) {
e91b9f68 98 usage();
5b0a4722 99 }
e91b9f68
A
100
101 openlog(getprogname(), LOG_PID|LOG_CONS|(gDebugFlag ? LOG_PERROR : 0), LOG_DAEMON);
5b0a4722 102 if (gDebugFlag) {
e91b9f68 103 setlogmask(LOG_UPTO(LOG_DEBUG));
5b0a4722
A
104 } else if (gVerboseFlag) {
105 setlogmask(LOG_UPTO(LOG_INFO));
106 } else {
107 setlogmask(LOG_UPTO(LOG_NOTICE));
108 }
e91b9f68
A
109
110 if (!gNoRunFlag && (getuid() != 0)) {
111 syslog(LOG_ERR, "must be root to run");
112 exit(EXIT_FAILURE);
113 }
114
115 if (argc > 0) {
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;
122 } else {
123 usage();
124 }
125 }
126
5b0a4722
A
127 if (argc == 2) {
128 exit(system_starter(anAction, argv[1]));
129 }
e91b9f68
A
130
131 unlink(kFixerPath);
132
5b0a4722
A
133 mach_timespec_t w = { 600, 0 };
134 kern_return_t kr;
5b0a4722
A
135
136 /*
137 * Too many old StartupItems had implicit dependancies on "Network" via
138 * other StartupItems that are now no-ops.
139 *
140 * SystemStarter is not on the critical path for boot up, so we'll
141 * stall here to deal with this legacy dependancy problem.
142 */
143
144 if ((kr = IOKitWaitQuiet(kIOMasterPortDefault, &w)) != kIOReturnSuccess) {
145 syslog(LOG_NOTICE, "IOKitWaitQuiet: %d\n", kr);
e91b9f68
A
146 }
147
5b0a4722 148 fwexec("/usr/sbin/ipconfig", "waitall", NULL);
ddbbfbc1 149 autodiskmount(); /* wait for Disk Arbitration to report idle */
5b0a4722
A
150
151 system_starter(kActionStart, NULL);
152
ddbbfbc1 153 if (StartupItemSecurityCheck("/etc/rc.local")) {
5b0a4722
A
154 fwexec(_PATH_BSHELL, "/etc/rc.local", NULL);
155 }
156
157 CFNotificationCenterPostNotificationWithOptions(
158 CFNotificationCenterGetDistributedCenter(),
159 CFSTR("com.apple.startupitems.completed"),
160 NULL, NULL,
161 kCFNotificationDeliverImmediately | kCFNotificationPostToAllSessions);
162
163 r = kevent(kq, NULL, 0, &kev, 1, NULL);
164 assert(r != -1);
165 assert(kev.filter == EVFILT_SIGNAL && kev.ident == SIGTERM);
166
ddbbfbc1 167 if (StartupItemSecurityCheck("/etc/rc.shutdown.local")) {
5b0a4722
A
168 fwexec(_PATH_BSHELL, "/etc/rc.shutdown.local", NULL);
169 }
170
171 system_starter(kActionStop, NULL);
172
173 exit(EXIT_SUCCESS);
e91b9f68
A
174}
175
176
177/**
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.
180 **/
181static void
182checkForActivity(StartupContext aStartupContext)
183{
184 static CFIndex aLastStatusDictionaryCount = -1;
185 static CFStringRef aWaitingForString = NULL;
186
187 if (aStartupContext && aStartupContext->aStatusDict) {
188 CFIndex aCount = CFDictionaryGetCount(aStartupContext->aStatusDict);
189
190 if (!aWaitingForString) {
191 aWaitingForString = CFSTR("Waiting for %@");
192 }
193 if (aLastStatusDictionaryCount == aCount) {
194 CFArrayRef aRunningList = StartupItemListGetRunning(aStartupContext->aWaitingList);
195 if (aRunningList && CFArrayGetCount(aRunningList) > 0) {
196 CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aRunningList, 0);
197 CFStringRef anItemDescription = StartupItemGetDescription(anItem);
198 CFStringRef aString = aWaitingForString && anItemDescription ?
199 CFStringCreateWithFormat(NULL, NULL, aWaitingForString, anItemDescription) : NULL;
200
201 if (aString) {
202 CF_syslog(LOG_INFO, CFSTR("%@"), aString);
203 CFRelease(aString);
204 }
205 if (anItemDescription)
206 CFRelease(anItemDescription);
207 }
208 if (aRunningList)
209 CFRelease(aRunningList);
210 }
211 aLastStatusDictionaryCount = aCount;
212 }
213}
214
215/*
216 * print out any error messages to the log regarding non starting StartupItems
217 */
218void
ddbbfbc1 219displayErrorMessages(StartupContext aStartupContext, Action anAction)
e91b9f68
A
220{
221 if (aStartupContext->aFailedList && CFArrayGetCount(aStartupContext->aFailedList) > 0) {
222 CFIndex anItemCount = CFArrayGetCount(aStartupContext->aFailedList);
223 CFIndex anItemIndex;
224
225
ddbbfbc1 226 syslog(LOG_WARNING, "The following StartupItems failed to %s properly:", (anAction == kActionStart) ? "start" : "stop");
e91b9f68
A
227
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);
232
233 if (anItemPath) {
234 CF_syslog(LOG_WARNING, CFSTR("%@"), anItemPath);
235 }
236 if (anErrorDescription) {
237 CF_syslog(LOG_WARNING, CFSTR(" - %@"), anErrorDescription);
238 } else {
239 CF_syslog(LOG_WARNING, CFSTR(" - %@"), kErrorInternal);
240 }
241 }
242 }
243 if (CFArrayGetCount(aStartupContext->aWaitingList) > 0) {
244 CFIndex anItemCount = CFArrayGetCount(aStartupContext->aWaitingList);
245 CFIndex anItemIndex;
246
247 syslog(LOG_WARNING, "The following StartupItems were not attempted due to failure of a required service:");
248
249 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
250 CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aStartupContext->aWaitingList, anItemIndex);
251 CFStringRef anItemPath = CFDictionaryGetValue(anItem, kBundlePathKey);
252 if (anItemPath) {
253 CF_syslog(LOG_WARNING, CFSTR("%@"), anItemPath);
254 }
255 }
256 }
257}
258
259
260static int
261system_starter(Action anAction, const char *aService_cstr)
262{
e91b9f68
A
263 CFStringRef aService = NULL;
264 NSSearchPathDomainMask aMask;
265
266 if (aService_cstr)
267 aService = CFStringCreateWithCString(kCFAllocatorDefault, aService_cstr, kCFStringEncodingUTF8);
268
269 StartupContext aStartupContext = (StartupContext) malloc(sizeof(struct StartupContextStorage));
270 if (!aStartupContext) {
271 syslog(LOG_ERR, "Not enough memory to allocate startup context");
272 return (1);
273 }
274 if (gDebugFlag && gNoRunFlag)
275 sleep(1);
276
e91b9f68
A
277 /**
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.
280 **/
281 aMask = NSSystemDomainMask | NSLocalDomainMask;
282
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;
289
290 if (aService) {
291 CFMutableArrayRef aDependentsList = StartupItemListCreateDependentsList(aStartupContext->aWaitingList, aService, anAction);
292
293 if (aDependentsList) {
294 CFRelease(aStartupContext->aWaitingList);
295 aStartupContext->aWaitingList = aDependentsList;
296 } else {
297 CF_syslog(LOG_ERR, CFSTR("Unknown service: %@"), aService);
298 return (1);
299 }
300 }
301 aStartupContext->aServicesCount = StartupItemListCountServices(aStartupContext->aWaitingList);
302
303 /**
304 * Do the run loop
305 **/
306 while (1) {
307 CFMutableDictionaryRef anItem = StartupItemListGetNext(aStartupContext->aWaitingList, aStartupContext->aStatusDict, anAction);
308
309 if (anItem) {
310 int err = StartupItemRun(aStartupContext->aStatusDict, anItem, anAction);
311 if (!err) {
312 ++aStartupContext->aRunningCount;
313 MonitorStartupItem(aStartupContext, anItem);
314 } else {
315 /* add item to failed list */
316 AddItemToFailedList(aStartupContext, anItem);
317
318 /* Remove the item from the waiting list. */
319 RemoveItemFromWaitingList(aStartupContext, anItem);
320 }
321 } else {
322 /*
323 * If no item was selected to run, and if no items
324 * are running, startup is done.
325 */
326 if (aStartupContext->aRunningCount == 0) {
327 syslog(LOG_DEBUG, "none left");
328 break;
329 }
330 /*
331 * Process incoming IPC messages and item
332 * terminations
333 */
334 switch (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 3.0, true)) {
335 case kCFRunLoopRunTimedOut:
336 checkForActivity(aStartupContext);
337 break;
338 case kCFRunLoopRunFinished:
339 break;
340 case kCFRunLoopRunStopped:
341 break;
342 case kCFRunLoopRunHandledSource:
343 break;
344 default:
345 /* unknown return value */
346 break;
347 }
348 }
349 }
350
351 /**
352 * Good-bye.
353 **/
ddbbfbc1 354 displayErrorMessages(aStartupContext, anAction);
e91b9f68
A
355
356 /* clean up */
357 if (aStartupContext->aStatusDict)
358 CFRelease(aStartupContext->aStatusDict);
359 if (aStartupContext->aWaitingList)
360 CFRelease(aStartupContext->aWaitingList);
361 if (aStartupContext->aFailedList)
362 CFRelease(aStartupContext->aFailedList);
363
364 free(aStartupContext);
365 return (0);
366}
367
368void
369CF_syslog(int level, CFStringRef message,...)
370{
371 char buf[8192];
372 CFStringRef cooked_msg;
373 va_list ap;
374
375 va_start(ap, message);
376 cooked_msg = CFStringCreateWithFormatAndArguments(NULL, NULL, message, ap);
377 va_end(ap);
378
379 if (CFStringGetCString(cooked_msg, buf, sizeof(buf), kCFStringEncodingUTF8))
380 syslog(level, buf);
381
382 CFRelease(cooked_msg);
383}
384
385static void
386usage(void)
387{
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"
391 "options:\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",
397 getprogname());
398 exit(EXIT_FAILURE);
399}
400
5b0a4722
A
401pid_t
402fwexec(const char *cmd, ...)
e91b9f68 403{
5b0a4722
A
404 const char *argv[100] = { cmd };
405 va_list ap;
406 int wstatus, i = 1;
407 pid_t p;
408
409 va_start(ap, cmd);
410 do {
411 argv[i] = va_arg(ap, char *);
412 } while (argv[i++]);
413 va_end(ap);
414
415 switch ((p = fork())) {
416 case -1:
417 return -1;
418 case 0:
419 execvp(argv[0], (char *const *)argv);
420 _exit(EXIT_FAILURE);
421 break;
422 default:
423 if (waitpid(p, &wstatus, 0) == -1) {
424 return -1;
425 } else if (WIFEXITED(wstatus)) {
426 if (WEXITSTATUS(wstatus) == 0) {
427 return 0;
428 } else {
429 syslog(LOG_WARNING, "%s exit status: %d", argv[0], WEXITSTATUS(wstatus));
430 }
431 } else {
432 /* must have died due to signal */
433 syslog(LOG_WARNING, "%s died: %s", argv[0], strsignal(WTERMSIG(wstatus)));
434 }
435 break;
436 }
437
438 return -1;
e91b9f68 439}
ddbbfbc1
A
440
441static void
442autodiskmount_idle(void* context __attribute__((unused)))
443{
444 CFRunLoopStop(CFRunLoopGetCurrent());
445}
446
447static void
448autodiskmount(void)
449{
450 DASessionRef session = DASessionCreate(NULL);
451 if (session) {
452 DASessionScheduleWithRunLoop(session, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
453 DARegisterIdleCallback(session, autodiskmount_idle, NULL);
454 CFRunLoopRun();
455 CFRelease(session);
456 }
457}