]> git.saurik.com Git - apple/launchd.git/blame - launchd/src/SystemStarter.c
launchd-106.20.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 *
8 * @APPLE_LICENSE_HEADER_START@
9 *
10 * Portions Copyright (c) 1999 Apple Computer, Inc. All Rights
11 * Reserved. This file contains Original Code and/or Modifications of
12 * Original Code as defined in and that are subject to the Apple Public
13 * Source License Version 1.1 (the "License"). You may not use this file
14 * except in compliance with the License. Please obtain a copy of the
15 * License at http://www.apple.com/publicsource and read it before using
16 * this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the
23 * License for the specific language governing rights and limitations
24 * under the License.
25 *
26 * @APPLE_LICENSE_HEADER_END@
27 **/
28
29#include <unistd.h>
30#include <crt_externs.h>
31#include <fcntl.h>
32#include <syslog.h>
33#include <CoreFoundation/CoreFoundation.h>
34#include <NSSystemDirectories.h>
35#include "IPC.h"
36#include "StartupItems.h"
37#include "SystemStarter.h"
38#include "SystemStarterIPC.h"
39
40bool gDebugFlag = false;
41bool gVerboseFlag = false;
42bool gNoRunFlag = false;
43
44static void usage(void) __attribute__((noreturn));
45static int system_starter(Action anAction, const char *aService);
46static void doCFnote(void);
47
48int
49main(int argc, char *argv[])
50{
51 Action anAction = kActionStart;
52 char *aService = NULL;
53 int ch;
54
55 while ((ch = getopt(argc, argv, "gvxirdDqn?")) != -1) {
56 switch (ch) {
57 case 'v':
58 gVerboseFlag = true;
59 break;
60 case 'x':
61 case 'g':
62 case 'r':
63 case 'q':
64 break;
65 case 'd':
66 case 'D':
67 gDebugFlag = true;
68 break;
69 case 'n':
70 gNoRunFlag = true;
71 break;
72 case '?':
73 default:
74 usage();
75 break;
76 }
77 }
78 argc -= optind;
79 argv += optind;
80
81 if (argc > 2)
82 usage();
83
84 openlog(getprogname(), LOG_PID|LOG_CONS|(gDebugFlag ? LOG_PERROR : 0), LOG_DAEMON);
85 setlogmask(LOG_UPTO(LOG_NOTICE));
86 if (gVerboseFlag)
87 setlogmask(LOG_UPTO(LOG_INFO));
88 if (gDebugFlag)
89 setlogmask(LOG_UPTO(LOG_DEBUG));
90
91 if (!gNoRunFlag && (getuid() != 0)) {
92 syslog(LOG_ERR, "must be root to run");
93 exit(EXIT_FAILURE);
94 }
95
96 if (argc > 0) {
97 if (strcmp(argv[0], "start") == 0) {
98 anAction = kActionStart;
99 } else if (strcmp(argv[0], "stop") == 0) {
100 anAction = kActionStop;
101 } else if (strcmp(argv[0], "restart") == 0) {
102 anAction = kActionRestart;
103 } else {
104 usage();
105 }
106 }
107
108 atexit(doCFnote);
109
110 unlink(kFixerPath);
111
112 if (argc == 2) {
113 aService = argv[1];
114 } else if (!gDebugFlag && anAction != kActionStop) {
115 pid_t ipwa;
116 int status;
117
118 setpriority(PRIO_PROCESS, 0, 20);
119 daemon(0, 0);
120
121 /* Too many old StartupItems had implicit dependancies on
122 * "Network" via other StartupItems that are now no-ops.
123 *
124 * SystemStarter is not on the critical path for boot up,
125 * so we'll stall here to deal with this legacy dependancy
126 * problem.
127 */
128 switch ((ipwa = fork())) {
129 case -1:
130 syslog(LOG_WARNING, "fork(): %m");
131 break;
132 case 0:
133 execl("/usr/sbin/ipconfig", "ipconfig", "waitall", NULL);
134 syslog(LOG_WARNING, "execl(): %m");
135 exit(EXIT_FAILURE);
136 default:
137 if (waitpid(ipwa, &status, 0) == -1) {
138 syslog(LOG_WARNING, "waitpid(): %m");
139 break;
140 } else if (WIFEXITED(status)) {
141 if (WEXITSTATUS(status) == 0) {
142 break;
143 } else {
144 syslog(LOG_WARNING, "ipconfig waitall exit status: %d", WEXITSTATUS(status));
145 }
146 } else {
147 /* must have died due to signal */
148 syslog(LOG_WARNING, "ipconfig waitall: %s", strsignal(WTERMSIG(status)));
149 }
150 break;
151 }
152 }
153
154 exit(system_starter(anAction, aService));
155}
156
157
158/**
159 * checkForActivity checks to see if any items have completed since the last invokation.
160 * If not, a message is displayed showing what item(s) are being waited on.
161 **/
162static void
163checkForActivity(StartupContext aStartupContext)
164{
165 static CFIndex aLastStatusDictionaryCount = -1;
166 static CFStringRef aWaitingForString = NULL;
167
168 if (aStartupContext && aStartupContext->aStatusDict) {
169 CFIndex aCount = CFDictionaryGetCount(aStartupContext->aStatusDict);
170
171 if (!aWaitingForString) {
172 aWaitingForString = CFSTR("Waiting for %@");
173 }
174 if (aLastStatusDictionaryCount == aCount) {
175 CFArrayRef aRunningList = StartupItemListGetRunning(aStartupContext->aWaitingList);
176 if (aRunningList && CFArrayGetCount(aRunningList) > 0) {
177 CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aRunningList, 0);
178 CFStringRef anItemDescription = StartupItemGetDescription(anItem);
179 CFStringRef aString = aWaitingForString && anItemDescription ?
180 CFStringCreateWithFormat(NULL, NULL, aWaitingForString, anItemDescription) : NULL;
181
182 if (aString) {
183 CF_syslog(LOG_INFO, CFSTR("%@"), aString);
184 CFRelease(aString);
185 }
186 if (anItemDescription)
187 CFRelease(anItemDescription);
188 }
189 if (aRunningList)
190 CFRelease(aRunningList);
191 }
192 aLastStatusDictionaryCount = aCount;
193 }
194}
195
196/*
197 * print out any error messages to the log regarding non starting StartupItems
198 */
199void
200displayErrorMessages(StartupContext aStartupContext)
201{
202 if (aStartupContext->aFailedList && CFArrayGetCount(aStartupContext->aFailedList) > 0) {
203 CFIndex anItemCount = CFArrayGetCount(aStartupContext->aFailedList);
204 CFIndex anItemIndex;
205
206
207 syslog(LOG_WARNING, "The following StartupItems failed to properly start:");
208
209 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
210 CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aStartupContext->aFailedList, anItemIndex);
211 CFStringRef anErrorDescription = CFDictionaryGetValue(anItem, kErrorKey);
212 CFStringRef anItemPath = CFDictionaryGetValue(anItem, kBundlePathKey);
213
214 if (anItemPath) {
215 CF_syslog(LOG_WARNING, CFSTR("%@"), anItemPath);
216 }
217 if (anErrorDescription) {
218 CF_syslog(LOG_WARNING, CFSTR(" - %@"), anErrorDescription);
219 } else {
220 CF_syslog(LOG_WARNING, CFSTR(" - %@"), kErrorInternal);
221 }
222 }
223 }
224 if (CFArrayGetCount(aStartupContext->aWaitingList) > 0) {
225 CFIndex anItemCount = CFArrayGetCount(aStartupContext->aWaitingList);
226 CFIndex anItemIndex;
227
228 syslog(LOG_WARNING, "The following StartupItems were not attempted due to failure of a required service:");
229
230 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
231 CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aStartupContext->aWaitingList, anItemIndex);
232 CFStringRef anItemPath = CFDictionaryGetValue(anItem, kBundlePathKey);
233 if (anItemPath) {
234 CF_syslog(LOG_WARNING, CFSTR("%@"), anItemPath);
235 }
236 }
237 }
238}
239
240
241static int
242system_starter(Action anAction, const char *aService_cstr)
243{
244 CFRunLoopSourceRef anIPCSource = NULL;
245 CFStringRef aService = NULL;
246 NSSearchPathDomainMask aMask;
247
248 if (aService_cstr)
249 aService = CFStringCreateWithCString(kCFAllocatorDefault, aService_cstr, kCFStringEncodingUTF8);
250
251 StartupContext aStartupContext = (StartupContext) malloc(sizeof(struct StartupContextStorage));
252 if (!aStartupContext) {
253 syslog(LOG_ERR, "Not enough memory to allocate startup context");
254 return (1);
255 }
256 if (gDebugFlag && gNoRunFlag)
257 sleep(1);
258
259 /**
260 * Create the IPC port
261 **/
262 anIPCSource = CreateIPCRunLoopSource(CFSTR(kSystemStarterMessagePort), aStartupContext);
263 if (anIPCSource) {
264 CFRunLoopAddSource(CFRunLoopGetCurrent(), anIPCSource, kCFRunLoopCommonModes);
265 CFRelease(anIPCSource);
266 } else {
267 syslog(LOG_ERR, "Could not create IPC bootstrap port: %s", kSystemStarterMessagePort);
268 return (1);
269 }
270
271 /**
272 * Get a list of Startup Items which are in /Local and /System.
273 * We can't search /Network yet because the network isn't up.
274 **/
275 aMask = NSSystemDomainMask | NSLocalDomainMask;
276
277 aStartupContext->aWaitingList = StartupItemListCreateWithMask(aMask);
278 aStartupContext->aFailedList = NULL;
279 aStartupContext->aStatusDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
280 &kCFTypeDictionaryValueCallBacks);
281 aStartupContext->aServicesCount = 0;
282 aStartupContext->aRunningCount = 0;
283
284 if (aService) {
285 CFMutableArrayRef aDependentsList = StartupItemListCreateDependentsList(aStartupContext->aWaitingList, aService, anAction);
286
287 if (aDependentsList) {
288 CFRelease(aStartupContext->aWaitingList);
289 aStartupContext->aWaitingList = aDependentsList;
290 } else {
291 CF_syslog(LOG_ERR, CFSTR("Unknown service: %@"), aService);
292 return (1);
293 }
294 }
295 aStartupContext->aServicesCount = StartupItemListCountServices(aStartupContext->aWaitingList);
296
297 /**
298 * Do the run loop
299 **/
300 while (1) {
301 CFMutableDictionaryRef anItem = StartupItemListGetNext(aStartupContext->aWaitingList, aStartupContext->aStatusDict, anAction);
302
303 if (anItem) {
304 int err = StartupItemRun(aStartupContext->aStatusDict, anItem, anAction);
305 if (!err) {
306 ++aStartupContext->aRunningCount;
307 MonitorStartupItem(aStartupContext, anItem);
308 } else {
309 /* add item to failed list */
310 AddItemToFailedList(aStartupContext, anItem);
311
312 /* Remove the item from the waiting list. */
313 RemoveItemFromWaitingList(aStartupContext, anItem);
314 }
315 } else {
316 /*
317 * If no item was selected to run, and if no items
318 * are running, startup is done.
319 */
320 if (aStartupContext->aRunningCount == 0) {
321 syslog(LOG_DEBUG, "none left");
322 break;
323 }
324 /*
325 * Process incoming IPC messages and item
326 * terminations
327 */
328 switch (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 3.0, true)) {
329 case kCFRunLoopRunTimedOut:
330 checkForActivity(aStartupContext);
331 break;
332 case kCFRunLoopRunFinished:
333 break;
334 case kCFRunLoopRunStopped:
335 break;
336 case kCFRunLoopRunHandledSource:
337 break;
338 default:
339 /* unknown return value */
340 break;
341 }
342 }
343 }
344
345 /**
346 * Good-bye.
347 **/
348 displayErrorMessages(aStartupContext);
349
350 /* clean up */
351 if (aStartupContext->aStatusDict)
352 CFRelease(aStartupContext->aStatusDict);
353 if (aStartupContext->aWaitingList)
354 CFRelease(aStartupContext->aWaitingList);
355 if (aStartupContext->aFailedList)
356 CFRelease(aStartupContext->aFailedList);
357
358 free(aStartupContext);
359 return (0);
360}
361
362void
363CF_syslog(int level, CFStringRef message,...)
364{
365 char buf[8192];
366 CFStringRef cooked_msg;
367 va_list ap;
368
369 va_start(ap, message);
370 cooked_msg = CFStringCreateWithFormatAndArguments(NULL, NULL, message, ap);
371 va_end(ap);
372
373 if (CFStringGetCString(cooked_msg, buf, sizeof(buf), kCFStringEncodingUTF8))
374 syslog(level, buf);
375
376 CFRelease(cooked_msg);
377}
378
379static void
380usage(void)
381{
382 fprintf(stderr, "usage: %s [-vdqn?] [ <action> [ <item> ] ]\n"
383 "\t<action>: action to take (start|stop|restart); default is start\n"
384 "\t<item> : name of item to act on; default is all items\n"
385 "options:\n"
386 "\t-v: verbose startup\n"
387 "\t-d: print debugging output\n"
388 "\t-q: be quiet (disable debugging output)\n"
389 "\t-n: don't actually perform action on items (pretend mode)\n"
390 "\t-?: show this help\n",
391 getprogname());
392 exit(EXIT_FAILURE);
393}
394
395static void doCFnote(void)
396{
397 CFNotificationCenterPostNotificationWithOptions(
398 CFNotificationCenterGetDistributedCenter(),
399 CFSTR("com.apple.startupitems.completed"),
400 NULL, NULL,
401 kCFNotificationDeliverImmediately | kCFNotificationPostToAllSessions);
402}