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