/** * System Starter main * Wilfredo Sanchez | wsanchez@opensource.apple.com * $Apple$ ** * Copyright (c) 1999-2002 Apple Computer, Inc. All rights reserved. * * @APPLE_APACHE_LICENSE_HEADER_START@ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @APPLE_APACHE_LICENSE_HEADER_END@ **/ #include #include #include #include #include #include #include #include #include #include #include #include #include "IPC.h" #include "StartupItems.h" #include "SystemStarter.h" #include "SystemStarterIPC.h" bool gDebugFlag = false; bool gVerboseFlag = false; bool gNoRunFlag = false; static void usage(void) __attribute__((noreturn)); static int system_starter(Action anAction, const char *aService); static void displayErrorMessages(StartupContext aStartupContext); static pid_t fwexec(const char *cmd, ...) __attribute__((sentinel)); static void dummy_sig(int signo __attribute__((unused))) { } int main(int argc, char *argv[]) { struct kevent kev; Action anAction = kActionStart; int ch, r, kq = kqueue(); assert(kq != -1); EV_SET(&kev, SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, 0); r = kevent(kq, &kev, 1, NULL, 0, NULL); assert(r != -1); signal(SIGTERM, dummy_sig); while ((ch = getopt(argc, argv, "gvxirdDqn?")) != -1) { switch (ch) { case 'v': gVerboseFlag = true; break; case 'x': case 'g': case 'r': case 'q': break; case 'd': case 'D': gDebugFlag = true; break; case 'n': gNoRunFlag = true; break; case '?': default: usage(); break; } } argc -= optind; argv += optind; if (argc > 2) { usage(); } openlog(getprogname(), LOG_PID|LOG_CONS|(gDebugFlag ? LOG_PERROR : 0), LOG_DAEMON); if (gDebugFlag) { setlogmask(LOG_UPTO(LOG_DEBUG)); } else if (gVerboseFlag) { setlogmask(LOG_UPTO(LOG_INFO)); } else { setlogmask(LOG_UPTO(LOG_NOTICE)); } if (!gNoRunFlag && (getuid() != 0)) { syslog(LOG_ERR, "must be root to run"); exit(EXIT_FAILURE); } if (argc > 0) { if (strcmp(argv[0], "start") == 0) { anAction = kActionStart; } else if (strcmp(argv[0], "stop") == 0) { anAction = kActionStop; } else if (strcmp(argv[0], "restart") == 0) { anAction = kActionRestart; } else { usage(); } } if (argc == 2) { exit(system_starter(anAction, argv[1])); } unlink(kFixerPath); mach_timespec_t w = { 600, 0 }; kern_return_t kr; struct stat sb; /* * Too many old StartupItems had implicit dependancies on "Network" via * other StartupItems that are now no-ops. * * SystemStarter is not on the critical path for boot up, so we'll * stall here to deal with this legacy dependancy problem. */ if ((kr = IOKitWaitQuiet(kIOMasterPortDefault, &w)) != kIOReturnSuccess) { syslog(LOG_NOTICE, "IOKitWaitQuiet: %d\n", kr); } fwexec("/usr/sbin/ipconfig", "waitall", NULL); fwexec("/sbin/autodiskmount", "-va", NULL); system_starter(kActionStart, NULL); if (stat("/etc/rc.local", &sb) != -1) { fwexec(_PATH_BSHELL, "/etc/rc.local", NULL); } CFNotificationCenterPostNotificationWithOptions( CFNotificationCenterGetDistributedCenter(), CFSTR("com.apple.startupitems.completed"), NULL, NULL, kCFNotificationDeliverImmediately | kCFNotificationPostToAllSessions); r = kevent(kq, NULL, 0, &kev, 1, NULL); assert(r != -1); assert(kev.filter == EVFILT_SIGNAL && kev.ident == SIGTERM); if (stat("/etc/rc.shutdown.local", &sb) != -1) { fwexec(_PATH_BSHELL, "/etc/rc.shutdown.local", NULL); } system_starter(kActionStop, NULL); exit(EXIT_SUCCESS); } /** * checkForActivity checks to see if any items have completed since the last invokation. * If not, a message is displayed showing what item(s) are being waited on. **/ static void checkForActivity(StartupContext aStartupContext) { static CFIndex aLastStatusDictionaryCount = -1; static CFStringRef aWaitingForString = NULL; if (aStartupContext && aStartupContext->aStatusDict) { CFIndex aCount = CFDictionaryGetCount(aStartupContext->aStatusDict); if (!aWaitingForString) { aWaitingForString = CFSTR("Waiting for %@"); } if (aLastStatusDictionaryCount == aCount) { CFArrayRef aRunningList = StartupItemListGetRunning(aStartupContext->aWaitingList); if (aRunningList && CFArrayGetCount(aRunningList) > 0) { CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aRunningList, 0); CFStringRef anItemDescription = StartupItemGetDescription(anItem); CFStringRef aString = aWaitingForString && anItemDescription ? CFStringCreateWithFormat(NULL, NULL, aWaitingForString, anItemDescription) : NULL; if (aString) { CF_syslog(LOG_INFO, CFSTR("%@"), aString); CFRelease(aString); } if (anItemDescription) CFRelease(anItemDescription); } if (aRunningList) CFRelease(aRunningList); } aLastStatusDictionaryCount = aCount; } } /* * print out any error messages to the log regarding non starting StartupItems */ void displayErrorMessages(StartupContext aStartupContext) { if (aStartupContext->aFailedList && CFArrayGetCount(aStartupContext->aFailedList) > 0) { CFIndex anItemCount = CFArrayGetCount(aStartupContext->aFailedList); CFIndex anItemIndex; syslog(LOG_WARNING, "The following StartupItems failed to properly start:"); for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) { CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aStartupContext->aFailedList, anItemIndex); CFStringRef anErrorDescription = CFDictionaryGetValue(anItem, kErrorKey); CFStringRef anItemPath = CFDictionaryGetValue(anItem, kBundlePathKey); if (anItemPath) { CF_syslog(LOG_WARNING, CFSTR("%@"), anItemPath); } if (anErrorDescription) { CF_syslog(LOG_WARNING, CFSTR(" - %@"), anErrorDescription); } else { CF_syslog(LOG_WARNING, CFSTR(" - %@"), kErrorInternal); } } } if (CFArrayGetCount(aStartupContext->aWaitingList) > 0) { CFIndex anItemCount = CFArrayGetCount(aStartupContext->aWaitingList); CFIndex anItemIndex; syslog(LOG_WARNING, "The following StartupItems were not attempted due to failure of a required service:"); for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) { CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aStartupContext->aWaitingList, anItemIndex); CFStringRef anItemPath = CFDictionaryGetValue(anItem, kBundlePathKey); if (anItemPath) { CF_syslog(LOG_WARNING, CFSTR("%@"), anItemPath); } } } } static int system_starter(Action anAction, const char *aService_cstr) { CFStringRef aService = NULL; NSSearchPathDomainMask aMask; if (aService_cstr) aService = CFStringCreateWithCString(kCFAllocatorDefault, aService_cstr, kCFStringEncodingUTF8); StartupContext aStartupContext = (StartupContext) malloc(sizeof(struct StartupContextStorage)); if (!aStartupContext) { syslog(LOG_ERR, "Not enough memory to allocate startup context"); return (1); } if (gDebugFlag && gNoRunFlag) sleep(1); /** * Get a list of Startup Items which are in /Local and /System. * We can't search /Network yet because the network isn't up. **/ aMask = NSSystemDomainMask | NSLocalDomainMask; aStartupContext->aWaitingList = StartupItemListCreateWithMask(aMask); aStartupContext->aFailedList = NULL; aStartupContext->aStatusDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); aStartupContext->aServicesCount = 0; aStartupContext->aRunningCount = 0; if (aService) { CFMutableArrayRef aDependentsList = StartupItemListCreateDependentsList(aStartupContext->aWaitingList, aService, anAction); if (aDependentsList) { CFRelease(aStartupContext->aWaitingList); aStartupContext->aWaitingList = aDependentsList; } else { CF_syslog(LOG_ERR, CFSTR("Unknown service: %@"), aService); return (1); } } aStartupContext->aServicesCount = StartupItemListCountServices(aStartupContext->aWaitingList); /** * Do the run loop **/ while (1) { CFMutableDictionaryRef anItem = StartupItemListGetNext(aStartupContext->aWaitingList, aStartupContext->aStatusDict, anAction); if (anItem) { int err = StartupItemRun(aStartupContext->aStatusDict, anItem, anAction); if (!err) { ++aStartupContext->aRunningCount; MonitorStartupItem(aStartupContext, anItem); } else { /* add item to failed list */ AddItemToFailedList(aStartupContext, anItem); /* Remove the item from the waiting list. */ RemoveItemFromWaitingList(aStartupContext, anItem); } } else { /* * If no item was selected to run, and if no items * are running, startup is done. */ if (aStartupContext->aRunningCount == 0) { syslog(LOG_DEBUG, "none left"); break; } /* * Process incoming IPC messages and item * terminations */ switch (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 3.0, true)) { case kCFRunLoopRunTimedOut: checkForActivity(aStartupContext); break; case kCFRunLoopRunFinished: break; case kCFRunLoopRunStopped: break; case kCFRunLoopRunHandledSource: break; default: /* unknown return value */ break; } } } /** * Good-bye. **/ displayErrorMessages(aStartupContext); /* clean up */ if (aStartupContext->aStatusDict) CFRelease(aStartupContext->aStatusDict); if (aStartupContext->aWaitingList) CFRelease(aStartupContext->aWaitingList); if (aStartupContext->aFailedList) CFRelease(aStartupContext->aFailedList); free(aStartupContext); return (0); } void CF_syslog(int level, CFStringRef message,...) { char buf[8192]; CFStringRef cooked_msg; va_list ap; va_start(ap, message); cooked_msg = CFStringCreateWithFormatAndArguments(NULL, NULL, message, ap); va_end(ap); if (CFStringGetCString(cooked_msg, buf, sizeof(buf), kCFStringEncodingUTF8)) syslog(level, buf); CFRelease(cooked_msg); } static void usage(void) { fprintf(stderr, "usage: %s [-vdqn?] [ [ ] ]\n" "\t: action to take (start|stop|restart); default is start\n" "\t : name of item to act on; default is all items\n" "options:\n" "\t-v: verbose startup\n" "\t-d: print debugging output\n" "\t-q: be quiet (disable debugging output)\n" "\t-n: don't actually perform action on items (pretend mode)\n" "\t-?: show this help\n", getprogname()); exit(EXIT_FAILURE); } pid_t fwexec(const char *cmd, ...) { const char *argv[100] = { cmd }; va_list ap; int wstatus, i = 1; pid_t p; va_start(ap, cmd); do { argv[i] = va_arg(ap, char *); } while (argv[i++]); va_end(ap); switch ((p = fork())) { case -1: return -1; case 0: execvp(argv[0], (char *const *)argv); _exit(EXIT_FAILURE); break; default: if (waitpid(p, &wstatus, 0) == -1) { return -1; } else if (WIFEXITED(wstatus)) { if (WEXITSTATUS(wstatus) == 0) { return 0; } else { syslog(LOG_WARNING, "%s exit status: %d", argv[0], WEXITSTATUS(wstatus)); } } else { /* must have died due to signal */ syslog(LOG_WARNING, "%s died: %s", argv[0], strsignal(WTERMSIG(wstatus))); } break; } return -1; }