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