/ src / launchd / SystemStarter / SystemStarter.c
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 <DiskArbitration/DiskArbitration.h>
 37  #include <DiskArbitration/DiskArbitrationPrivate.h>
 38  #include <NSSystemDirectories.h>
 39  #include "IPC.h"
 40  #include "StartupItems.h"
 41  #include "SystemStarter.h"
 42  #include "SystemStarterIPC.h"
 43  
 44  bool gDebugFlag = false;
 45  bool gVerboseFlag = false;
 46  bool gNoRunFlag = false;
 47  
 48  static void     usage(void) __attribute__((noreturn));
 49  static int      system_starter(Action anAction, const char *aService);
 50  static void	displayErrorMessages(StartupContext aStartupContext, Action anAction);
 51  static pid_t	fwexec(const char *cmd, ...) __attribute__((sentinel));
 52  static void	autodiskmount(void);
 53  static void	dummy_sig(int signo __attribute__((unused)))
 54  {
 55  }
 56  
 57  int 
 58  main(int argc, char *argv[])
 59  {
 60  	struct kevent	kev;
 61  	Action          anAction = kActionStart;
 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);
 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  
 97  	if (argc > 2) {
 98  		usage();
 99  	}
100  
101  	openlog(getprogname(), LOG_PID|LOG_CONS|(gDebugFlag ? LOG_PERROR : 0), LOG_DAEMON);
102  	if (gDebugFlag) {
103  		setlogmask(LOG_UPTO(LOG_DEBUG));
104  	} else if (gVerboseFlag) {
105  		setlogmask(LOG_UPTO(LOG_INFO));
106  	} else {
107  		setlogmask(LOG_UPTO(LOG_NOTICE));
108  	}
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  
127  	if (argc == 2) {
128  		exit(system_starter(anAction, argv[1]));
129  	}
130  
131  	unlink(kFixerPath);
132  
133  	mach_timespec_t w = { 600, 0 };
134  	kern_return_t kr;
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);
146  	}
147  
148  	fwexec("/usr/sbin/ipconfig", "waitall", NULL);
149  	autodiskmount(); /* wait for Disk Arbitration to report idle */
150  
151  	system_starter(kActionStart, NULL);
152  
153  	if (StartupItemSecurityCheck("/etc/rc.local")) {
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  
167  	if (StartupItemSecurityCheck("/etc/rc.shutdown.local")) {
168  		fwexec(_PATH_BSHELL, "/etc/rc.shutdown.local", NULL);
169  	}
170  
171  	system_starter(kActionStop, NULL);
172  
173  	exit(EXIT_SUCCESS);
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   **/
181  static void 
182  checkForActivity(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 = StartupItemListCreateFromRunning(aStartupContext->aWaitingList);
195  			if (aRunningList && CFArrayGetCount(aRunningList) > 0) {
196  				CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aRunningList, 0);
197  				CFStringRef     anItemDescription = StartupItemCreateDescription(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   */
218  void 
219  displayErrorMessages(StartupContext aStartupContext, Action anAction)
220  {
221  	if (aStartupContext->aFailedList && CFArrayGetCount(aStartupContext->aFailedList) > 0) {
222  		CFIndex         anItemCount = CFArrayGetCount(aStartupContext->aFailedList);
223  		CFIndex         anItemIndex;
224  
225  
226  		syslog(LOG_WARNING, "The following StartupItems failed to %s properly:", (anAction == kActionStart) ? "start" : "stop");
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  
260  static int 
261  system_starter(Action anAction, const char *aService_cstr)
262  {
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  
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           **/
354  	displayErrorMessages(aStartupContext, anAction);
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  
368  void 
369  CF_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, "%s", buf);
381  
382  	CFRelease(cooked_msg);
383  }
384  
385  static void 
386  usage(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  
401  pid_t
402  fwexec(const char *cmd, ...)
403  {
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;
439  }
440  
441  static void
442  autodiskmount_idle(void* context __attribute__((unused)))
443  {
444  	CFRunLoopStop(CFRunLoopGetCurrent());
445  }
446  
447  static void
448  autodiskmount(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  }