#include <IOKit/IOKitLib.h>
#include <sys/types.h>
#include <sys/event.h>
#include <sys/stat.h>
#include <paths.h>
#include <unistd.h>
#include <crt_externs.h>
#include <fcntl.h>
#include <syslog.h>
#include <assert.h>
#include <CoreFoundation/CoreFoundation.h>
#include <DiskArbitration/DiskArbitration.h>
#include <DiskArbitration/DiskArbitrationPrivate.h>
#include <NSSystemDirectories.h>
#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, Action anAction);
static pid_t fwexec(const char *cmd, ...) __attribute__((sentinel));
static void autodiskmount(void);
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;
if ((kr = IOKitWaitQuiet(kIOMasterPortDefault, &w)) != kIOReturnSuccess) {
syslog(LOG_NOTICE, "IOKitWaitQuiet: %d\n", kr);
}
fwexec("/usr/sbin/ipconfig", "waitall", NULL);
autodiskmount();
system_starter(kActionStart, NULL);
if (StartupItemSecurityCheck("/etc/rc.local")) {
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 (StartupItemSecurityCheck("/etc/rc.shutdown.local")) {
fwexec(_PATH_BSHELL, "/etc/rc.shutdown.local", NULL);
}
system_starter(kActionStop, NULL);
exit(EXIT_SUCCESS);
}
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 = StartupItemListCreateFromRunning(aStartupContext->aWaitingList);
if (aRunningList && CFArrayGetCount(aRunningList) > 0) {
CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aRunningList, 0);
CFStringRef anItemDescription = StartupItemCreateDescription(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;
}
}
void
displayErrorMessages(StartupContext aStartupContext, Action anAction)
{
if (aStartupContext->aFailedList && CFArrayGetCount(aStartupContext->aFailedList) > 0) {
CFIndex anItemCount = CFArrayGetCount(aStartupContext->aFailedList);
CFIndex anItemIndex;
syslog(LOG_WARNING, "The following StartupItems failed to %s properly:", (anAction == kActionStart) ? "start" : "stop");
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);
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);
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 {
AddItemToFailedList(aStartupContext, anItem);
RemoveItemFromWaitingList(aStartupContext, anItem);
}
} else {
if (aStartupContext->aRunningCount == 0) {
syslog(LOG_DEBUG, "none left");
break;
}
switch (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 3.0, true)) {
case kCFRunLoopRunTimedOut:
checkForActivity(aStartupContext);
break;
case kCFRunLoopRunFinished:
break;
case kCFRunLoopRunStopped:
break;
case kCFRunLoopRunHandledSource:
break;
default:
break;
}
}
}
displayErrorMessages(aStartupContext, anAction);
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, "%s", buf);
CFRelease(cooked_msg);
}
static void
usage(void)
{
fprintf(stderr, "usage: %s [-vdqn?] [ <action> [ <item> ] ]\n"
"\t<action>: action to take (start|stop|restart); default is start\n"
"\t<item> : 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 {
syslog(LOG_WARNING, "%s died: %s", argv[0], strsignal(WTERMSIG(wstatus)));
}
break;
}
return -1;
}
static void
autodiskmount_idle(void* context __attribute__((unused)))
{
CFRunLoopStop(CFRunLoopGetCurrent());
}
static void
autodiskmount(void)
{
DASessionRef session = DASessionCreate(NULL);
if (session) {
DASessionScheduleWithRunLoop(session, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
DARegisterIdleCallback(session, autodiskmount_idle, NULL);
CFRunLoopRun();
CFRelease(session);
}
}