--- /dev/null
+/**
+ * IPC.c - System Starter IPC routines
+ * Wilfredo Sanchez | wsanchez@opensource.apple.com
+ * Kevin Van Vechten | kevinvv@uclink4.berkeley.edu
+ * $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 <sys/wait.h>
+#include <mach/mach.h>
+#include <mach/message.h>
+#include <mach/mach_error.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <syslog.h>
+
+#include "bootstrap.h"
+
+#include "IPC.h"
+#include "StartupItems.h"
+#include "SystemStarter.h"
+#include "SystemStarterIPC.h"
+
+/* Structure to pass StartupContext and anItem to the termination handler. */
+typedef struct TerminationContextStorage {
+ StartupContext aStartupContext;
+ CFMutableDictionaryRef anItem;
+} *TerminationContext;
+
+/**
+ * A CFMachPort invalidation callback that records the termination of
+ * a startup item task. Stops the current run loop to give system_starter
+ * another go at running items.
+ **/
+static void
+startupItemTerminated(CFMachPortRef aMachPort, void *anInfo)
+{
+ TerminationContext aTerminationContext = (TerminationContext) anInfo;
+
+ if (aMachPort) {
+ mach_port_deallocate(mach_task_self(), CFMachPortGetPort(aMachPort));
+ }
+ if (aTerminationContext && aTerminationContext->anItem) {
+ pid_t aPID = 0;
+ pid_t rPID = 0;
+ int aStatus = 0;
+ CFMutableDictionaryRef anItem = aTerminationContext->anItem;
+ StartupContext aStartupContext = aTerminationContext->aStartupContext;
+
+ /* Get the exit status */
+ if (anItem) {
+ aPID = StartupItemGetPID(anItem);
+ if (aPID > 0)
+ rPID = waitpid(aPID, &aStatus, 0);
+ }
+ if (aStartupContext) {
+ --aStartupContext->aRunningCount;
+
+ /* Record the item's status */
+ if (aStartupContext->aStatusDict) {
+ StartupItemExit(aStartupContext->aStatusDict, anItem, (WIFEXITED(aStatus) && WEXITSTATUS(aStatus) == 0));
+ if (aStatus) {
+ CF_syslog(LOG_WARNING, CFSTR("%@ (%d) did not complete successfully"), CFDictionaryGetValue(anItem, CFSTR("Description")), aPID);
+ } else {
+ CF_syslog(LOG_DEBUG, CFSTR("Finished %@ (%d)"), CFDictionaryGetValue(anItem, CFSTR("Description")), aPID);
+ }
+ }
+ /*
+ * If the item failed to start, then add it to the
+ * failed list
+ */
+ if (WEXITSTATUS(aStatus) || WTERMSIG(aStatus) || WCOREDUMP(aStatus)) {
+ CFDictionarySetValue(anItem, kErrorKey, kErrorReturnNonZero);
+ AddItemToFailedList(aStartupContext, anItem);
+ }
+ /*
+ * Remove the item from the waiting list regardless
+ * if it was successful or it failed.
+ */
+ RemoveItemFromWaitingList(aStartupContext, anItem);
+ }
+ }
+ if (aTerminationContext)
+ free(aTerminationContext);
+}
+
+void
+MonitorStartupItem(StartupContext aStartupContext, CFMutableDictionaryRef anItem)
+{
+ pid_t aPID = StartupItemGetPID(anItem);
+ if (anItem && aPID > 0) {
+ mach_port_t aPort;
+ kern_return_t aResult;
+ CFMachPortContext aContext;
+ CFMachPortRef aMachPort;
+ CFRunLoopSourceRef aSource;
+ TerminationContext aTerminationContext = (TerminationContext) malloc(sizeof(struct TerminationContextStorage));
+
+ aTerminationContext->aStartupContext = aStartupContext;
+ aTerminationContext->anItem = anItem;
+
+ aContext.version = 0;
+ aContext.info = aTerminationContext;
+ aContext.retain = 0;
+ aContext.release = 0;
+
+ if ((aResult = task_name_for_pid(mach_task_self(), aPID, &aPort)) != KERN_SUCCESS)
+ goto out_bad;
+
+ if (!(aMachPort = CFMachPortCreateWithPort(NULL, aPort, NULL, &aContext, NULL)))
+ goto out_bad;
+
+ if (!(aSource = CFMachPortCreateRunLoopSource(NULL, aMachPort, 0))) {
+ CFRelease(aMachPort);
+ goto out_bad;
+ }
+ CFMachPortSetInvalidationCallBack(aMachPort, startupItemTerminated);
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), aSource, kCFRunLoopCommonModes);
+ CFRelease(aSource);
+ CFRelease(aMachPort);
+ return;
+out_bad:
+ /*
+ * The assumption is something failed, the task already
+ * terminated.
+ */
+ startupItemTerminated(NULL, aTerminationContext);
+ }
+}
--- /dev/null
+/**
+ * IPC.h - System Starter IPC routines
+ * Wilfredo Sanchez | wsanchez@opensource.apple.com
+ * Kevin Van Vechten | kevinvv@uclink4.berkeley.edu
+ * $Apple$
+ **
+ * Copyright (c) 1999-2001 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@
+ **/
+
+#ifndef _IPC_H_
+#define _IPC_H_
+
+#include "SystemStarter.h"
+
+/**
+ * Monitor a startup item task. Creates a mach port and uses the
+ * invalidation callback to notify system starter when the process
+ * terminates.
+ **/
+void MonitorStartupItem (StartupContext aStartupContext, CFMutableDictionaryRef anItem);
+
+#endif /* _IPC_H_ */
--- /dev/null
+#!/bin/sh
+
+unset LAUNCHD_SOCKET
+
+exec launchctl bsexec / "$@"
--- /dev/null
+.Dd July 7, 2002
+.Dt StartupItemContext 8
+.Os Darwin
+.Sh NAME
+.Nm StartupItemContext
+.\" The following lines are read in generating the apropos(man -k) database. Use only key
+.\" words here as the database is built based on the words here and in the .ND line.
+.\" Use .Nm macro to designate other names for the documented program.
+.Nd Execute a program in StartupItem context
+.Sh SYNOPSIS
+.Nm
+.Op Ar program Op Ar arguments
+.Sh DESCRIPTION
+The
+.Nm
+utility launches the specified program in StartupItem bootstrap context. Each Darwin
+and Mac OS X login creates a unique bootstrap subset context to contain login specific
+Mach port registrations with the bootstrap server. All such registrations performed
+within the context of that subset are only visible to other processes within that
+context or subsequent subsets of it. Therefore, a Mach port based service/daemon
+launched within a login context will not be visible to other such contexts.
+.Pp
+To override this, a root user can use the
+.Nm
+utility to launch the program within the same bootstrap context as all other
+StartupItems. All subsequent Mach port bootstrap registrations perfomed by the program
+will be visible system-wide.
+.Sh NOTES
+All bootstrap port lookups will also be resticted
+to the StartupItem context. The services provided on a per-login basis (clipboard,
+etc...) will not be available to the program.
+.Sh SEE ALSO
+.\" List links in ascending order by section, alphabetically within a section.
+.\" Please do not reference files that do not exist without filing a bug report
+.Xr SystemStarter 8
--- /dev/null
+/**
+ * StartupItems.c - Startup Item management routines
+ * Wilfredo Sanchez | wsanchez@opensource.apple.com
+ * Kevin Van Vechten | kevinvv@uclink4.berkeley.edu
+ * $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 <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/mman.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <limits.h>
+#include <errno.h>
+#include <string.h>
+#include <sysexits.h>
+#include <syslog.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "StartupItems.h"
+
+#define kStartupItemsPath "/StartupItems"
+#define kParametersFile "StartupParameters.plist"
+#define kDisabledFile ".disabled"
+
+#define kRunSuccess CFSTR("success")
+#define kRunFailure CFSTR("failure")
+
+static const char *argumentForAction(Action anAction)
+{
+ switch (anAction) {
+ case kActionStart:
+ return "start";
+ case kActionStop:
+ return "stop";
+ case kActionRestart:
+ return "restart";
+ default:
+ return NULL;
+ }
+}
+
+#define checkTypeOfValue(aKey,aTypeID) \
+ { \
+ CFStringRef aProperty = CFDictionaryGetValue(aConfig, aKey); \
+ if (aProperty && CFGetTypeID(aProperty) != aTypeID) \
+ return FALSE; \
+ }
+
+static int StartupItemValidate(CFDictionaryRef aConfig)
+{
+ if (aConfig && CFGetTypeID(aConfig) == CFDictionaryGetTypeID()) {
+ checkTypeOfValue(kProvidesKey, CFArrayGetTypeID());
+ checkTypeOfValue(kRequiresKey, CFArrayGetTypeID());
+
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/*
+ * remove item from waiting list
+ */
+void RemoveItemFromWaitingList(StartupContext aStartupContext, CFMutableDictionaryRef anItem)
+{
+ /* Remove the item from the waiting list. */
+ if (aStartupContext && anItem && aStartupContext->aWaitingList) {
+ CFRange aRange = { 0, CFArrayGetCount(aStartupContext->aWaitingList) };
+ CFIndex anIndex = CFArrayGetFirstIndexOfValue(aStartupContext->aWaitingList, aRange, anItem);
+
+ if (anIndex >= 0) {
+ CFArrayRemoveValueAtIndex(aStartupContext->aWaitingList, anIndex);
+ }
+ }
+}
+
+/*
+ * add item to failed list, create list if it doesn't exist
+ * return and fail quietly if it can't create list
+ */
+void AddItemToFailedList(StartupContext aStartupContext, CFMutableDictionaryRef anItem)
+{
+ if (aStartupContext && anItem) {
+ /* create the failed list if it doesn't exist */
+ if (!aStartupContext->aFailedList) {
+ aStartupContext->aFailedList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ }
+ if (aStartupContext->aFailedList) {
+ CFArrayAppendValue(aStartupContext->aFailedList, anItem);
+ }
+ }
+}
+
+/**
+ * startupItemListCopyMatches returns an array of items which contain the string aService in the key aKey
+ **/
+static CFMutableArrayRef startupItemListCopyMatches(CFArrayRef anItemList, CFStringRef aKey, CFStringRef aService)
+{
+ CFMutableArrayRef aResult = NULL;
+
+ if (anItemList && aKey && aService) {
+ CFIndex anItemCount = CFArrayGetCount(anItemList);
+ CFIndex anItemIndex = 0;
+
+ aResult = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+
+ for (anItemIndex = 0; anItemIndex < anItemCount; ++anItemIndex) {
+ CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(anItemList, anItemIndex);
+ CFArrayRef aList = CFDictionaryGetValue(anItem, aKey);
+
+ if (aList) {
+ if (CFArrayContainsValue(aList, CFRangeMake(0, CFArrayGetCount(aList)), aService) &&
+ !CFArrayContainsValue(aResult, CFRangeMake(0, CFArrayGetCount(aResult)), anItem)) {
+ CFArrayAppendValue(aResult, anItem);
+ }
+ }
+ }
+ }
+ return aResult;
+}
+
+static void SpecialCasesStartupItemHandler(CFMutableDictionaryRef aConfig)
+{
+ static const CFStringRef stubitems[] = {
+ CFSTR("Accounting"),
+ CFSTR("System Tuning"),
+ CFSTR("SecurityServer"),
+ CFSTR("Portmap"),
+ CFSTR("System Log"),
+ CFSTR("Resolver"),
+ CFSTR("LDAP"),
+ CFSTR("NetInfo"),
+ CFSTR("NetworkExtensions"),
+ CFSTR("DirectoryServices"),
+ CFSTR("Network Configuration"),
+ CFSTR("mDNSResponder"),
+ CFSTR("Cron"),
+ CFSTR("Core Graphics"),
+ CFSTR("Core Services"),
+ CFSTR("Network"),
+ CFSTR("TIM"),
+ CFSTR("Disks"),
+ CFSTR("NIS"),
+ NULL
+ };
+ CFMutableArrayRef aList, aNewList;
+ CFIndex i, aCount;
+ CFStringRef ci, type = kRequiresKey;
+ const CFStringRef *c;
+
+ again:
+ aList = (CFMutableArrayRef) CFDictionaryGetValue(aConfig, type);
+ if (aList) {
+ aCount = CFArrayGetCount(aList);
+
+ aNewList = CFArrayCreateMutable(kCFAllocatorDefault, aCount, &kCFTypeArrayCallBacks);
+
+ for (i = 0; i < aCount; i++) {
+ ci = CFArrayGetValueAtIndex(aList, i);
+ CF_syslog(LOG_DEBUG, CFSTR("%@: Evaluating %@"), type, ci);
+ for (c = stubitems; *c; c++) {
+ if (CFEqual(*c, ci))
+ break;
+ }
+ if (*c == NULL) {
+ CFArrayAppendValue(aNewList, ci);
+ CF_syslog(LOG_DEBUG, CFSTR("%@: Keeping %@"), type, ci);
+ }
+ }
+
+ CFDictionaryReplaceValue(aConfig, type, aNewList);
+ CFRelease(aNewList);
+ }
+ if (type == kUsesKey)
+ return;
+ type = kUsesKey;
+ goto again;
+}
+
+CFIndex StartupItemListCountServices(CFArrayRef anItemList)
+{
+ CFIndex aResult = 0;
+
+ if (anItemList) {
+ CFIndex anItemCount = CFArrayGetCount(anItemList);
+ CFIndex anItemIndex = 0;
+
+ for (anItemIndex = 0; anItemIndex < anItemCount; ++anItemIndex) {
+ CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
+ CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
+
+ if (aProvidesList)
+ aResult += CFArrayGetCount(aProvidesList);
+ }
+ }
+ return aResult;
+}
+
+bool StartupItemSecurityCheck(const char *aPath)
+{
+ static struct timeval boot_time;
+ struct stat aStatBuf;
+ bool r = true;
+
+ if (boot_time.tv_sec == 0) {
+ int mib[] = { CTL_KERN, KERN_BOOTTIME };
+ size_t boot_time_sz = sizeof(boot_time);
+ int rv;
+
+ rv = sysctl(mib, sizeof(mib) / sizeof(mib[0]), &boot_time, &boot_time_sz, NULL, 0);
+
+ assert(rv != -1);
+ assert(boot_time_sz == sizeof(boot_time));
+ }
+
+ /* should use lstatx_np() on Tiger? */
+ if (lstat(aPath, &aStatBuf) == -1) {
+ if (errno != ENOENT)
+ syslog(LOG_ERR, "lstat(\"%s\"): %m", aPath);
+ return false;
+ }
+ /*
+ * We check the boot time because of 5409386.
+ * We ignore the boot time if PPID != 1 because of 5503536.
+ */
+ if ((aStatBuf.st_ctimespec.tv_sec > boot_time.tv_sec) && (getppid() == 1)) {
+ syslog(LOG_WARNING, "\"%s\" failed sanity check: path was created after boot up", aPath);
+ return false;
+ }
+ if (!(S_ISREG(aStatBuf.st_mode) || S_ISDIR(aStatBuf.st_mode))) {
+ syslog(LOG_WARNING, "\"%s\" failed security check: not a directory or regular file", aPath);
+ r = false;
+ }
+ if (aStatBuf.st_mode & S_IWOTH) {
+ syslog(LOG_WARNING, "\"%s\" failed security check: world writable", aPath);
+ r = false;
+ }
+ if (aStatBuf.st_mode & S_IWGRP) {
+ syslog(LOG_WARNING, "\"%s\" failed security check: group writable", aPath);
+ r = false;
+ }
+ if (aStatBuf.st_uid != 0) {
+ syslog(LOG_WARNING, "\"%s\" failed security check: not owned by UID 0", aPath);
+ r = false;
+ }
+ if (aStatBuf.st_gid != 0) {
+ syslog(LOG_WARNING, "\"%s\" failed security check: not owned by GID 0", aPath);
+ r = false;
+ }
+ if (r == false) {
+ mkdir(kFixerDir, ACCESSPERMS);
+ close(open(kFixerPath, O_RDWR|O_CREAT|O_NOCTTY, DEFFILEMODE));
+ }
+ return r;
+}
+
+CFMutableArrayRef StartupItemListCreateWithMask(NSSearchPathDomainMask aMask)
+{
+ CFMutableArrayRef anItemList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+
+ char aPath[PATH_MAX];
+ CFIndex aDomainIndex = 0;
+
+ NSSearchPathEnumerationState aState = NSStartSearchPathEnumeration(NSLibraryDirectory, aMask);
+
+ while ((aState = NSGetNextSearchPathEnumeration(aState, aPath))) {
+ DIR *aDirectory;
+
+ strlcat(aPath, kStartupItemsPath, sizeof(aPath));
+ ++aDomainIndex;
+
+ /* 5485016
+ *
+ * Just in case...
+ */
+ mkdir(aPath, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
+
+ if (!StartupItemSecurityCheck(aPath))
+ continue;
+
+ if ((aDirectory = opendir(aPath))) {
+ struct dirent *aBundle;
+
+ while ((aBundle = readdir(aDirectory))) {
+ struct stat aStatBuf;
+ char *aBundleName = aBundle->d_name;
+ char aBundlePath[PATH_MAX];
+ char aBundleExecutablePath[PATH_MAX];
+ char aConfigFile[PATH_MAX];
+ char aDisabledFile[PATH_MAX];
+
+ if (aBundleName[0] == '.')
+ continue;
+
+ syslog(LOG_DEBUG, "Found item: %s", aBundleName);
+
+ sprintf(aBundlePath, "%s/%s", aPath, aBundleName);
+ sprintf(aBundleExecutablePath, "%s/%s", aBundlePath, aBundleName);
+ sprintf(aConfigFile, "%s/%s", aBundlePath, kParametersFile);
+ sprintf(aDisabledFile, "%s/%s", aBundlePath, kDisabledFile);
+
+ if (lstat(aDisabledFile, &aStatBuf) == 0) {
+ syslog(LOG_NOTICE, "Skipping disabled StartupItem: %s", aBundlePath);
+ continue;
+ }
+ if (!StartupItemSecurityCheck(aBundlePath))
+ continue;
+ if (!StartupItemSecurityCheck(aBundleExecutablePath))
+ continue;
+ if (!StartupItemSecurityCheck(aConfigFile))
+ continue;
+
+ /* Stow away the plist data for each bundle */
+ {
+ int aConfigFileDescriptor;
+
+ if ((aConfigFileDescriptor = open(aConfigFile, O_RDONLY|O_NOCTTY, (mode_t) 0)) != -1) {
+ struct stat aConfigFileStatBuffer;
+
+ if (stat(aConfigFile, &aConfigFileStatBuffer) != -1) {
+ off_t aConfigFileContentsSize = aConfigFileStatBuffer.st_size;
+ char *aConfigFileContentsBuffer;
+
+ if ((aConfigFileContentsBuffer =
+ mmap((caddr_t) 0, aConfigFileContentsSize,
+ PROT_READ, MAP_FILE | MAP_PRIVATE,
+ aConfigFileDescriptor, (off_t) 0)) != (caddr_t) - 1) {
+ CFDataRef aConfigData = NULL;
+ CFMutableDictionaryRef aConfig = NULL;
+
+ aConfigData =
+ CFDataCreateWithBytesNoCopy(NULL,
+ (const UInt8 *)aConfigFileContentsBuffer,
+ aConfigFileContentsSize,
+ kCFAllocatorNull);
+
+ if (aConfigData) {
+ aConfig = (CFMutableDictionaryRef)
+ CFPropertyListCreateFromXMLData(NULL, aConfigData,
+ kCFPropertyListMutableContainers,
+ NULL);
+ }
+ if (StartupItemValidate(aConfig)) {
+ CFStringRef aBundlePathString =
+ CFStringCreateWithCString(NULL, aBundlePath,
+ kCFStringEncodingUTF8);
+
+ CFNumberRef aDomainNumber =
+ CFNumberCreate(NULL, kCFNumberCFIndexType,
+ &aDomainIndex);
+
+ CFDictionarySetValue(aConfig, kBundlePathKey,
+ aBundlePathString);
+ CFDictionarySetValue(aConfig, kDomainKey, aDomainNumber);
+ CFRelease(aDomainNumber);
+ SpecialCasesStartupItemHandler(aConfig);
+ CFArrayAppendValue(anItemList, aConfig);
+
+ CFRelease(aBundlePathString);
+ } else {
+ syslog(LOG_ERR, "Malformatted parameters file: %s",
+ aConfigFile);
+ }
+
+ if (aConfig)
+ CFRelease(aConfig);
+ if (aConfigData)
+ CFRelease(aConfigData);
+
+ if (munmap(aConfigFileContentsBuffer, aConfigFileContentsSize) ==
+ -1) {
+ syslog(LOG_WARNING,
+ "Unable to unmap parameters file %s for item %s: %m",
+ aConfigFile, aBundleName);
+ }
+ } else {
+ syslog(LOG_ERR,
+ "Unable to map parameters file %s for item %s: %m",
+ aConfigFile, aBundleName);
+ }
+ } else {
+ syslog(LOG_ERR, "Unable to stat parameters file %s for item %s: %m",
+ aConfigFile, aBundleName);
+ }
+
+ if (close(aConfigFileDescriptor) == -1) {
+ syslog(LOG_ERR, "Unable to close parameters file %s for item %s: %m",
+ aConfigFile, aBundleName);
+ }
+ } else {
+ syslog(LOG_ERR, "Unable to open parameters file %s for item %s: %m", aConfigFile,
+ aBundleName);
+ }
+ }
+ }
+ if (closedir(aDirectory) == -1) {
+ syslog(LOG_WARNING, "Unable to directory bundle %s: %m", aPath);
+ }
+ } else {
+ if (errno != ENOENT) {
+ syslog(LOG_WARNING, "Open on directory %s failed: %m", aPath);
+ return (NULL);
+ }
+ }
+ }
+
+ return anItemList;
+}
+
+CFMutableDictionaryRef StartupItemListGetProvider(CFArrayRef anItemList, CFStringRef aService)
+{
+ CFMutableDictionaryRef aResult = NULL;
+ CFMutableArrayRef aList = startupItemListCopyMatches(anItemList, kProvidesKey, aService);
+
+ if (aList && CFArrayGetCount(aList) > 0)
+ aResult = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aList, 0);
+
+ if (aList) CFRelease(aList);
+
+ return aResult;
+}
+
+CFArrayRef StartupItemListCreateFromRunning(CFArrayRef anItemList)
+{
+ CFMutableArrayRef aResult = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ if (aResult) {
+ CFIndex anIndex, aCount = CFArrayGetCount(anItemList);
+ for (anIndex = 0; anIndex < aCount; ++anIndex) {
+ CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anIndex);
+ if (anItem) {
+ CFNumberRef aPID = CFDictionaryGetValue(anItem, kPIDKey);
+ if (aPID)
+ CFArrayAppendValue(aResult, anItem);
+ }
+ }
+ }
+ return aResult;
+}
+
+/*
+ * Append items in anItemList to aDependents which depend on
+ * aParentItem.
+ * If anAction is kActionStart, dependent items are those which
+ * require any service provided by aParentItem.
+ * If anAction is kActionStop, dependent items are those which provide
+ * any service required by aParentItem.
+ */
+static void appendDependents(CFMutableArrayRef aDependents, CFArrayRef anItemList, CFDictionaryRef aParentItem, Action anAction)
+{
+ CFStringRef anInnerKey, anOuterKey;
+ CFArrayRef anOuterList;
+
+ /* Append the parent item to the list (avoiding duplicates) */
+ if (!CFArrayContainsValue(aDependents, CFRangeMake(0, CFArrayGetCount(aDependents)), aParentItem))
+ CFArrayAppendValue(aDependents, aParentItem);
+
+ /**
+ * Recursively append any children of the parent item for kStartAction and kStopAction.
+ * Do nothing for other actions.
+ **/
+ switch (anAction) {
+ case kActionStart:
+ anInnerKey = kProvidesKey;
+ anOuterKey = kRequiresKey;
+ break;
+ case kActionStop:
+ anInnerKey = kRequiresKey;
+ anOuterKey = kProvidesKey;
+ break;
+ default:
+ return;
+ }
+
+ anOuterList = CFDictionaryGetValue(aParentItem, anOuterKey);
+
+ if (anOuterList) {
+ CFIndex anOuterCount = CFArrayGetCount(anOuterList);
+ CFIndex anOuterIndex;
+
+ for (anOuterIndex = 0; anOuterIndex < anOuterCount; anOuterIndex++) {
+ CFStringRef anOuterElement = CFArrayGetValueAtIndex(anOuterList, anOuterIndex);
+ CFIndex anItemCount = CFArrayGetCount(anItemList);
+ CFIndex anItemIndex;
+
+ for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
+ CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
+ CFArrayRef anInnerList = CFDictionaryGetValue(anItem, anInnerKey);
+
+ if (anInnerList &&
+ CFArrayContainsValue(anInnerList, CFRangeMake(0, CFArrayGetCount(anInnerList)),
+ anOuterElement)
+ && !CFArrayContainsValue(aDependents, CFRangeMake(0, CFArrayGetCount(aDependents)), anItem))
+ appendDependents(aDependents, anItemList, anItem, anAction);
+ }
+ }
+ }
+}
+
+CFMutableArrayRef StartupItemListCreateDependentsList(CFMutableArrayRef anItemList, CFStringRef aService, Action anAction)
+{
+ CFMutableArrayRef aDependents = NULL;
+ CFMutableDictionaryRef anItem = NULL;
+
+ if (anItemList && aService)
+ anItem = StartupItemListGetProvider(anItemList, aService);
+
+ if (anItem) {
+ switch (anAction) {
+ case kActionRestart:
+ case kActionStart:
+ case kActionStop:
+ aDependents = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+
+ if (!aDependents) {
+ CF_syslog(LOG_EMERG, CFSTR("Failed to allocate dependancy list for item %@"), anItem);
+ return NULL;
+ }
+ appendDependents(aDependents, anItemList, anItem, anAction);
+ break;
+
+ default:
+ break;
+ }
+ }
+ return aDependents;
+}
+
+/**
+ * countUnmetRequirements counts the number of items in anItemList
+ * which are pending in aStatusDict.
+ **/
+static int countUnmetRequirements(CFDictionaryRef aStatusDict, CFArrayRef anItemList)
+{
+ int aCount = 0;
+ CFIndex anItemCount = CFArrayGetCount(anItemList);
+ CFIndex anItemIndex;
+
+ for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
+ CFStringRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
+ CFStringRef aStatus = CFDictionaryGetValue(aStatusDict, anItem);
+
+ if (!aStatus || !CFEqual(aStatus, kRunSuccess)) {
+ CF_syslog(LOG_DEBUG, CFSTR("\tFailed requirement/uses: %@"), anItem);
+ aCount++;
+ }
+ }
+
+ return aCount;
+}
+
+/**
+ * countDependantsPresent counts the number of items in aWaitingList
+ * which depend on items in anItemList.
+ **/
+static int countDependantsPresent(CFArrayRef aWaitingList, CFArrayRef anItemList, CFStringRef aKey)
+{
+ int aCount = 0;
+ CFIndex anItemCount = CFArrayGetCount(anItemList);
+ CFIndex anItemIndex;
+
+ for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
+ CFStringRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
+ CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, aKey, anItem);
+
+ if (aMatchesList) {
+ aCount = aCount + CFArrayGetCount(aMatchesList);
+ CFRelease(aMatchesList);
+ }
+ }
+
+ return aCount;
+}
+
+/**
+ * pendingAntecedents returns TRUE if any antecedents of this item
+ * are currently running, have not yet run, or none exist.
+ **/
+static Boolean
+pendingAntecedents(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, CFArrayRef anAntecedentList, Action anAction)
+{
+ int aPendingFlag = FALSE;
+
+ CFIndex anAntecedentCount = CFArrayGetCount(anAntecedentList);
+ CFIndex anAntecedentIndex;
+
+ for (anAntecedentIndex = 0; anAntecedentIndex < anAntecedentCount; ++anAntecedentIndex) {
+ CFStringRef anAntecedent = CFArrayGetValueAtIndex(anAntecedentList, anAntecedentIndex);
+ CFStringRef aKey = (anAction == kActionStart) ? kProvidesKey : kUsesKey;
+ CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, aKey, anAntecedent);
+
+ if (aMatchesList) {
+ CFIndex aMatchesListCount = CFArrayGetCount(aMatchesList);
+ CFIndex aMatchesListIndex;
+
+ for (aMatchesListIndex = 0; aMatchesListIndex < aMatchesListCount; ++aMatchesListIndex) {
+ CFDictionaryRef anItem = CFArrayGetValueAtIndex(aMatchesList, aMatchesListIndex);
+
+ if (!anItem ||
+ !CFDictionaryGetValue(anItem, kPIDKey) || !CFDictionaryGetValue(aStatusDict, anAntecedent)) {
+ aPendingFlag = TRUE;
+ break;
+ }
+ }
+
+ CFRelease(aMatchesList);
+
+ if (aPendingFlag)
+ break;
+ }
+ }
+ return (aPendingFlag);
+}
+
+/**
+ * checkForDuplicates returns TRUE if an item provides the same service as a
+ * pending item, or an item that already succeeded.
+ **/
+static Boolean checkForDuplicates(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, CFDictionaryRef anItem)
+{
+ int aDuplicateFlag = FALSE;
+
+ CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
+ CFIndex aProvidesCount = aProvidesList ? CFArrayGetCount(aProvidesList) : 0;
+ CFIndex aProvidesIndex;
+
+ for (aProvidesIndex = 0; aProvidesIndex < aProvidesCount; ++aProvidesIndex) {
+ CFStringRef aProvides = CFArrayGetValueAtIndex(aProvidesList, aProvidesIndex);
+
+ /* If the service succeeded, return true. */
+ CFStringRef aStatus = CFDictionaryGetValue(aStatusDict, aProvides);
+ if (aStatus && CFEqual(aStatus, kRunSuccess)) {
+ aDuplicateFlag = TRUE;
+ break;
+ }
+ /*
+ * Otherwise test if any item is currently running which
+ * might provide that service.
+ */
+ else {
+ CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, kProvidesKey, aProvides);
+ if (aMatchesList) {
+ CFIndex aMatchesListCount = CFArrayGetCount(aMatchesList);
+ CFIndex aMatchesListIndex;
+
+ for (aMatchesListIndex = 0; aMatchesListIndex < aMatchesListCount; ++aMatchesListIndex) {
+ CFDictionaryRef anDupItem = CFArrayGetValueAtIndex(aMatchesList, aMatchesListIndex);
+ if (anDupItem && CFDictionaryGetValue(anDupItem, kPIDKey)) {
+ /*
+ * Item is running, avoid
+ * race condition.
+ */
+ aDuplicateFlag = TRUE;
+ break;
+ } else {
+ CFNumberRef anItemDomain = CFDictionaryGetValue(anItem, kDomainKey);
+ CFNumberRef anotherItemDomain = CFDictionaryGetValue(anDupItem, kDomainKey);
+ /*
+ * If anItem was found later
+ * than aDupItem, stall
+ * anItem until aDupItem
+ * runs.
+ */
+ if (anItemDomain &&
+ anotherItemDomain &&
+ CFNumberCompare(anItemDomain, anotherItemDomain,
+ NULL) == kCFCompareGreaterThan) {
+ /*
+ * Item not running,
+ * but takes
+ * precedence.
+ */
+ aDuplicateFlag = TRUE;
+ break;
+ }
+ }
+ }
+
+ CFRelease(aMatchesList);
+ if (aDuplicateFlag)
+ break;
+ }
+ }
+ }
+ return (aDuplicateFlag);
+}
+
+CFMutableDictionaryRef StartupItemListGetNext(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, Action anAction)
+{
+ CFMutableDictionaryRef aNextItem = NULL;
+ CFIndex aWaitingCount = CFArrayGetCount(aWaitingList);
+ int aMinFailedAntecedents = INT_MAX;
+ CFIndex aWaitingIndex;
+
+ switch (anAction) {
+ case kActionStart:
+ break;
+ case kActionStop:
+ break;
+ case kActionRestart:
+ break;
+ default:
+ return NULL;
+ }
+
+ if (!aWaitingList || !aStatusDict || aWaitingCount <= 0)
+ return NULL;
+
+ /**
+ * Iterate through the items in aWaitingList and look for an optimally ready item.
+ **/
+ for (aWaitingIndex = 0; aWaitingIndex < aWaitingCount; aWaitingIndex++) {
+ CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aWaitingList, aWaitingIndex);
+ CFArrayRef anAntecedentList;
+ int aFailedAntecedentsCount = 0; /* Number of unmet soft
+ * depenancies */
+ Boolean aBestPick = FALSE; /* Is this the best pick
+ * so far? */
+
+ /* Filter out running items. */
+ if (CFDictionaryGetValue(anItem, kPIDKey))
+ continue;
+
+ /*
+ * Filter out dupilicate services; if someone has
+ * provided what we provide, we don't run.
+ */
+ if (checkForDuplicates(aWaitingList, aStatusDict, anItem)) {
+ CF_syslog(LOG_DEBUG, CFSTR("Skipping %@ because of duplicate service."),
+ CFDictionaryGetValue(anItem, kDescriptionKey));
+ continue;
+ }
+ /*
+ * Dependencies don't matter when restarting an item;
+ * stop here.
+ */
+ if (anAction == kActionRestart) {
+ aNextItem = anItem;
+ break;
+ }
+ anAntecedentList = CFDictionaryGetValue(anItem, ((anAction == kActionStart) ? kRequiresKey : kProvidesKey));
+
+ CF_syslog(LOG_DEBUG, CFSTR("Checking %@"), CFDictionaryGetValue(anItem, kDescriptionKey));
+
+ if (anAntecedentList)
+ CF_syslog(LOG_DEBUG, CFSTR("Antecedents: %@"), anAntecedentList);
+ else
+ syslog(LOG_DEBUG, "No antecedents");
+
+ /**
+ * Filter out the items which have unsatisfied antecedents.
+ **/
+ if (anAntecedentList &&
+ ((anAction == kActionStart) ?
+ countUnmetRequirements(aStatusDict, anAntecedentList) :
+ countDependantsPresent(aWaitingList, anAntecedentList, kRequiresKey)))
+ continue;
+
+ /**
+ * anItem has all hard dependancies met; check for soft dependancies.
+ * We'll favor the item with the fewest unmet soft dependancies here.
+ **/
+ anAntecedentList = CFDictionaryGetValue(anItem, ((anAction == kActionStart) ? kUsesKey : kProvidesKey));
+
+ if (anAntecedentList)
+ CF_syslog(LOG_DEBUG, CFSTR("Soft dependancies: %@"), anAntecedentList);
+ else
+ syslog(LOG_DEBUG, "No soft dependancies");
+
+ if (anAntecedentList) {
+ aFailedAntecedentsCount =
+ ((anAction == kActionStart) ?
+ countUnmetRequirements(aStatusDict, anAntecedentList) :
+ countDependantsPresent(aWaitingList, anAntecedentList, kUsesKey));
+ } else {
+ if (aMinFailedAntecedents > 0)
+ aBestPick = TRUE;
+ }
+
+ /*
+ * anItem has unmet dependencies that will
+ * likely be met in the future, so delay it
+ */
+ if (aFailedAntecedentsCount > 0 && pendingAntecedents(aWaitingList, aStatusDict, anAntecedentList, anAction)) {
+ continue;
+ }
+ if (aFailedAntecedentsCount > 0)
+ syslog(LOG_DEBUG, "Total: %d", aFailedAntecedentsCount);
+
+ if (aFailedAntecedentsCount > aMinFailedAntecedents)
+ continue; /* Another item already won out */
+
+ if (aFailedAntecedentsCount < aMinFailedAntecedents)
+ aBestPick = TRUE;
+
+ if (!aBestPick)
+ continue;
+
+ /*
+ * anItem has less unmet
+ * dependancies than any
+ * other item so far, so it
+ * wins.
+ */
+ syslog(LOG_DEBUG, "Best pick so far, based on failed dependancies (%d->%d)",
+ aMinFailedAntecedents, aFailedAntecedentsCount);
+
+ /*
+ * We have a winner! Update success
+ * parameters to match anItem.
+ */
+ aMinFailedAntecedents = aFailedAntecedentsCount;
+ aNextItem = anItem;
+
+ } /* End of waiting list loop. */
+
+ return aNextItem;
+}
+
+CFStringRef StartupItemCreateDescription(CFMutableDictionaryRef anItem)
+{
+ CFStringRef aString = NULL;
+
+ if (anItem)
+ aString = CFDictionaryGetValue(anItem, kDescriptionKey);
+ if (aString)
+ CFRetain(aString);
+ return aString;
+}
+
+pid_t StartupItemGetPID(CFDictionaryRef anItem)
+{
+ CFIndex anItemPID = 0;
+ CFNumberRef aPIDNumber = anItem ? CFDictionaryGetValue(anItem, kPIDKey) : NULL;
+ if (aPIDNumber && CFNumberGetValue(aPIDNumber, kCFNumberCFIndexType, &anItemPID))
+ return (pid_t) anItemPID;
+ else
+ return 0;
+}
+
+CFMutableDictionaryRef StartupItemWithPID(CFArrayRef anItemList, pid_t aPID)
+{
+ CFIndex anItemCount = CFArrayGetCount(anItemList);
+ CFIndex anItemIndex;
+
+ for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
+ CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(anItemList, anItemIndex);
+ CFNumberRef aPIDNumber = CFDictionaryGetValue(anItem, kPIDKey);
+ CFIndex anItemPID;
+
+ if (aPIDNumber) {
+ CFNumberGetValue(aPIDNumber, kCFNumberCFIndexType, &anItemPID);
+
+ if ((pid_t) anItemPID == aPID)
+ return anItem;
+ }
+ }
+
+ return NULL;
+}
+
+int StartupItemRun(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Action anAction)
+{
+ int anError = -1;
+ CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
+ static const CFStringRef stubitems[] = {
+ CFSTR("BootROMUpdater"), /* 3893064 */
+ CFSTR("FCUUpdater"), /* 3893064 */
+ CFSTR("AutoProtect Daemon"), /* 3965785 */
+ CFSTR("Check For Missed Tasks"), /* 3965785 */
+ CFSTR("Privacy"), /* 3933484 */
+ CFSTR("Firmware Update Checking"), /* 4001504 */
+
+ CFSTR("M-Audio FireWire Audio Support"), /* 3931757 */
+ CFSTR("help for M-Audio Delta Family"), /* 3931757 */
+ CFSTR("help for M-Audio Devices"), /* 3931757 */
+ CFSTR("help for M-Audio Revo 5.1"), /* 3931757 */
+ CFSTR("M-Audio USB Duo Configuration Service"), /* 3931757 */
+ CFSTR("firmware loader for M-Audio devices"), /* 3931757 */
+ CFSTR("M-Audio MobilePre USB Configuration Service"), /* 3931757 */
+ CFSTR("M-Audio OmniStudio USB Configuration Service"), /* 3931757 */
+ CFSTR("M-Audio Transit USB Configuration Service"), /* 3931757 */
+ CFSTR("M-Audio Audiophile USB Configuration Service"), /* 3931757 */
+ NULL
+ };
+ const CFStringRef *c;
+
+ if (aProvidesList && anAction == kActionStop) {
+ CFIndex aProvidesCount = CFArrayGetCount(aProvidesList);
+ for (c = stubitems; *c; c++) {
+ if (CFArrayContainsValue(aProvidesList, CFRangeMake(0, aProvidesCount), *c)) {
+ CFIndex aPID = -1;
+ CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
+
+ CFDictionarySetValue(anItem, kPIDKey, aProcessNumber);
+ CFRelease(aProcessNumber);
+
+ StartupItemExit(aStatusDict, anItem, TRUE);
+ return -1;
+ }
+ }
+ }
+
+ if (anAction == kActionNone) {
+ StartupItemExit(aStatusDict, anItem, TRUE);
+ anError = 0;
+ } else {
+ CFStringRef aBundlePathString = CFDictionaryGetValue(anItem, kBundlePathKey);
+ char aBundlePath[PATH_MAX];
+ char anExecutable[PATH_MAX];
+ char *tmp;
+
+ if (!CFStringGetCString(aBundlePathString, aBundlePath, sizeof(aBundlePath), kCFStringEncodingUTF8)) {
+ CF_syslog(LOG_EMERG, CFSTR("Internal error while running item %@"), aBundlePathString);
+ return (anError);
+ }
+ /* Compute path to excecutable */
+ tmp = rindex(aBundlePath, '/');
+ snprintf(anExecutable, sizeof(anExecutable), "%s%s", aBundlePath, tmp);
+
+ /**
+ * Run the bundle
+ **/
+
+ if (access(anExecutable, X_OK)) {
+ /*
+ * Add PID key so that this item is marked as having
+ * been run.
+ */
+ CFIndex aPID = -1;
+ CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
+
+ CFDictionarySetValue(anItem, kPIDKey, aProcessNumber);
+ CFRelease(aProcessNumber);
+
+ CFDictionarySetValue(anItem, kErrorKey, kErrorPermissions);
+ StartupItemExit(aStatusDict, anItem, FALSE);
+ syslog(LOG_ERR, "No executable file %s", anExecutable);
+ } else {
+ pid_t aProccessID = fork();
+
+ switch (aProccessID) {
+ case -1: /* SystemStarter (fork failed) */
+ CFDictionarySetValue(anItem, kErrorKey, kErrorFork);
+ StartupItemExit(aStatusDict, anItem, FALSE);
+
+ CF_syslog(LOG_ERR, CFSTR("Failed to fork for item %@: %s"), aBundlePathString, strerror(errno));
+
+ break;
+
+ default: /* SystemStarter (fork succeeded) */
+ {
+ CFIndex aPID = (CFIndex) aProccessID;
+ CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
+
+ CFDictionarySetValue(anItem, kPIDKey, aProcessNumber);
+ CFRelease(aProcessNumber);
+
+ syslog(LOG_DEBUG, "Running command (%d): %s %s",
+ aProccessID, anExecutable, argumentForAction(anAction));
+ anError = 0;
+ }
+ break;
+
+ case 0: /* Child */
+ {
+ if (setsid() == -1)
+ syslog(LOG_WARNING, "Unable to create session for item %s: %m", anExecutable);
+
+ anError = execl(anExecutable, anExecutable, argumentForAction(anAction), NULL);
+
+ /* We shouldn't get here. */
+
+ syslog(LOG_ERR, "execl(\"%s\"): %m", anExecutable);
+
+ exit(anError);
+ }
+ }
+ }
+ }
+
+ return (anError);
+}
+
+void
+StartupItemSetStatus(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, CFStringRef aServiceName,
+ Boolean aSuccess, Boolean aReplaceFlag)
+{
+ void (*anAction) (CFMutableDictionaryRef, const void *, const void *) = aReplaceFlag ?
+ CFDictionarySetValue : CFDictionaryAddValue;
+
+ if (aStatusDict && anItem) {
+ CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
+ if (aProvidesList) {
+ CFIndex aProvidesCount = CFArrayGetCount(aProvidesList);
+ CFIndex aProvidesIndex;
+
+ /*
+ * If a service name was specified, and it is valid,
+ * use only it.
+ */
+ if (aServiceName && CFArrayContainsValue(aProvidesList, CFRangeMake(0, aProvidesCount), aServiceName)) {
+ aProvidesList = CFArrayCreate(NULL, (const void **)&aServiceName, 1, &kCFTypeArrayCallBacks);
+ aProvidesCount = 1;
+ } else {
+ CFRetain(aProvidesList);
+ }
+
+ for (aProvidesIndex = 0; aProvidesIndex < aProvidesCount; aProvidesIndex++) {
+ CFStringRef aService = CFArrayGetValueAtIndex(aProvidesList, aProvidesIndex);
+
+ if (aSuccess)
+ anAction(aStatusDict, aService, kRunSuccess);
+ else
+ anAction(aStatusDict, aService, kRunFailure);
+ }
+
+ CFRelease(aProvidesList);
+ }
+ }
+}
+
+void StartupItemExit(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Boolean aSuccess)
+{
+ StartupItemSetStatus(aStatusDict, anItem, NULL, aSuccess, FALSE);
+}
--- /dev/null
+/**
+ * StartupItems.h - Startup Item management routines
+ * Wilfredo Sanchez | wsanchez@opensource.apple.com
+ * Kevin Van Vechten | kevinvv@uclink4.berkeley.edu
+ * $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@
+ **/
+
+#ifndef _StartupItems_H_
+#define _StartupItems_H_
+
+#include <NSSystemDirectories.h>
+
+#include <CoreFoundation/CFArray.h>
+#include <CoreFoundation/CFDictionary.h>
+
+#include "SystemStarter.h"
+
+#define kProvidesKey CFSTR("Provides")
+#define kRequiresKey CFSTR("Requires")
+#define kDescriptionKey CFSTR("Description")
+#define kUsesKey CFSTR("Uses")
+#define kErrorKey CFSTR("Error")
+#define kBundlePathKey CFSTR("PathToBundle")
+#define kPIDKey CFSTR("ProcessID")
+#define kDomainKey CFSTR("Domain")
+
+
+#define kErrorPermissions CFSTR("incorrect permissions")
+#define kErrorInternal CFSTR("SystemStarter internal error")
+#define kErrorReturnNonZero CFSTR("execution of Startup script failed")
+#define kErrorFork CFSTR("could not fork() StartupItem")
+
+
+/*
+ * Find all available startup items in NSDomains specified by aMask.
+ */
+CFMutableArrayRef StartupItemListCreateWithMask (NSSearchPathDomainMask aMask);
+
+/*
+ * Returns the item responsible for providing aService.
+ */
+CFMutableDictionaryRef StartupItemListGetProvider (CFArrayRef anItemList, CFStringRef aService);
+
+/*
+ * Creates a list of items in anItemList which depend on anItem, given anAction.
+ */
+CFMutableArrayRef StartupItemListCreateDependentsList (CFMutableArrayRef anItemList,
+ CFStringRef aService ,
+ Action anAction );
+
+/*
+ * Given aWaitingList of startup items, and aStatusDict describing the current
+ * startup state, returns the next startup item to run, if any. Returns nil if
+ * none is available.
+ * Note that this is not necessarily deterministic; if more than one startup
+ * item is ready to run, which item gets returned is not specified. An item is
+ * not ready to run if the specified dependencies are not satisfied yet.
+ */
+CFMutableDictionaryRef StartupItemListGetNext (CFArrayRef aWaitingList,
+ CFDictionaryRef aStatusDict ,
+ Action anAction );
+
+CFMutableDictionaryRef StartupItemWithPID (CFArrayRef anItemList, pid_t aPID);
+pid_t StartupItemGetPID(CFDictionaryRef anItem);
+
+CFStringRef StartupItemCreateDescription(CFMutableDictionaryRef anItem);
+
+/*
+ * Returns a list of currently executing startup items.
+ */
+CFArrayRef StartupItemListCreateFromRunning(CFArrayRef anItemList);
+
+/*
+ * Returns the total number of "Provides" entries of all loaded items.
+ */
+CFIndex StartupItemListCountServices (CFArrayRef anItemList);
+
+
+/*
+ * Utility functions
+ */
+void RemoveItemFromWaitingList(StartupContext aStartupContext, CFMutableDictionaryRef anItem);
+void AddItemToFailedList(StartupContext aStartupContext, CFMutableDictionaryRef anItem);
+
+/*
+ * Run the startup item.
+ */
+int StartupItemRun (CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Action anAction);
+void StartupItemExit (CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Boolean aSuccess);
+void StartupItemSetStatus(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, CFStringRef aServiceName, Boolean aSuccess, Boolean aReplaceFlag);
+
+/*
+ * Check whether file was created before boot and has proper permissions to run.
+ */
+bool StartupItemSecurityCheck(const char *aPath);
+
+#endif /* _StartupItems_H_ */
--- /dev/null
+.Dd April 12, 2002
+.Dt SystemStarter 8
+.Os Darwin
+.Sh NAME
+.Nm SystemStarter
+.\" The following lines are read in generating the apropos(man -k) database. Use only key
+.\" words here as the database is built based on the words here and in the .ND line.
+.\" Use .Nm macro to designate other names for the documented program.
+.Nd Start, stop, and restart system services
+.Sh SYNOPSIS
+.Nm
+.Op Fl gvxdDqn
+.Op Ar action Op Ar service
+.Sh DESCRIPTION
+The
+.Nm
+utility is deprecated. System services should instead be described by a
+.Xr launchd.plist 5 .
+See
+.Xr launchd 8
+for more details.
+The
+.Nm launchd
+utility is available on Mac OS X 10.4 and later.
+.Pp
+In earlier versions of Mac OS X, the
+.Nm
+utility is used to start, stop, and restart the system services which
+are described in the
+.Pa /Library/StartupItems/
+and
+.Pa /System/Library/StartupItems/
+paths.
+.Pp
+The optional
+.Ar action
+argument specifies which action
+.Nm
+performs on the startup items. The optional
+.Ar service
+argument specifies which startup items to perform the action on. If no
+.Ar service
+is specified, all startup items will be acted on; otherwise, only the item providing the
+.Ar service ,
+any items it requires, or any items that depend on it will be acted on.
+.Pp
+During boot
+.Nm
+is invoked by
+.Xr launchd 8
+and is responsible for
+starting all startup items in an order that satisfies each item's
+requirements.
+.Sh ACTIONS
+.Bl -tag -width -indent
+.It Nm start
+start all items, or start the item that provides the specified
+.Ar service
+and all items providing services it requires.
+.It Nm stop
+stop all items, or stop the item that provides the specified
+.Ar service
+and all items that depend on it.
+.It Nm restart
+restart all items, or restart the item providing the specified
+.Ar service .
+.El
+.Sh OPTIONS
+.Bl -tag -width -indent
+.It Fl g
+(ignored)
+.It Fl v
+verbose (text mode) startup
+.It Fl x
+(ignored)
+.It Fl r
+(ignored)
+.It Fl d
+print debugging output
+.It Fl D
+print debugging output and dependencies
+.It Fl q
+be quiet (disable debugging output)
+.It Fl n
+don't actually perform action on items (no-run mode)
+.El
+.Sh NOTES
+Unless an explicit call to
+.Nm ConsoleMessage
+is made,
+.Nm
+examines the exit status of the startup item scripts to determine the success or failure of the services provided by that script.
+.Pp
+.Sh FILES
+.Bl -tag -width -/System/Library/StartupItems -compact
+.It Pa /Library/StartupItems/
+User-installed startup items.
+.It Pa /System/Library/StartupItems/
+System-provided startup items.
+.El
+.Sh SEE ALSO
+.\" List links in ascending order by section, alphabetically within a section.
+.\" Please do not reference files that do not exist without filing a bug report
+.Xr ConsoleMessage 8 ,
+.Xr launchd 8 ,
+.Xr launchd.plist 5 ,
+.Xr rc 8
+.\" .Sh BUGS \" Document known, unremedied bugs
+.Sh HISTORY
+The
+.Nm
+utility appeared in Darwin 1.0 and
+was extended in Darwin 6.0 to support partial startup and interprocess communication.
+.Nm
+was deprecated by
+.Xr launchd 8
+in Darwin 8.0.
--- /dev/null
+/**
+ * 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 <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;
+
+ /*
+ * 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);
+ autodiskmount(); /* wait for Disk Arbitration to report idle */
+
+ 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);
+}
+
+
+/**
+ * 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 = 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;
+ }
+}
+
+/*
+ * print out any error messages to the log regarding non starting StartupItems
+ */
+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);
+
+ /**
+ * 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, anAction);
+
+ /* 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, "%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 {
+ /* must have died due to signal */
+ 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);
+ }
+}
--- /dev/null
+/**
+ * SystemStarter.h - System Starter driver
+ * 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@
+ **/
+
+#ifndef _SYSTEM_STARTER_H_
+#define _SYSTEM_STARTER_H_
+
+/* Structure to pass common objects from system_starter to the IPC handlers */
+typedef struct StartupContextStorage {
+ CFMutableArrayRef aWaitingList;
+ CFMutableArrayRef aFailedList;
+ CFMutableDictionaryRef aStatusDict;
+ int aServicesCount;
+ int aRunningCount;
+} *StartupContext;
+
+#define kFixerDir "/var/db/fixer"
+#define kFixerPath "/var/db/fixer/StartupItems"
+
+/* Action types */
+typedef enum {
+ kActionNone = 0,
+ kActionStart,
+ kActionStop,
+ kActionRestart
+} Action;
+
+void CF_syslog(int level, CFStringRef message, ...);
+extern bool gVerboseFlag;
+
+#endif /* _SYSTEM_STARTER_H_ */
--- /dev/null
+/**
+ * SystemStarterIPC.h - System Starter IPC definitions
+ * Wilfredo Sanchez | wsanchez@opensource.apple.com
+ * Kevin Van Vechten | kevinvv@uclink4.berkeley.edu
+ **
+ * Copyright (c) 1999-2001 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@
+ **
+ * Definitions used for IPC communications with SystemStarter.
+ * SystemStarter listens on a CFMessagePort with the name defined by
+ * kSystemStarterMessagePort. The messageID of each message should
+ * be set to the kIPCProtocolVersion constant. The contents of each
+ * message should be an XML plist containing a dictionary using
+ * the keys defined in this file.
+ **/
+
+#ifndef _SYSTEM_STARTER_IPC_H
+#define _SYSTEM_STARTER_IPC_H
+
+#include <CoreFoundation/CFString.h>
+#include <mach/message.h>
+
+/* Compatible with inline CFMessagePort messages. */
+typedef struct SystemStarterIPCMessage {
+ mach_msg_header_t aHeader;
+ mach_msg_body_t aBody;
+ SInt32 aProtocol;
+ SInt32 aByteLength;
+ /* Data follows. */
+} SystemStarterIPCMessage;
+
+/* Name of the CFMessagePort SystemStarter listens on. */
+#define kSystemStarterMessagePort "com.apple.SystemStarter"
+
+/* kIPCProtocolVersion should be passed as the messageID of the CFMessage. */
+#define kIPCProtocolVersion 0
+
+/* kIPCTypeKey should be provided for all messages. */
+#define kIPCMessageKey CFSTR("Message")
+
+/* Messages are one of the following types: */
+#define kIPCConsoleMessage CFSTR("ConsoleMessage")
+#define kIPCStatusMessage CFSTR("StatusMessage")
+#define kIPCQueryMessage CFSTR("QueryMessage")
+#define kIPCLoadDisplayBundleMessage CFSTR("LoadDisplayBundle")
+#define kIPCUnloadDisplayBundleMessage CFSTR("UnloadDisplayBundle")
+
+/* kIPCServiceNameKey identifies a startup item by one of the services it provides. */
+#define kIPCServiceNameKey CFSTR("ServiceName")
+
+/* kIPCProcessIDKey identifies a running startup item by its process id. */
+#define kIPCProcessIDKey CFSTR("ProcessID")
+
+/* kIPCConsoleMessageKey contains the non-localized string to
+ * display for messages of type kIPCTypeConsoleMessage.
+ */
+#define kIPCConsoleMessageKey CFSTR("ConsoleMessage")
+
+/* kIPCStatus key contains a boolean value. True for success, false for failure. */
+#define kIPCStatusKey CFSTR("StatusKey")
+
+/* kIPCDisplayBundlePathKey contains a string path to the display bundle
+ SystemStarter should attempt to load. */
+#define kIPCDisplayBundlePathKey CFSTR("DisplayBundlePath")
+
+/* kIPCConfigNamegKey contains the name of a config setting to query */
+#define kIPCConfigSettingKey CFSTR("ConfigSetting")
+
+/* Some config settings */
+#define kIPCConfigSettingVerboseFlag CFSTR("VerboseFlag")
+#define kIPCConfigSettingNetworkUp CFSTR("NetworkUp")
+
+#endif /* _SYSTEM_STARTER_IPC_H */
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>KeepAlive</key>
+ <dict>
+ <key>PathState</key>
+ <dict>
+ <key>/etc/rc.local</key>
+ <true/>
+ <key>/etc/rc.shutdown.local</key>
+ <true/>
+ </dict>
+ </dict>
+ <key>Label</key>
+ <string>com.apple.SystemStarter</string>
+ <key>Program</key>
+ <string>/sbin/SystemStarter</string>
+ <key>QueueDirectories</key>
+ <array>
+ <string>/Library/StartupItems</string>
+ <string>/System/Library/StartupItems</string>
+ </array>
+</dict>
+</plist>
--- /dev/null
+# This file is going away
+
+AFPSERVER=-NO-
+AUTHSERVER=-NO-
+TIMESYNC=-NO-
+QTSSERVER=-NO-
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
- 4B0A3103131F266E002DE2E5 /* events.defs in Sources */ = {isa = PBXBuildFile; fileRef = 4B0A30FF131F24AC002DE2E5 /* events.defs */; settings = {ATTRIBUTES = (Server, ); }; };
- 4B0FB8EA1241FE3F00383109 /* domain.defs in Sources */ = {isa = PBXBuildFile; fileRef = 4B0FB8E91241FE3F00383109 /* domain.defs */; settings = {ATTRIBUTES = (Server, ); }; };
- 4B10F1B90F43BE7E00875782 /* launchd_internal.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0BD0E8C8A2A00D41150 /* launchd_internal.defs */; settings = {ATTRIBUTES = (Client, Server, ); }; };
- 4B10F1BA0F43BE7E00875782 /* protocol_vproc.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC3627DF0E9344BF0054F1A3 /* protocol_vproc.defs */; settings = {ATTRIBUTES = (Client, Server, ); }; };
- 4B10F1BB0F43BE7E00875782 /* protocol_job_reply.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC3629160E9348390054F1A3 /* protocol_job_reply.defs */; };
+ 4B10F1B90F43BE7E00875782 /* internal.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0BD0E8C8A2A00D41150 /* internal.defs */; settings = {ATTRIBUTES = (Client, Server, ); }; };
+ 4B10F1BA0F43BE7E00875782 /* job.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC3627DF0E9344BF0054F1A3 /* job.defs */; settings = {ATTRIBUTES = (Client, Server, ); }; };
+ 4B10F1BB0F43BE7E00875782 /* job_reply.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC3629160E9348390054F1A3 /* job_reply.defs */; };
4B10F1BC0F43BE7E00875782 /* mach_exc.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC36291F0E9349410054F1A3 /* mach_exc.defs */; settings = {ATTRIBUTES = (Server, ); }; };
4B10F1BD0F43BE7E00875782 /* notify.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC36290C0E93475F0054F1A3 /* notify.defs */; settings = {ATTRIBUTES = (Server, ); }; };
4B10F1BE0F43BE7E00875782 /* launchd.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0C40E8C8A4700D41150 /* launchd.c */; };
- 4B10F1BF0F43BE7E00875782 /* launchd_runtime.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B50E8C8A1F00D41150 /* launchd_runtime.c */; settings = {COMPILER_FLAGS = "-I\"$SYMROOT\""; }; };
- 4B10F1C00F43BE7E00875782 /* launchd_runtime_kill.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B30E8C8A1F00D41150 /* launchd_runtime_kill.c */; };
- 4B10F1C10F43BE7E00875782 /* launchd_core_logic.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B70E8C8A1F00D41150 /* launchd_core_logic.c */; };
- 4B10F1C20F43BE7E00875782 /* launchd_unix_ipc.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B10E8C8A1F00D41150 /* launchd_unix_ipc.c */; };
- 4B10F1C30F43BE7E00875782 /* launchd_ktrace.c in Sources */ = {isa = PBXBuildFile; fileRef = 72FDB15D0EA7D7B200B2AC84 /* launchd_ktrace.c */; };
- 4B10F1C40F43BE7E00875782 /* protocol_job_forward.defs in Sources */ = {isa = PBXBuildFile; fileRef = 72FDB1BF0EA7E21C00B2AC84 /* protocol_job_forward.defs */; };
+ 4B10F1BF0F43BE7E00875782 /* runtime.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B50E8C8A1F00D41150 /* runtime.c */; settings = {COMPILER_FLAGS = "-I\"$SYMROOT\""; }; };
+ 4B10F1C00F43BE7E00875782 /* kill2.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B30E8C8A1F00D41150 /* kill2.c */; };
+ 4B10F1C10F43BE7E00875782 /* core.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B70E8C8A1F00D41150 /* core.c */; };
+ 4B10F1C20F43BE7E00875782 /* ipc.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B10E8C8A1F00D41150 /* ipc.c */; };
+ 4B10F1C30F43BE7E00875782 /* ktrace.c in Sources */ = {isa = PBXBuildFile; fileRef = 72FDB15D0EA7D7B200B2AC84 /* ktrace.c */; };
+ 4B10F1C40F43BE7E00875782 /* job_forward.defs in Sources */ = {isa = PBXBuildFile; fileRef = 72FDB1BF0EA7E21C00B2AC84 /* job_forward.defs */; };
4B10F1C60F43BE7E00875782 /* libbsm.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FC36292C0E934AA40054F1A3 /* libbsm.dylib */; };
4B10F1C90F43BE7E00875782 /* launchd.conf.5 in CopyFiles */ = {isa = PBXBuildFile; fileRef = FC59A0C30E8C8A4700D41150 /* launchd.conf.5 */; };
4B10F1CA0F43BE7E00875782 /* launchd.plist.5 in CopyFiles */ = {isa = PBXBuildFile; fileRef = FC59A0C10E8C8A4700D41150 /* launchd.plist.5 */; };
4B10F1CD0F43BE7E00875782 /* rc.8 in CopyFiles */ = {isa = PBXBuildFile; fileRef = FC59A0F80E8C8AC300D41150 /* rc.8 */; };
4B10F1CF0F43BE7E00875782 /* rc.netboot in CopyFiles */ = {isa = PBXBuildFile; fileRef = FC59A0F90E8C8AC300D41150 /* rc.netboot */; };
4B10F1E80F43BF5C00875782 /* launchctl.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0AE0E8C8A0E00D41150 /* launchctl.c */; settings = {COMPILER_FLAGS = "-I\"$SDKROOT\"/System/Library/Frameworks/System.framework/PrivateHeaders"; }; };
- 4B10F1EA0F43BF5C00875782 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC36283E0E93463C0054F1A3 /* IOKit.framework */; };
4B10F1EB0F43BF5C00875782 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3628070E9345E10054F1A3 /* CoreFoundation.framework */; };
4B10F1EC0F43BF5C00875782 /* libedit.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FCD713730E95DE49001B0111 /* libedit.dylib */; settings = {ATTRIBUTES = (Weak, ); }; };
4B10F1EF0F43BF5C00875782 /* launchctl.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = FC59A0AD0E8C8A0E00D41150 /* launchctl.1 */; };
+ 4B1D128B143505CB00A2BDED /* log.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D1288143502DA00A2BDED /* log.c */; };
+ 4B1D128C143505CD00A2BDED /* log.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D1288143502DA00A2BDED /* log.c */; };
4B1D92010F8BDE7D00125940 /* launchd.ops in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4B1D91ED0F8BDE1A00125940 /* launchd.ops */; };
- 4B287733111A509400C07B35 /* launchd_helper.defs in Sources */ = {isa = PBXBuildFile; fileRef = 4B287732111A509400C07B35 /* launchd_helper.defs */; settings = {ATTRIBUTES = (Client, Server, ); }; };
- 4B28781B111A61A400C07B35 /* launchd_helper.defs in Sources */ = {isa = PBXBuildFile; fileRef = 4B287732111A509400C07B35 /* launchd_helper.defs */; };
- 4B43EAF414101C5800E9E776 /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B43EAF314101C5800E9E776 /* ServiceManagement.framework */; };
- 4B9A1C1F132759F700019C67 /* events.defs in Sources */ = {isa = PBXBuildFile; fileRef = 4B0A30FF131F24AC002DE2E5 /* events.defs */; settings = {ATTRIBUTES = (Server, ); }; };
+ 4B287733111A509400C07B35 /* helper.defs in Sources */ = {isa = PBXBuildFile; fileRef = 4B287732111A509400C07B35 /* helper.defs */; settings = {ATTRIBUTES = (Client, Server, ); }; };
+ 4B28781B111A61A400C07B35 /* helper.defs in Sources */ = {isa = PBXBuildFile; fileRef = 4B287732111A509400C07B35 /* helper.defs */; };
+ 4B7B6DA8143BD343000BCC80 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC36283E0E93463C0054F1A3 /* IOKit.framework */; };
+ 4B949DD5143D010F008712B9 /* libCrashReporterClient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B949DD4143D010F008712B9 /* libCrashReporterClient.a */; };
+ 4B949DD6143D010F008712B9 /* libCrashReporterClient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B949DD4143D010F008712B9 /* libCrashReporterClient.a */; };
4B9EDCA20EAFC77E00A78496 /* DiskArbitration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B9EDCA10EAFC77E00A78496 /* DiskArbitration.framework */; };
- 4BA2F5FD1243063D00C2AADD /* init.defs in Sources */ = {isa = PBXBuildFile; fileRef = 4BA2F5FC1243063D00C2AADD /* init.defs */; };
- 4BF8727C1187A5F000CC7DB5 /* launchd_helper.defs in Sources */ = {isa = PBXBuildFile; fileRef = 4B287732111A509400C07B35 /* launchd_helper.defs */; };
+ 4BF8727C1187A5F000CC7DB5 /* helper.defs in Sources */ = {isa = PBXBuildFile; fileRef = 4B287732111A509400C07B35 /* helper.defs */; };
+ 60416D1B1402EE6900C190AA /* init.defs in Sources */ = {isa = PBXBuildFile; fileRef = 4BA2F5FC1243063D00C2AADD /* init.defs */; };
+ 60416D1D1402EE6A00C190AA /* init.defs in Sources */ = {isa = PBXBuildFile; fileRef = 4BA2F5FC1243063D00C2AADD /* init.defs */; };
+ 60416D1E1402EE7200C190AA /* domain.defs in Sources */ = {isa = PBXBuildFile; fileRef = 4B0FB8E91241FE3F00383109 /* domain.defs */; settings = {ATTRIBUTES = (Server, ); }; };
+ 60416D1F1402EE7300C190AA /* domain.defs in Sources */ = {isa = PBXBuildFile; fileRef = 4B0FB8E91241FE3F00383109 /* domain.defs */; settings = {ATTRIBUTES = (Server, ); }; };
7215DE4C0EFAF2EC00ABD81E /* libauditd.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 7215DE4B0EFAF2EC00ABD81E /* libauditd.dylib */; };
726055EC0EA7EC2400D65FE7 /* mach_exc.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC36291F0E9349410054F1A3 /* mach_exc.defs */; settings = {ATTRIBUTES = (Server, ); }; };
- 726056090EA7FCF200D65FE7 /* launchd_ktrace.c in Sources */ = {isa = PBXBuildFile; fileRef = 72FDB15D0EA7D7B200B2AC84 /* launchd_ktrace.c */; };
+ 726056090EA7FCF200D65FE7 /* ktrace.c in Sources */ = {isa = PBXBuildFile; fileRef = 72FDB15D0EA7D7B200B2AC84 /* ktrace.c */; };
72AFE8090EFAF3D9004BDA46 /* libauditd.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 7215DE4B0EFAF2EC00ABD81E /* libauditd.dylib */; };
- 72FDB15F0EA7D7B200B2AC84 /* launchd_ktrace.c in Sources */ = {isa = PBXBuildFile; fileRef = 72FDB15D0EA7D7B200B2AC84 /* launchd_ktrace.c */; };
- 72FDB1C00EA7E21C00B2AC84 /* protocol_job_forward.defs in Sources */ = {isa = PBXBuildFile; fileRef = 72FDB1BF0EA7E21C00B2AC84 /* protocol_job_forward.defs */; };
+ 72FDB15F0EA7D7B200B2AC84 /* ktrace.c in Sources */ = {isa = PBXBuildFile; fileRef = 72FDB15D0EA7D7B200B2AC84 /* ktrace.c */; };
+ 72FDB1C00EA7E21C00B2AC84 /* job_forward.defs in Sources */ = {isa = PBXBuildFile; fileRef = 72FDB1BF0EA7E21C00B2AC84 /* job_forward.defs */; };
FC3627BA0E9343220054F1A3 /* StartupItems.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0FD0E8C8ADF00D41150 /* StartupItems.c */; };
FC3627BB0E93432A0054F1A3 /* SystemStarter.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A1000E8C8ADF00D41150 /* SystemStarter.c */; };
FC3627D40E93439B0054F1A3 /* StartupItemContext.8 in CopyFiles */ = {isa = PBXBuildFile; fileRef = FC59A0FE0E8C8ADF00D41150 /* StartupItemContext.8 */; };
FC3627D50E93439B0054F1A3 /* SystemStarter.8 in CopyFiles */ = {isa = PBXBuildFile; fileRef = FC59A1010E8C8ADF00D41150 /* SystemStarter.8 */; };
- FC3627E00E9344BF0054F1A3 /* protocol_vproc.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC3627DF0E9344BF0054F1A3 /* protocol_vproc.defs */; };
- FC3627E10E9344BF0054F1A3 /* protocol_vproc.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC3627DF0E9344BF0054F1A3 /* protocol_vproc.defs */; settings = {ATTRIBUTES = (Client, Server, ); }; };
+ FC3627E00E9344BF0054F1A3 /* job.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC3627DF0E9344BF0054F1A3 /* job.defs */; };
+ FC3627E10E9344BF0054F1A3 /* job.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC3627DF0E9344BF0054F1A3 /* job.defs */; settings = {ATTRIBUTES = (Client, Server, ); }; };
FC3628080E9345E10054F1A3 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3628070E9345E10054F1A3 /* CoreFoundation.framework */; };
FC3628090E9345E10054F1A3 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3628070E9345E10054F1A3 /* CoreFoundation.framework */; };
FC36283F0E93463C0054F1A3 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC36283E0E93463C0054F1A3 /* IOKit.framework */; };
FC36290D0E93475F0054F1A3 /* notify.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC36290C0E93475F0054F1A3 /* notify.defs */; settings = {ATTRIBUTES = (Server, ); }; };
- FC3629170E9348390054F1A3 /* protocol_job_reply.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC3629160E9348390054F1A3 /* protocol_job_reply.defs */; };
+ FC3629170E9348390054F1A3 /* job_reply.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC3629160E9348390054F1A3 /* job_reply.defs */; };
FC36292D0E934AA40054F1A3 /* libbsm.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FC36292C0E934AA40054F1A3 /* libbsm.dylib */; };
FC59A0A60E8C89C100D41150 /* IPC.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0A50E8C89C100D41150 /* IPC.c */; };
FC59A0AF0E8C8A0E00D41150 /* launchctl.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0AE0E8C8A0E00D41150 /* launchctl.c */; settings = {COMPILER_FLAGS = "-I\"$SDKROOT\"/System/Library/Frameworks/System.framework/PrivateHeaders"; }; };
- FC59A0B80E8C8A1F00D41150 /* launchd_unix_ipc.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B10E8C8A1F00D41150 /* launchd_unix_ipc.c */; };
- FC59A0B90E8C8A1F00D41150 /* launchd_runtime_kill.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B30E8C8A1F00D41150 /* launchd_runtime_kill.c */; };
- FC59A0BA0E8C8A1F00D41150 /* launchd_runtime.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B50E8C8A1F00D41150 /* launchd_runtime.c */; settings = {COMPILER_FLAGS = "-I\"$SYMROOT\""; }; };
- FC59A0BB0E8C8A1F00D41150 /* launchd_core_logic.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B70E8C8A1F00D41150 /* launchd_core_logic.c */; };
- FC59A0BF0E8C8A2A00D41150 /* launchd_internal.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0BD0E8C8A2A00D41150 /* launchd_internal.defs */; settings = {ATTRIBUTES = (Client, Server, ); }; };
+ FC59A0B80E8C8A1F00D41150 /* ipc.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B10E8C8A1F00D41150 /* ipc.c */; };
+ FC59A0B90E8C8A1F00D41150 /* kill2.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B30E8C8A1F00D41150 /* kill2.c */; };
+ FC59A0BA0E8C8A1F00D41150 /* runtime.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B50E8C8A1F00D41150 /* runtime.c */; settings = {COMPILER_FLAGS = "-I\"$SYMROOT\""; }; };
+ FC59A0BB0E8C8A1F00D41150 /* core.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0B70E8C8A1F00D41150 /* core.c */; };
+ FC59A0BF0E8C8A2A00D41150 /* internal.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0BD0E8C8A2A00D41150 /* internal.defs */; settings = {ATTRIBUTES = (Client, Server, ); }; };
FC59A0C50E8C8A4700D41150 /* launchd.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0C40E8C8A4700D41150 /* launchd.c */; };
FC59A0DC0E8C8A6900D41150 /* launchproxy.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0DA0E8C8A6900D41150 /* launchproxy.c */; };
FC59A0ED0E8C8AA600D41150 /* vproc.h in Headers */ = {isa = PBXBuildFile; fileRef = FC59A0E20E8C8AA600D41150 /* vproc.h */; settings = {ATTRIBUTES = (Public, ); }; };
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 4B0A30FF131F24AC002DE2E5 /* events.defs */ = {isa = PBXFileReference; explicitFileType = sourcecode.mig; fileEncoding = 4; name = events.defs; path = /usr/local/include/xpc/events.defs; sourceTree = SDKROOT; };
- 4B0A3100131F24AC002DE2E5 /* types.defs */ = {isa = PBXFileReference; explicitFileType = sourcecode.mig; fileEncoding = 4; name = types.defs; path = /usr/local/include/xpc/types.defs; sourceTree = SDKROOT; };
- 4B0FB8E91241FE3F00383109 /* domain.defs */ = {isa = PBXFileReference; explicitFileType = sourcecode.mig; fileEncoding = 4; name = domain.defs; path = /usr/local/include/xpc/domain.defs; sourceTree = SDKROOT; };
+ 4B0A3100131F24AC002DE2E5 /* types.defs */ = {isa = PBXFileReference; explicitFileType = sourcecode.mig; fileEncoding = 4; name = types.defs; path = usr/local/include/xpc/types.defs; sourceTree = SDKROOT; };
+ 4B0FB8E91241FE3F00383109 /* domain.defs */ = {isa = PBXFileReference; explicitFileType = sourcecode.mig; fileEncoding = 4; name = domain.defs; path = usr/local/include/xpc/domain.defs; sourceTree = SDKROOT; };
4B10F1D30F43BE7E00875782 /* launchd */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = launchd; sourceTree = BUILT_PRODUCTS_DIR; };
4B10F1F30F43BF5C00875782 /* launchctl */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = launchctl; sourceTree = BUILT_PRODUCTS_DIR; };
- 4B1D91ED0F8BDE1A00125940 /* launchd.ops */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = launchd.ops; path = launchd/src/launchd.ops; sourceTree = "<group>"; };
- 4B287732111A509400C07B35 /* launchd_helper.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = launchd_helper.defs; path = launchd/src/launchd_helper.defs; sourceTree = "<group>"; };
+ 4B1D12741433D79800A2BDED /* launchd.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = launchd.xcconfig; path = xcconfigs/launchd.xcconfig; sourceTree = "<group>"; };
+ 4B1D12751433D7BE00A2BDED /* liblaunch.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = liblaunch.xcconfig; path = xcconfigs/liblaunch.xcconfig; sourceTree = "<group>"; };
+ 4B1D12771433E5EB00A2BDED /* launchctl.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = launchctl.xcconfig; path = xcconfigs/launchctl.xcconfig; sourceTree = "<group>"; };
+ 4B1D12781433E70400A2BDED /* launchd-postflight.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; name = "launchd-postflight.sh"; path = "xcscripts/launchd-postflight.sh"; sourceTree = "<group>"; };
+ 4B1D12791433E76F00A2BDED /* liblaunch-postflight.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; name = "liblaunch-postflight.sh"; path = "xcscripts/liblaunch-postflight.sh"; sourceTree = "<group>"; };
+ 4B1D127A1433E7A400A2BDED /* launchctl-postflight.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; name = "launchctl-postflight.sh"; path = "xcscripts/launchctl-postflight.sh"; sourceTree = "<group>"; };
+ 4B1D127C1433E85A00A2BDED /* SystemStarter-postflight.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; name = "SystemStarter-postflight.sh"; path = "xcscripts/SystemStarter-postflight.sh"; sourceTree = "<group>"; };
+ 4B1D127D1433E91D00A2BDED /* common.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = common.xcconfig; path = xcconfigs/common.xcconfig; sourceTree = "<group>"; };
+ 4B1D1288143502DA00A2BDED /* log.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; lineEnding = 0; name = log.c; path = src/log.c; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.c; };
+ 4B1D128A143502F000A2BDED /* log.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = log.h; path = src/log.h; sourceTree = "<group>"; };
+ 4B1D91ED0F8BDE1A00125940 /* launchd.ops */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = launchd.ops; path = liblaunch/launchd.ops; sourceTree = "<group>"; };
+ 4B287732111A509400C07B35 /* helper.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = helper.defs; path = src/helper.defs; sourceTree = "<group>"; };
4B43EAF314101C5800E9E776 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = /System/Library/Frameworks/ServiceManagement.framework; sourceTree = "<absolute>"; };
+ 4B949DD4143D010F008712B9 /* libCrashReporterClient.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libCrashReporterClient.a; path = usr/local/lib/libCrashReporterClient.a; sourceTree = SDKROOT; };
4B9EDCA10EAFC77E00A78496 /* DiskArbitration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DiskArbitration.framework; path = /System/Library/Frameworks/DiskArbitration.framework; sourceTree = "<absolute>"; };
- 4BA2F5FC1243063D00C2AADD /* init.defs */ = {isa = PBXFileReference; explicitFileType = sourcecode.mig; fileEncoding = 4; name = init.defs; path = /usr/local/include/xpc/init.defs; sourceTree = SDKROOT; };
+ 4BA2F5FC1243063D00C2AADD /* init.defs */ = {isa = PBXFileReference; explicitFileType = sourcecode.mig; fileEncoding = 4; name = init.defs; path = usr/local/include/xpc/init.defs; sourceTree = SDKROOT; };
+ 4BC868A8143CFD0300B46F40 /* osx_redirect_name */ = {isa = PBXFileReference; lastKnownFileType = text; name = osx_redirect_name; path = xcsupport/osx_redirect_name; sourceTree = "<group>"; };
7215DE4B0EFAF2EC00ABD81E /* libauditd.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libauditd.dylib; path = /usr/lib/libauditd.dylib; sourceTree = "<absolute>"; };
- 721FBEA50EA7ABC40057462B /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = config.h; path = launchd/src/config.h; sourceTree = "<group>"; };
- 72FDB15D0EA7D7B200B2AC84 /* launchd_ktrace.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = launchd_ktrace.c; path = launchd/src/launchd_ktrace.c; sourceTree = "<group>"; };
- 72FDB15E0EA7D7B200B2AC84 /* launchd_ktrace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = launchd_ktrace.h; path = launchd/src/launchd_ktrace.h; sourceTree = "<group>"; };
- 72FDB1BF0EA7E21C00B2AC84 /* protocol_job_forward.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = protocol_job_forward.defs; path = launchd/src/protocol_job_forward.defs; sourceTree = "<group>"; };
- FC3627DF0E9344BF0054F1A3 /* protocol_vproc.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = protocol_vproc.defs; path = launchd/src/protocol_vproc.defs; sourceTree = "<group>"; };
+ 721FBEA50EA7ABC40057462B /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = config.h; path = src/config.h; sourceTree = "<group>"; };
+ 72FDB15D0EA7D7B200B2AC84 /* ktrace.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ktrace.c; path = src/ktrace.c; sourceTree = "<group>"; };
+ 72FDB15E0EA7D7B200B2AC84 /* ktrace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ktrace.h; path = src/ktrace.h; sourceTree = "<group>"; };
+ 72FDB1BF0EA7E21C00B2AC84 /* job_forward.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = job_forward.defs; path = src/job_forward.defs; sourceTree = "<group>"; };
+ FC3627DF0E9344BF0054F1A3 /* job.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = job.defs; path = src/job.defs; sourceTree = "<group>"; };
FC3628070E9345E10054F1A3 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = "<absolute>"; };
FC36283E0E93463C0054F1A3 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = /System/Library/Frameworks/IOKit.framework; sourceTree = "<absolute>"; };
- FC36290C0E93475F0054F1A3 /* notify.defs */ = {isa = PBXFileReference; explicitFileType = sourcecode.mig; fileEncoding = 4; name = notify.defs; path = /usr/include/mach/notify.defs; sourceTree = SDKROOT; };
- FC3629160E9348390054F1A3 /* protocol_job_reply.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = protocol_job_reply.defs; path = launchd/src/protocol_job_reply.defs; sourceTree = "<group>"; };
- FC36291F0E9349410054F1A3 /* mach_exc.defs */ = {isa = PBXFileReference; explicitFileType = sourcecode.mig; fileEncoding = 4; name = mach_exc.defs; path = /usr/include/mach/mach_exc.defs; sourceTree = SDKROOT; };
+ FC36290C0E93475F0054F1A3 /* notify.defs */ = {isa = PBXFileReference; explicitFileType = sourcecode.mig; fileEncoding = 4; name = notify.defs; path = usr/include/mach/notify.defs; sourceTree = SDKROOT; };
+ FC3629160E9348390054F1A3 /* job_reply.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = job_reply.defs; path = src/job_reply.defs; sourceTree = "<group>"; };
+ FC36291F0E9349410054F1A3 /* mach_exc.defs */ = {isa = PBXFileReference; explicitFileType = sourcecode.mig; fileEncoding = 4; name = mach_exc.defs; path = usr/include/mach/mach_exc.defs; sourceTree = SDKROOT; };
FC36292C0E934AA40054F1A3 /* libbsm.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libbsm.dylib; path = /usr/lib/libbsm.dylib; sourceTree = "<absolute>"; };
FC59A0540E8C884700D41150 /* launchd */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = launchd; sourceTree = BUILT_PRODUCTS_DIR; };
FC59A0600E8C885100D41150 /* liblaunch.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = liblaunch.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
FC59A06D0E8C888A00D41150 /* launchctl */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = launchctl; sourceTree = BUILT_PRODUCTS_DIR; };
FC59A0910E8C892300D41150 /* SystemStarter */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SystemStarter; sourceTree = BUILT_PRODUCTS_DIR; };
- FC59A0A40E8C89C100D41150 /* IPC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IPC.h; path = launchd/src/IPC.h; sourceTree = SOURCE_ROOT; };
- FC59A0A50E8C89C100D41150 /* IPC.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = IPC.c; path = launchd/src/IPC.c; sourceTree = SOURCE_ROOT; };
- FC59A0AD0E8C8A0E00D41150 /* launchctl.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; name = launchctl.1; path = launchd/src/launchctl.1; sourceTree = SOURCE_ROOT; };
- FC59A0AE0E8C8A0E00D41150 /* launchctl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = launchctl.c; path = launchd/src/launchctl.c; sourceTree = SOURCE_ROOT; };
- FC59A0B00E8C8A1F00D41150 /* launchd_unix_ipc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = launchd_unix_ipc.h; path = launchd/src/launchd_unix_ipc.h; sourceTree = "<group>"; };
- FC59A0B10E8C8A1F00D41150 /* launchd_unix_ipc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = launchd_unix_ipc.c; path = launchd/src/launchd_unix_ipc.c; sourceTree = "<group>"; };
- FC59A0B20E8C8A1F00D41150 /* launchd_runtime_kill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = launchd_runtime_kill.h; path = launchd/src/launchd_runtime_kill.h; sourceTree = "<group>"; };
- FC59A0B30E8C8A1F00D41150 /* launchd_runtime_kill.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = launchd_runtime_kill.c; path = launchd/src/launchd_runtime_kill.c; sourceTree = "<group>"; };
- FC59A0B40E8C8A1F00D41150 /* launchd_runtime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = launchd_runtime.h; path = launchd/src/launchd_runtime.h; sourceTree = "<group>"; };
- FC59A0B50E8C8A1F00D41150 /* launchd_runtime.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = launchd_runtime.c; path = launchd/src/launchd_runtime.c; sourceTree = "<group>"; };
- FC59A0B60E8C8A1F00D41150 /* launchd_core_logic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = launchd_core_logic.h; path = launchd/src/launchd_core_logic.h; sourceTree = "<group>"; };
- FC59A0B70E8C8A1F00D41150 /* launchd_core_logic.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = launchd_core_logic.c; path = launchd/src/launchd_core_logic.c; sourceTree = "<group>"; };
- FC59A0BC0E8C8A2A00D41150 /* launchd_mig_types.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = launchd_mig_types.defs; path = launchd/src/launchd_mig_types.defs; sourceTree = "<group>"; };
- FC59A0BD0E8C8A2A00D41150 /* launchd_internal.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = launchd_internal.defs; path = launchd/src/launchd_internal.defs; sourceTree = "<group>"; };
- FC59A0C00E8C8A3A00D41150 /* launchd.8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = launchd.8; path = launchd/src/launchd.8; sourceTree = "<group>"; };
- FC59A0C10E8C8A4700D41150 /* launchd.plist.5 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = launchd.plist.5; path = launchd/src/launchd.plist.5; sourceTree = "<group>"; };
- FC59A0C20E8C8A4700D41150 /* launchd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = launchd.h; path = launchd/src/launchd.h; sourceTree = "<group>"; };
- FC59A0C30E8C8A4700D41150 /* launchd.conf.5 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = launchd.conf.5; path = launchd/src/launchd.conf.5; sourceTree = "<group>"; };
- FC59A0C40E8C8A4700D41150 /* launchd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = launchd.c; path = launchd/src/launchd.c; sourceTree = "<group>"; };
+ FC59A0A40E8C89C100D41150 /* IPC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IPC.h; path = SystemStarter/IPC.h; sourceTree = SOURCE_ROOT; };
+ FC59A0A50E8C89C100D41150 /* IPC.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = IPC.c; path = SystemStarter/IPC.c; sourceTree = SOURCE_ROOT; };
+ FC59A0AD0E8C8A0E00D41150 /* launchctl.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; name = launchctl.1; path = man/launchctl.1; sourceTree = SOURCE_ROOT; };
+ FC59A0AE0E8C8A0E00D41150 /* launchctl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = launchctl.c; path = support/launchctl.c; sourceTree = SOURCE_ROOT; };
+ FC59A0B00E8C8A1F00D41150 /* ipc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ipc.h; path = src/ipc.h; sourceTree = "<group>"; };
+ FC59A0B10E8C8A1F00D41150 /* ipc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ipc.c; path = src/ipc.c; sourceTree = "<group>"; };
+ FC59A0B20E8C8A1F00D41150 /* kill2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = kill2.h; path = src/kill2.h; sourceTree = "<group>"; };
+ FC59A0B30E8C8A1F00D41150 /* kill2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = kill2.c; path = src/kill2.c; sourceTree = "<group>"; };
+ FC59A0B40E8C8A1F00D41150 /* runtime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = runtime.h; path = src/runtime.h; sourceTree = "<group>"; };
+ FC59A0B50E8C8A1F00D41150 /* runtime.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = runtime.c; path = src/runtime.c; sourceTree = "<group>"; };
+ FC59A0B60E8C8A1F00D41150 /* core.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = core.h; path = src/core.h; sourceTree = "<group>"; };
+ FC59A0B70E8C8A1F00D41150 /* core.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = core.c; path = src/core.c; sourceTree = "<group>"; };
+ FC59A0BC0E8C8A2A00D41150 /* job_types.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = job_types.defs; path = src/job_types.defs; sourceTree = "<group>"; };
+ FC59A0BD0E8C8A2A00D41150 /* internal.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = internal.defs; path = src/internal.defs; sourceTree = "<group>"; };
+ FC59A0C00E8C8A3A00D41150 /* launchd.8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = launchd.8; path = man/launchd.8; sourceTree = "<group>"; };
+ FC59A0C10E8C8A4700D41150 /* launchd.plist.5 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = launchd.plist.5; path = man/launchd.plist.5; sourceTree = "<group>"; };
+ FC59A0C20E8C8A4700D41150 /* launchd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = launchd.h; path = src/launchd.h; sourceTree = "<group>"; };
+ FC59A0C30E8C8A4700D41150 /* launchd.conf.5 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = launchd.conf.5; path = man/launchd.conf.5; sourceTree = "<group>"; };
+ FC59A0C40E8C8A4700D41150 /* launchd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = launchd.c; path = src/launchd.c; sourceTree = "<group>"; };
FC59A0CE0E8C8A5C00D41150 /* launchproxy */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = launchproxy; sourceTree = BUILT_PRODUCTS_DIR; };
- FC59A0DA0E8C8A6900D41150 /* launchproxy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = launchproxy.c; path = launchd/src/launchproxy.c; sourceTree = SOURCE_ROOT; };
- FC59A0DB0E8C8A6900D41150 /* launchproxy.8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = launchproxy.8; path = launchd/src/launchproxy.8; sourceTree = SOURCE_ROOT; };
- FC59A0E20E8C8AA600D41150 /* vproc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vproc.h; path = launchd/src/vproc.h; sourceTree = "<group>"; };
- FC59A0E30E8C8AA600D41150 /* vproc_priv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vproc_priv.h; path = launchd/src/vproc_priv.h; sourceTree = "<group>"; };
- FC59A0E40E8C8AA600D41150 /* vproc_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vproc_internal.h; path = launchd/src/vproc_internal.h; sourceTree = "<group>"; };
- FC59A0E50E8C8AA600D41150 /* libvproc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = libvproc.c; path = launchd/src/libvproc.c; sourceTree = SOURCE_ROOT; };
- FC59A0E60E8C8AA600D41150 /* launch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = launch.h; path = launchd/src/launch.h; sourceTree = "<group>"; };
- FC59A0E70E8C8AA600D41150 /* launch_priv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = launch_priv.h; path = launchd/src/launch_priv.h; sourceTree = "<group>"; };
- FC59A0E80E8C8AA600D41150 /* launch_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = launch_internal.h; path = launchd/src/launch_internal.h; sourceTree = "<group>"; };
- FC59A0E90E8C8AA600D41150 /* liblaunch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = liblaunch.c; path = launchd/src/liblaunch.c; sourceTree = SOURCE_ROOT; };
- FC59A0EA0E8C8AA600D41150 /* bootstrap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bootstrap.h; path = launchd/src/bootstrap.h; sourceTree = "<group>"; };
- FC59A0EB0E8C8AA600D41150 /* bootstrap_priv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bootstrap_priv.h; path = launchd/src/bootstrap_priv.h; sourceTree = "<group>"; };
- FC59A0EC0E8C8AA600D41150 /* libbootstrap.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = libbootstrap.c; path = launchd/src/libbootstrap.c; sourceTree = SOURCE_ROOT; };
- FC59A0F80E8C8AC300D41150 /* rc.8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = rc.8; path = launchd/src/rc.8; sourceTree = "<group>"; };
- FC59A0F90E8C8AC300D41150 /* rc.netboot */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = rc.netboot; path = launchd/src/rc.netboot; sourceTree = "<group>"; };
- FC59A0FA0E8C8AC300D41150 /* rc.common */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = rc.common; path = launchd/src/rc.common; sourceTree = "<group>"; };
- FC59A0FB0E8C8ACE00D41150 /* reboot2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = reboot2.h; path = launchd/src/reboot2.h; sourceTree = SOURCE_ROOT; };
- FC59A0FD0E8C8ADF00D41150 /* StartupItems.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = StartupItems.c; path = launchd/src/StartupItems.c; sourceTree = SOURCE_ROOT; };
- FC59A0FE0E8C8ADF00D41150 /* StartupItemContext.8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = StartupItemContext.8; path = launchd/src/StartupItemContext.8; sourceTree = SOURCE_ROOT; };
- FC59A0FF0E8C8ADF00D41150 /* StartupItemContext */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = StartupItemContext; path = launchd/src/StartupItemContext; sourceTree = SOURCE_ROOT; };
- FC59A1000E8C8ADF00D41150 /* SystemStarter.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SystemStarter.c; path = launchd/src/SystemStarter.c; sourceTree = SOURCE_ROOT; };
- FC59A1010E8C8ADF00D41150 /* SystemStarter.8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = SystemStarter.8; path = launchd/src/SystemStarter.8; sourceTree = SOURCE_ROOT; };
- FC59A1020E8C8ADF00D41150 /* StartupItems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StartupItems.h; path = launchd/src/StartupItems.h; sourceTree = SOURCE_ROOT; };
- FC59A1030E8C8ADF00D41150 /* SystemStarterIPC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SystemStarterIPC.h; path = launchd/src/SystemStarterIPC.h; sourceTree = SOURCE_ROOT; };
- FC59A1040E8C8ADF00D41150 /* SystemStarter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SystemStarter.h; path = launchd/src/SystemStarter.h; sourceTree = SOURCE_ROOT; };
- FCD713200E95D5D3001B0111 /* com.apple.SystemStarter.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = com.apple.SystemStarter.plist; path = launchd/src/com.apple.SystemStarter.plist; sourceTree = "<group>"; };
+ FC59A0DA0E8C8A6900D41150 /* launchproxy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = launchproxy.c; path = support/launchproxy.c; sourceTree = SOURCE_ROOT; };
+ FC59A0DB0E8C8A6900D41150 /* launchproxy.8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = launchproxy.8; path = man/launchproxy.8; sourceTree = SOURCE_ROOT; };
+ FC59A0E20E8C8AA600D41150 /* vproc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vproc.h; path = liblaunch/vproc.h; sourceTree = "<group>"; };
+ FC59A0E30E8C8AA600D41150 /* vproc_priv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vproc_priv.h; path = liblaunch/vproc_priv.h; sourceTree = "<group>"; };
+ FC59A0E40E8C8AA600D41150 /* vproc_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vproc_internal.h; path = liblaunch/vproc_internal.h; sourceTree = "<group>"; };
+ FC59A0E50E8C8AA600D41150 /* libvproc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = libvproc.c; path = liblaunch/libvproc.c; sourceTree = SOURCE_ROOT; };
+ FC59A0E60E8C8AA600D41150 /* launch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = launch.h; path = liblaunch/launch.h; sourceTree = "<group>"; };
+ FC59A0E70E8C8AA600D41150 /* launch_priv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = launch_priv.h; path = liblaunch/launch_priv.h; sourceTree = "<group>"; };
+ FC59A0E80E8C8AA600D41150 /* launch_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = launch_internal.h; path = liblaunch/launch_internal.h; sourceTree = "<group>"; };
+ FC59A0E90E8C8AA600D41150 /* liblaunch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = liblaunch.c; path = liblaunch/liblaunch.c; sourceTree = SOURCE_ROOT; };
+ FC59A0EA0E8C8AA600D41150 /* bootstrap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bootstrap.h; path = liblaunch/bootstrap.h; sourceTree = "<group>"; };
+ FC59A0EB0E8C8AA600D41150 /* bootstrap_priv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bootstrap_priv.h; path = liblaunch/bootstrap_priv.h; sourceTree = "<group>"; };
+ FC59A0EC0E8C8AA600D41150 /* libbootstrap.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = libbootstrap.c; path = liblaunch/libbootstrap.c; sourceTree = SOURCE_ROOT; };
+ FC59A0F80E8C8AC300D41150 /* rc.8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = rc.8; path = man/rc.8; sourceTree = "<group>"; };
+ FC59A0F90E8C8AC300D41150 /* rc.netboot */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = rc.netboot; path = rc/rc.netboot; sourceTree = "<group>"; };
+ FC59A0FA0E8C8AC300D41150 /* rc.common */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = rc.common; path = rc/rc.common; sourceTree = "<group>"; };
+ FC59A0FB0E8C8ACE00D41150 /* reboot2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = reboot2.h; path = liblaunch/reboot2.h; sourceTree = SOURCE_ROOT; };
+ FC59A0FD0E8C8ADF00D41150 /* StartupItems.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = StartupItems.c; path = SystemStarter/StartupItems.c; sourceTree = SOURCE_ROOT; };
+ FC59A0FE0E8C8ADF00D41150 /* StartupItemContext.8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = StartupItemContext.8; path = SystemStarter/StartupItemContext.8; sourceTree = SOURCE_ROOT; };
+ FC59A0FF0E8C8ADF00D41150 /* StartupItemContext */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = StartupItemContext; path = SystemStarter/StartupItemContext; sourceTree = SOURCE_ROOT; };
+ FC59A1000E8C8ADF00D41150 /* SystemStarter.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SystemStarter.c; path = SystemStarter/SystemStarter.c; sourceTree = SOURCE_ROOT; };
+ FC59A1010E8C8ADF00D41150 /* SystemStarter.8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = SystemStarter.8; path = SystemStarter/SystemStarter.8; sourceTree = SOURCE_ROOT; };
+ FC59A1020E8C8ADF00D41150 /* StartupItems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StartupItems.h; path = SystemStarter/StartupItems.h; sourceTree = SOURCE_ROOT; };
+ FC59A1030E8C8ADF00D41150 /* SystemStarterIPC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SystemStarterIPC.h; path = SystemStarter/SystemStarterIPC.h; sourceTree = SOURCE_ROOT; };
+ FC59A1040E8C8ADF00D41150 /* SystemStarter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SystemStarter.h; path = SystemStarter/SystemStarter.h; sourceTree = SOURCE_ROOT; };
+ FCD713200E95D5D3001B0111 /* com.apple.SystemStarter.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = com.apple.SystemStarter.plist; path = SystemStarter/com.apple.SystemStarter.plist; sourceTree = "<group>"; };
FCD7132B0E95D64D001B0111 /* wait4path */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = wait4path; sourceTree = BUILT_PRODUCTS_DIR; };
- FCD713370E95D69E001B0111 /* wait4path.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; name = wait4path.1; path = launchd/src/wait4path.1; sourceTree = "<group>"; };
- FCD713380E95D69E001B0111 /* wait4path.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wait4path.c; path = launchd/src/wait4path.c; sourceTree = "<group>"; };
- FCD713520E95D7B3001B0111 /* hostconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = hostconfig; path = launchd/src/hostconfig; sourceTree = "<group>"; };
+ FCD713370E95D69E001B0111 /* wait4path.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; name = wait4path.1; path = man/wait4path.1; sourceTree = "<group>"; };
+ FCD713380E95D69E001B0111 /* wait4path.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wait4path.c; path = support/wait4path.c; sourceTree = "<group>"; };
+ FCD713520E95D7B3001B0111 /* hostconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = hostconfig; path = SystemStarter/hostconfig; sourceTree = "<group>"; };
FCD713730E95DE49001B0111 /* libedit.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libedit.dylib; path = /usr/lib/libedit.dylib; sourceTree = "<absolute>"; };
/* End PBXFileReference section */
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 4B10F1EA0F43BF5C00875782 /* IOKit.framework in Frameworks */,
4B10F1EB0F43BF5C00875782 /* CoreFoundation.framework in Frameworks */,
+ 4B7B6DA8143BD343000BCC80 /* IOKit.framework in Frameworks */,
4B10F1EC0F43BF5C00875782 /* libedit.dylib in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
files = (
FC36292D0E934AA40054F1A3 /* libbsm.dylib in Frameworks */,
7215DE4C0EFAF2EC00ABD81E /* libauditd.dylib in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- FC59A05E0E8C885100D41150 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
+ 4B949DD5143D010F008712B9 /* libCrashReporterClient.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 4B43EAF414101C5800E9E776 /* ServiceManagement.framework in Frameworks */,
FCC841CC0EA7138700C01666 /* IOKit.framework in Frameworks */,
FC3628080E9345E10054F1A3 /* CoreFoundation.framework in Frameworks */,
FCD713740E95DE49001B0111 /* libedit.dylib in Frameworks */,
72AFE8090EFAF3D9004BDA46 /* libauditd.dylib in Frameworks */,
+ 4B949DD6143D010F008712B9 /* libCrashReporterClient.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
- 4B0A3102131F24B3002DE2E5 /* XPC */ = {
+ 4B1D12731433D77400A2BDED /* xcconfigs */ = {
isa = PBXGroup;
children = (
- 4B0A3100131F24AC002DE2E5 /* types.defs */,
- 4B0FB8E91241FE3F00383109 /* domain.defs */,
- 4B0A30FF131F24AC002DE2E5 /* events.defs */,
- 4BA2F5FC1243063D00C2AADD /* init.defs */,
+ 4B1D127D1433E91D00A2BDED /* common.xcconfig */,
+ 4B1D12741433D79800A2BDED /* launchd.xcconfig */,
+ 4B1D12751433D7BE00A2BDED /* liblaunch.xcconfig */,
+ 4B1D12771433E5EB00A2BDED /* launchctl.xcconfig */,
);
- name = XPC;
+ name = xcconfigs;
sourceTree = "<group>";
};
- 4B8D8239132C5F400081FD4E /* Mach */ = {
+ 4B1D12761433D94200A2BDED /* xcscripts */ = {
isa = PBXGroup;
children = (
- FC36291F0E9349410054F1A3 /* mach_exc.defs */,
- FC36290C0E93475F0054F1A3 /* notify.defs */,
+ 4B1D12781433E70400A2BDED /* launchd-postflight.sh */,
+ 4B1D12791433E76F00A2BDED /* liblaunch-postflight.sh */,
+ 4B1D127A1433E7A400A2BDED /* launchctl-postflight.sh */,
+ 4B1D127C1433E85A00A2BDED /* SystemStarter-postflight.sh */,
+ );
+ name = xcscripts;
+ sourceTree = "<group>";
+ };
+ 4B2086731433CBEF006A5B71 /* man */ = {
+ isa = PBXGroup;
+ children = (
+ FC59A0C00E8C8A3A00D41150 /* launchd.8 */,
+ FC59A0C10E8C8A4700D41150 /* launchd.plist.5 */,
+ FC59A0C30E8C8A4700D41150 /* launchd.conf.5 */,
+ FC59A0AD0E8C8A0E00D41150 /* launchctl.1 */,
+ FC59A0DB0E8C8A6900D41150 /* launchproxy.8 */,
+ FCD713370E95D69E001B0111 /* wait4path.1 */,
+ FC59A0F80E8C8AC300D41150 /* rc.8 */,
);
- name = Mach;
+ name = man;
sourceTree = "<group>";
};
- 4B9EDCD60EAFD11000A78496 /* MIG */ = {
+ 4B2086771433CE05006A5B71 /* public */ = {
isa = PBXGroup;
children = (
- FC59A0BC0E8C8A2A00D41150 /* launchd_mig_types.defs */,
- 72FDB1BF0EA7E21C00B2AC84 /* protocol_job_forward.defs */,
- FC3629160E9348390054F1A3 /* protocol_job_reply.defs */,
- FC3627DF0E9344BF0054F1A3 /* protocol_vproc.defs */,
- FC59A0BD0E8C8A2A00D41150 /* launchd_internal.defs */,
- 4B287732111A509400C07B35 /* launchd_helper.defs */,
- );
- name = MIG;
+ FC59A0E60E8C8AA600D41150 /* launch.h */,
+ FC59A0EA0E8C8AA600D41150 /* bootstrap.h */,
+ FC59A0E20E8C8AA600D41150 /* vproc.h */,
+ );
+ name = public;
+ sourceTree = "<group>";
+ };
+ 4B2086781433CE0E006A5B71 /* private */ = {
+ isa = PBXGroup;
+ children = (
+ FC59A0E70E8C8AA600D41150 /* launch_priv.h */,
+ FC59A0EB0E8C8AA600D41150 /* bootstrap_priv.h */,
+ FC59A0E30E8C8AA600D41150 /* vproc_priv.h */,
+ FC59A0FB0E8C8ACE00D41150 /* reboot2.h */,
+ );
+ name = private;
sourceTree = "<group>";
};
- 4B9EDCD70EAFD14500A78496 /* Source */ = {
+ 4B2086791433CE49006A5B71 /* internal */ = {
isa = PBXGroup;
children = (
+ 4B1D91ED0F8BDE1A00125940 /* launchd.ops */,
+ FC59A0E80E8C8AA600D41150 /* launch_internal.h */,
+ FC59A0E40E8C8AA600D41150 /* vproc_internal.h */,
721FBEA50EA7ABC40057462B /* config.h */,
FC59A0C20E8C8A4700D41150 /* launchd.h */,
- FC59A0C40E8C8A4700D41150 /* launchd.c */,
- FC59A0B00E8C8A1F00D41150 /* launchd_unix_ipc.h */,
- FC59A0B10E8C8A1F00D41150 /* launchd_unix_ipc.c */,
- FC59A0B20E8C8A1F00D41150 /* launchd_runtime_kill.h */,
- FC59A0B30E8C8A1F00D41150 /* launchd_runtime_kill.c */,
- FC59A0B40E8C8A1F00D41150 /* launchd_runtime.h */,
- FC59A0B50E8C8A1F00D41150 /* launchd_runtime.c */,
- FC59A0B60E8C8A1F00D41150 /* launchd_core_logic.h */,
- FC59A0B70E8C8A1F00D41150 /* launchd_core_logic.c */,
- 72FDB15E0EA7D7B200B2AC84 /* launchd_ktrace.h */,
- 72FDB15D0EA7D7B200B2AC84 /* launchd_ktrace.c */,
- );
- name = Source;
+ FC59A0B00E8C8A1F00D41150 /* ipc.h */,
+ FC59A0B20E8C8A1F00D41150 /* kill2.h */,
+ FC59A0B40E8C8A1F00D41150 /* runtime.h */,
+ 72FDB15E0EA7D7B200B2AC84 /* ktrace.h */,
+ FC59A0B60E8C8A1F00D41150 /* core.h */,
+ 4B1D128A143502F000A2BDED /* log.h */,
+ );
+ name = internal;
sourceTree = "<group>";
};
- 4B9EDCD80EAFD15D00A78496 /* Documentation */ = {
+ 4B20867A1433CE7E006A5B71 /* support */ = {
isa = PBXGroup;
children = (
- FC59A0F80E8C8AC300D41150 /* rc.8 */,
- FC59A0C10E8C8A4700D41150 /* launchd.plist.5 */,
- FC59A0C30E8C8A4700D41150 /* launchd.conf.5 */,
- FC59A0C00E8C8A3A00D41150 /* launchd.8 */,
+ FC59A0AE0E8C8A0E00D41150 /* launchctl.c */,
+ FC59A0DA0E8C8A6900D41150 /* launchproxy.c */,
+ FCD713380E95D69E001B0111 /* wait4path.c */,
);
- name = Documentation;
+ name = support;
+ sourceTree = "<group>";
+ };
+ 4B9EDCD70EAFD14500A78496 /* src */ = {
+ isa = PBXGroup;
+ children = (
+ FC59A0BC0E8C8A2A00D41150 /* job_types.defs */,
+ FC3627DF0E9344BF0054F1A3 /* job.defs */,
+ FC3629160E9348390054F1A3 /* job_reply.defs */,
+ 72FDB1BF0EA7E21C00B2AC84 /* job_forward.defs */,
+ FC59A0BD0E8C8A2A00D41150 /* internal.defs */,
+ 4B287732111A509400C07B35 /* helper.defs */,
+ 4B0A3100131F24AC002DE2E5 /* types.defs */,
+ 4B0FB8E91241FE3F00383109 /* domain.defs */,
+ 4BA2F5FC1243063D00C2AADD /* init.defs */,
+ FC36291F0E9349410054F1A3 /* mach_exc.defs */,
+ FC36290C0E93475F0054F1A3 /* notify.defs */,
+ FC59A0C40E8C8A4700D41150 /* launchd.c */,
+ FC59A0B10E8C8A1F00D41150 /* ipc.c */,
+ FC59A0B30E8C8A1F00D41150 /* kill2.c */,
+ FC59A0B50E8C8A1F00D41150 /* runtime.c */,
+ 72FDB15D0EA7D7B200B2AC84 /* ktrace.c */,
+ FC59A0B70E8C8A1F00D41150 /* core.c */,
+ 4B1D1288143502DA00A2BDED /* log.c */,
+ );
+ name = src;
sourceTree = "<group>";
};
4B9EDCD90EAFD19800A78496 /* rc */ = {
name = rc;
sourceTree = "<group>";
};
+ 4BC868A6143CFC8C00B46F40 /* xcsupport */ = {
+ isa = PBXGroup;
+ children = (
+ 4BC868A8143CFD0300B46F40 /* osx_redirect_name */,
+ );
+ name = xcsupport;
+ sourceTree = "<group>";
+ };
FC36280C0E9345F60054F1A3 /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 4B949DD4143D010F008712B9 /* libCrashReporterClient.a */,
4B43EAF314101C5800E9E776 /* ServiceManagement.framework */,
4B9EDCA10EAFC77E00A78496 /* DiskArbitration.framework */,
FC36292C0E934AA40054F1A3 /* libbsm.dylib */,
FC59A03D0E8C87FD00D41150 = {
isa = PBXGroup;
children = (
- FC59A04E0E8C883300D41150 /* launchd */,
+ 4B1D12731433D77400A2BDED /* xcconfigs */,
+ 4B1D12761433D94200A2BDED /* xcscripts */,
+ 4BC868A6143CFC8C00B46F40 /* xcsupport */,
+ 4B2086771433CE05006A5B71 /* public */,
+ 4B2086781433CE0E006A5B71 /* private */,
+ 4B2086791433CE49006A5B71 /* internal */,
+ 4B9EDCD70EAFD14500A78496 /* src */,
FC59A0E10E8C8A9400D41150 /* liblaunch */,
- FC59A0A90E8C89C900D41150 /* launchctl */,
- FC59A0C80E8C8A4E00D41150 /* launchproxy */,
+ 4B20867A1433CE7E006A5B71 /* support */,
+ 4B2086731433CBEF006A5B71 /* man */,
+ 4B9EDCD90EAFD19800A78496 /* rc */,
FC59A0A00E8C899600D41150 /* SystemStarter */,
- FCD713330E95D65F001B0111 /* wait4path */,
FC36280C0E9345F60054F1A3 /* Frameworks */,
FC59A0550E8C884700D41150 /* Products */,
);
sourceTree = "<group>";
};
- FC59A04E0E8C883300D41150 /* launchd */ = {
- isa = PBXGroup;
- children = (
- 4B8D8239132C5F400081FD4E /* Mach */,
- 4B0A3102131F24B3002DE2E5 /* XPC */,
- 4B9EDCD60EAFD11000A78496 /* MIG */,
- 4B9EDCD70EAFD14500A78496 /* Source */,
- 4B9EDCD90EAFD19800A78496 /* rc */,
- 4B9EDCD80EAFD15D00A78496 /* Documentation */,
- );
- name = launchd;
- sourceTree = "<group>";
- };
FC59A0550E8C884700D41150 /* Products */ = {
isa = PBXGroup;
children = (
FC59A0A00E8C899600D41150 /* SystemStarter */ = {
isa = PBXGroup;
children = (
- FC59A0FD0E8C8ADF00D41150 /* StartupItems.c */,
- FC59A0FE0E8C8ADF00D41150 /* StartupItemContext.8 */,
- FC59A0FF0E8C8ADF00D41150 /* StartupItemContext */,
- FC59A1000E8C8ADF00D41150 /* SystemStarter.c */,
- FC59A1010E8C8ADF00D41150 /* SystemStarter.8 */,
+ FCD713200E95D5D3001B0111 /* com.apple.SystemStarter.plist */,
FC59A1020E8C8ADF00D41150 /* StartupItems.h */,
FC59A1030E8C8ADF00D41150 /* SystemStarterIPC.h */,
FC59A1040E8C8ADF00D41150 /* SystemStarter.h */,
FC59A0A40E8C89C100D41150 /* IPC.h */,
+ FC59A0FD0E8C8ADF00D41150 /* StartupItems.c */,
+ FC59A1000E8C8ADF00D41150 /* SystemStarter.c */,
FC59A0A50E8C89C100D41150 /* IPC.c */,
- FCD713200E95D5D3001B0111 /* com.apple.SystemStarter.plist */,
+ FC59A0FF0E8C8ADF00D41150 /* StartupItemContext */,
FCD713520E95D7B3001B0111 /* hostconfig */,
+ FC59A0FE0E8C8ADF00D41150 /* StartupItemContext.8 */,
+ FC59A1010E8C8ADF00D41150 /* SystemStarter.8 */,
);
name = SystemStarter;
sourceTree = "<group>";
};
- FC59A0A90E8C89C900D41150 /* launchctl */ = {
- isa = PBXGroup;
- children = (
- FC59A0AD0E8C8A0E00D41150 /* launchctl.1 */,
- FC59A0AE0E8C8A0E00D41150 /* launchctl.c */,
- );
- name = launchctl;
- sourceTree = "<group>";
- };
- FC59A0C80E8C8A4E00D41150 /* launchproxy */ = {
- isa = PBXGroup;
- children = (
- FC59A0DB0E8C8A6900D41150 /* launchproxy.8 */,
- FC59A0DA0E8C8A6900D41150 /* launchproxy.c */,
- );
- name = launchproxy;
- sourceTree = "<group>";
- };
FC59A0E10E8C8A9400D41150 /* liblaunch */ = {
isa = PBXGroup;
children = (
- 4B1D91ED0F8BDE1A00125940 /* launchd.ops */,
- FC59A0FB0E8C8ACE00D41150 /* reboot2.h */,
- FC59A0E20E8C8AA600D41150 /* vproc.h */,
- FC59A0E30E8C8AA600D41150 /* vproc_priv.h */,
- FC59A0E40E8C8AA600D41150 /* vproc_internal.h */,
FC59A0E50E8C8AA600D41150 /* libvproc.c */,
- FC59A0E60E8C8AA600D41150 /* launch.h */,
- FC59A0E70E8C8AA600D41150 /* launch_priv.h */,
- FC59A0E80E8C8AA600D41150 /* launch_internal.h */,
FC59A0E90E8C8AA600D41150 /* liblaunch.c */,
- FC59A0EA0E8C8AA600D41150 /* bootstrap.h */,
- FC59A0EB0E8C8AA600D41150 /* bootstrap_priv.h */,
FC59A0EC0E8C8AA600D41150 /* libbootstrap.c */,
);
name = liblaunch;
sourceTree = "<group>";
};
- FCD713330E95D65F001B0111 /* wait4path */ = {
- isa = PBXGroup;
- children = (
- FCD713370E95D69E001B0111 /* wait4path.1 */,
- FCD713380E95D69E001B0111 /* wait4path.c */,
- );
- name = wait4path;
- sourceTree = "<group>";
- };
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
4B1D91F40F8BDE6800125940 /* CopyFiles */,
FC59A05C0E8C885100D41150 /* Headers */,
FC59A05D0E8C885100D41150 /* Sources */,
- FC59A05E0E8C885100D41150 /* Frameworks */,
FC7B88230EA7280100542082 /* ShellScript */,
);
buildRules = (
FC59A0CB0E8C8A5C00D41150 /* Sources */,
FC59A0CC0E8C8A5C00D41150 /* Frameworks */,
FCD713120E95D554001B0111 /* CopyFiles */,
- FC7B87F20EA71A6200542082 /* ShellScript */,
);
buildRules = (
);
FCD713280E95D64D001B0111 /* Sources */,
FCD713290E95D64D001B0111 /* Frameworks */,
FCD7134D0E95D728001B0111 /* CopyFiles */,
- FC7B87EE0EA71A4900542082 /* ShellScript */,
);
buildRules = (
);
FC59A03F0E8C87FD00D41150 /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 0420;
+ LastUpgradeCheck = 0450;
};
buildConfigurationList = FC59A0420E8C87FD00D41150 /* Build configuration list for PBXProject "launchd" */;
compatibilityVersion = "Xcode 3.2";
726056200EA8088C00D65FE7 /* embedded */,
FC59A07A0E8C88BB00D41150 /* launchd_libs */,
FC59A0530E8C884700D41150 /* launchd */,
+ FC59A05F0E8C885100D41150 /* liblaunch */,
FC59A06C0E8C888A00D41150 /* launchctl */,
4B10F1B70F43BE7E00875782 /* launchd-embedded */,
4B10F1E60F43BF5C00875782 /* launchctl-embedded */,
FC59A0CD0E8C8A5C00D41150 /* launchproxy */,
FCD7132A0E95D64D001B0111 /* wait4path */,
FC59A0900E8C892300D41150 /* SystemStarter */,
- FC59A05F0E8C885100D41150 /* liblaunch */,
);
};
/* End PBXProject section */
);
runOnlyForDeploymentPostprocessing = 1;
shellPath = /bin/sh;
- shellScript = "/Developer/Makefiles/bin/compress-man-pages.pl -d \"$DSTROOT\" /usr/share/man\n/bin/mkdir -p \"$DSTROOT/private/var/db/launchd.db/com.apple.launchd\"\n/usr/sbin/chown root:wheel \"$DSTROOT/private/var/db/launchd.db\"\n/usr/sbin/chown root:wheel \"$DSTROOT/private/var/db/launchd.db/com.apple.launchd\"\n\n";
+ shellScript = "set -ex\n\n/bin/bash ${BUILD_XCSCRIPTS_DIR}/launchd-postflight.sh";
showEnvVarsInLog = 0;
};
4B10F1F00F43BF5C00875782 /* ShellScript */ = {
);
runOnlyForDeploymentPostprocessing = 1;
shellPath = /bin/sh;
- shellScript = "install -o \"$INSTALL_OWNER\" -g \"$INSTALL_GROUP\" -m 0755 -d \"$DSTROOT\"/System/Library/LaunchAgents\ninstall -o \"$INSTALL_OWNER\" -g \"$INSTALL_GROUP\" -m 0755 -d \"$DSTROOT\"/System/Library/LaunchDaemons\ninstall -o \"$INSTALL_OWNER\" -g \"$INSTALL_GROUP\" -m 0755 -d \"$DSTROOT\"/Library/LaunchAgents\ninstall -o \"$INSTALL_OWNER\" -g \"$INSTALL_GROUP\" -m 0755 -d \"$DSTROOT\"/Library/LaunchDaemons\n/Developer/Makefiles/bin/compress-man-pages.pl -d \"$DSTROOT\" /usr/share/man";
+ shellScript = "set -ex\n\n/bin/bash ${BUILD_XCSCRIPTS_DIR}/launchctl-postflight.sh";
showEnvVarsInLog = 0;
};
FC7B87B20EA7195F00542082 /* ShellScript */ = {
);
runOnlyForDeploymentPostprocessing = 1;
shellPath = /bin/sh;
- shellScript = "/Developer/Makefiles/bin/compress-man-pages.pl -d \"$DSTROOT\" /usr/share/man\n/bin/mkdir -p \"$DSTROOT/private/var/db/launchd.db/com.apple.launchd\"\n/usr/sbin/chown root:wheel \"$DSTROOT/private/var/db/launchd.db\"\n/usr/sbin/chown root:wheel \"$DSTROOT/private/var/db/launchd.db/com.apple.launchd\"\n/bin/mkdir -p \"$DSTROOT/private/etc/mach_init.d\"\n/bin/mkdir -p \"$DSTROOT/private/etc/mach_init_per_user.d\"\n/bin/mkdir -p \"$DSTROOT/private/etc/mach_init_per_login_session.d\"\n/usr/sbin/chown root:wheel \"$DSTROOT/private/etc/mach_init.d\"\n/usr/sbin/chown root:wheel \"$DSTROOT/private/etc/mach_init_per_user.d\"\n/usr/sbin/chown root:wheel \"$DSTROOT/private/etc/mach_init_per_login_session.d\"";
- showEnvVarsInLog = 0;
- };
- FC7B87EE0EA71A4900542082 /* ShellScript */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 8;
- files = (
- );
- inputPaths = (
- );
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 1;
- shellPath = /bin/sh;
- shellScript = "/Developer/Makefiles/bin/compress-man-pages.pl -d \"$DSTROOT\" /usr/share/man";
- showEnvVarsInLog = 0;
- };
- FC7B87F20EA71A6200542082 /* ShellScript */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 8;
- files = (
- );
- inputPaths = (
- );
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 1;
- shellPath = /bin/sh;
- shellScript = "/Developer/Makefiles/bin/compress-man-pages.pl -d \"$DSTROOT\" /usr/share/man";
+ shellScript = "set -ex\n\n/bin/bash ${BUILD_XCSCRIPTS_DIR}/launchd-postflight.sh";
showEnvVarsInLog = 0;
};
FC7B88230EA7280100542082 /* ShellScript */ = {
);
runOnlyForDeploymentPostprocessing = 1;
shellPath = /bin/sh;
- shellScript = "install -o \"$INSTALL_OWNER\" -g \"$INSTALL_GROUP\" -m 0755 -d \"$DSTROOT\"/usr/include/servers\nmv \"$DSTROOT\"/usr/include/bootstrap.h \"$DSTROOT\"/usr/include/servers/\nln -sf bootstrap.h \"$DSTROOT\"/usr/include/servers/bootstrap_defs.h";
+ shellScript = "set -ex\n\n/bin/bash ${BUILD_XCSCRIPTS_DIR}/liblaunch-postflight.sh";
showEnvVarsInLog = 0;
};
FCC8427E0EA7175100C01666 /* ShellScript */ = {
);
runOnlyForDeploymentPostprocessing = 1;
shellPath = /bin/sh;
- shellScript = "install -o \"$INSTALL_OWNER\" -g \"$INSTALL_GROUP\" -m 0755 -d \"$DSTROOT\"/System/Library/StartupItems\ninstall -o \"$INSTALL_OWNER\" -g \"$INSTALL_GROUP\" -m 0755 -d \"$DSTROOT\"/Library/StartupItems\n/Developer/Makefiles/bin/compress-man-pages.pl -d \"$DSTROOT\" /usr/share/man";
+ shellScript = "set -ex\n\n/bin/bash ${BUILD_XCSCRIPTS_DIR}/SystemStarter-postflight.sh";
showEnvVarsInLog = 0;
};
FCC842860EA718C400C01666 /* ShellScript */ = {
);
runOnlyForDeploymentPostprocessing = 1;
shellPath = /bin/sh;
- shellScript = "install -o \"$INSTALL_OWNER\" -g \"$INSTALL_GROUP\" -m 0755 -d \"$DSTROOT\"/System/Library/LaunchAgents\ninstall -o \"$INSTALL_OWNER\" -g \"$INSTALL_GROUP\" -m 0755 -d \"$DSTROOT\"/System/Library/LaunchDaemons\ninstall -o \"$INSTALL_OWNER\" -g \"$INSTALL_GROUP\" -m 0755 -d \"$DSTROOT\"/Library/LaunchAgents\ninstall -o \"$INSTALL_OWNER\" -g \"$INSTALL_GROUP\" -m 0755 -d \"$DSTROOT\"/Library/LaunchDaemons\n/Developer/Makefiles/bin/compress-man-pages.pl -d \"$DSTROOT\" /usr/share/man";
+ shellScript = "set -ex\n\n/bin/bash ${BUILD_XCSCRIPTS_DIR}/launchctl-postflight.sh";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 4B9A1C1F132759F700019C67 /* events.defs in Sources */,
- 4BF8727C1187A5F000CC7DB5 /* launchd_helper.defs in Sources */,
- 4B10F1B90F43BE7E00875782 /* launchd_internal.defs in Sources */,
- 4B10F1BA0F43BE7E00875782 /* protocol_vproc.defs in Sources */,
- 4B10F1BB0F43BE7E00875782 /* protocol_job_reply.defs in Sources */,
+ 60416D1D1402EE6A00C190AA /* init.defs in Sources */,
+ 60416D1F1402EE7300C190AA /* domain.defs in Sources */,
+ 4BF8727C1187A5F000CC7DB5 /* helper.defs in Sources */,
+ 4B10F1B90F43BE7E00875782 /* internal.defs in Sources */,
+ 4B10F1BA0F43BE7E00875782 /* job.defs in Sources */,
+ 4B10F1BB0F43BE7E00875782 /* job_reply.defs in Sources */,
4B10F1BC0F43BE7E00875782 /* mach_exc.defs in Sources */,
4B10F1BD0F43BE7E00875782 /* notify.defs in Sources */,
+ 4B10F1C40F43BE7E00875782 /* job_forward.defs in Sources */,
4B10F1BE0F43BE7E00875782 /* launchd.c in Sources */,
- 4B10F1BF0F43BE7E00875782 /* launchd_runtime.c in Sources */,
- 4B10F1C00F43BE7E00875782 /* launchd_runtime_kill.c in Sources */,
- 4B10F1C10F43BE7E00875782 /* launchd_core_logic.c in Sources */,
- 4B10F1C20F43BE7E00875782 /* launchd_unix_ipc.c in Sources */,
- 4B10F1C30F43BE7E00875782 /* launchd_ktrace.c in Sources */,
- 4B10F1C40F43BE7E00875782 /* protocol_job_forward.defs in Sources */,
+ 4B10F1BF0F43BE7E00875782 /* runtime.c in Sources */,
+ 4B10F1C00F43BE7E00875782 /* kill2.c in Sources */,
+ 4B10F1C10F43BE7E00875782 /* core.c in Sources */,
+ 4B10F1C20F43BE7E00875782 /* ipc.c in Sources */,
+ 4B10F1C30F43BE7E00875782 /* ktrace.c in Sources */,
+ 4B1D128C143505CD00A2BDED /* log.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 4B28781B111A61A400C07B35 /* launchd_helper.defs in Sources */,
- FC59A0BF0E8C8A2A00D41150 /* launchd_internal.defs in Sources */,
- FC3627E10E9344BF0054F1A3 /* protocol_vproc.defs in Sources */,
- FC3629170E9348390054F1A3 /* protocol_job_reply.defs in Sources */,
+ 4B28781B111A61A400C07B35 /* helper.defs in Sources */,
+ FC59A0BF0E8C8A2A00D41150 /* internal.defs in Sources */,
+ FC3627E10E9344BF0054F1A3 /* job.defs in Sources */,
+ FC3629170E9348390054F1A3 /* job_reply.defs in Sources */,
726055EC0EA7EC2400D65FE7 /* mach_exc.defs in Sources */,
FC36290D0E93475F0054F1A3 /* notify.defs in Sources */,
- 4B0FB8EA1241FE3F00383109 /* domain.defs in Sources */,
- 4B0A3103131F266E002DE2E5 /* events.defs in Sources */,
- 4BA2F5FD1243063D00C2AADD /* init.defs in Sources */,
- 72FDB1C00EA7E21C00B2AC84 /* protocol_job_forward.defs in Sources */,
+ 60416D1E1402EE7200C190AA /* domain.defs in Sources */,
+ 60416D1B1402EE6900C190AA /* init.defs in Sources */,
+ 72FDB1C00EA7E21C00B2AC84 /* job_forward.defs in Sources */,
FC59A0C50E8C8A4700D41150 /* launchd.c in Sources */,
- FC59A0BA0E8C8A1F00D41150 /* launchd_runtime.c in Sources */,
- FC59A0B90E8C8A1F00D41150 /* launchd_runtime_kill.c in Sources */,
- FC59A0BB0E8C8A1F00D41150 /* launchd_core_logic.c in Sources */,
- FC59A0B80E8C8A1F00D41150 /* launchd_unix_ipc.c in Sources */,
- 72FDB15F0EA7D7B200B2AC84 /* launchd_ktrace.c in Sources */,
+ FC59A0BA0E8C8A1F00D41150 /* runtime.c in Sources */,
+ FC59A0B90E8C8A1F00D41150 /* kill2.c in Sources */,
+ FC59A0BB0E8C8A1F00D41150 /* core.c in Sources */,
+ FC59A0B80E8C8A1F00D41150 /* ipc.c in Sources */,
+ 72FDB15F0EA7D7B200B2AC84 /* ktrace.c in Sources */,
+ 4B1D128B143505CB00A2BDED /* log.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 4B287733111A509400C07B35 /* helper.defs in Sources */,
+ FC3627E00E9344BF0054F1A3 /* job.defs in Sources */,
FC59A0F40E8C8AA600D41150 /* liblaunch.c in Sources */,
FC59A0F00E8C8AA600D41150 /* libvproc.c in Sources */,
FC59A0F70E8C8AA600D41150 /* libbootstrap.c in Sources */,
- FC3627E00E9344BF0054F1A3 /* protocol_vproc.defs in Sources */,
- 726056090EA7FCF200D65FE7 /* launchd_ktrace.c in Sources */,
- 4B287733111A509400C07B35 /* launchd_helper.defs in Sources */,
+ 726056090EA7FCF200D65FE7 /* ktrace.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* Begin XCBuildConfiguration section */
4B10F1D20F43BE7E00875782 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 4B1D12741433D79800A2BDED /* launchd.xcconfig */;
buildSettings = {
- GCC_PREPROCESSOR_DEFINITIONS = (
- __LAUNCH_DISABLE_XPC_SUPPORT__,
- XPC_BUILDING_LAUNCHD,
- );
- HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/local/include";
- INSTALL_PATH = /sbin;
- OTHER_MIGFLAGS = "-DXPC_BUILDING_LAUNCHD -I$(PROJECT_DIR)/launchd/src/ -I$(SDKROOT)/usr/local/include";
- PRODUCT_NAME = launchd;
+ SUPPORTED_PLATFORMS = iphoneos;
};
name = Release;
};
4B10F1F20F43BF5C00875782 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 4B1D12771433E5EB00A2BDED /* launchctl.xcconfig */;
buildSettings = {
INSTALL_PATH = /bin;
PRODUCT_NAME = launchctl;
+ SUPPORTED_PLATFORMS = iphoneos;
};
name = Release;
};
726056210EA8088D00D65FE7 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
- COPY_PHASE_STRIP = YES;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- PRODUCT_NAME = embedded;
- ZERO_LINK = NO;
+ PRODUCT_NAME = "launchd-embedded";
};
name = Release;
};
FC59A0410E8C87FD00D41150 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = YES;
CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)";
DEAD_CODE_STRIPPING = YES;
};
FC59A0580E8C884800D41150 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 4B1D12741433D79800A2BDED /* launchd.xcconfig */;
buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = YES;
- ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
- GCC_DYNAMIC_NO_PIC = NO;
- GCC_PREPROCESSOR_DEFINITIONS = "";
- HEADER_SEARCH_PATHS = "";
- INSTALL_PATH = /sbin;
- OTHER_CFLAGS = (
- "-D__MigTypeCheck=1",
- "-Dmig_external=__private_extern__",
- "-D_DARWIN_USE_64_BIT_INODE=1",
- );
- OTHER_MIGFLAGS = "-DXPC_BUILDING_LAUNCHD -I$(PROJECT_DIR)/launchd/src/";
- PRODUCT_NAME = launchd;
};
name = Release;
};
FC59A0620E8C885100D41150 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 4B1D12751433D7BE00A2BDED /* liblaunch.xcconfig */;
buildSettings = {
- ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
- BUILD_VARIANTS = (
- normal,
- debug,
- profile,
- );
- CRASHREPORTER_LINKER_FLAGS = "";
- "CRASHREPORTER_LINKER_FLAGS[sdk=macosx*][arch=*]" = "-lCrashReporterClient";
- "CRASHREPORTER_LINKER_FLAGS[sdk=macosx10.6][arch=*]" = "";
- DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = "$(RC_ProjectSourceVersion)";
- EXECUTABLE_PREFIX = lib;
- INSTALLHDRS_SCRIPT_PHASE = YES;
- INSTALL_PATH = /usr/lib/system;
- LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)";
- OTHER_CFLAGS = (
- "-D__MigTypeCheck=1",
- "-Dmig_external=__private_extern__",
- "-D__DARWIN_NON_CANCELABLE=1",
- "-D_DARWIN_USE_64_BIT_INODE=1",
- );
- OTHER_LDFLAGS = (
- "-Wl,-umbrella,System",
- "$(CRASHREPORTER_LINKER_FLAGS)",
- );
- PRODUCT_NAME = launch;
- PUBLIC_HEADERS_FOLDER_PATH = /usr/include;
- STRIP_INSTALLED_PRODUCT = YES;
- STRIP_STYLE = "non-global";
- VERSION_INFO_PREFIX = _;
};
name = Release;
};
FC59A0700E8C888A00D41150 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 4B1D12771433E5EB00A2BDED /* launchctl.xcconfig */;
buildSettings = {
- ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
- GCC_DYNAMIC_NO_PIC = NO;
- INSTALL_PATH = /bin;
- PRODUCT_NAME = launchctl;
};
name = Release;
};
FC59A0770E8C88AC00D41150 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
- COPY_PHASE_STRIP = YES;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- PRODUCT_NAME = default;
- ZERO_LINK = NO;
+ PRODUCT_NAME = "launchd-default";
};
name = Release;
};
FC59A07C0E8C88BC00D41150 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
- COPY_PHASE_STRIP = YES;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- PRODUCT_NAME = launchd_libs;
- ZERO_LINK = NO;
+ PRODUCT_NAME = liblaunch;
};
name = Release;
};
FC59A0940E8C892400D41150 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 4B1D127D1433E91D00A2BDED /* common.xcconfig */;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
GCC_DYNAMIC_NO_PIC = NO;
};
FC59A0D10E8C8A5C00D41150 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 4B1D127D1433E91D00A2BDED /* common.xcconfig */;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
GCC_DYNAMIC_NO_PIC = NO;
INSTALL_PATH = /usr/libexec;
PRODUCT_NAME = launchproxy;
+ SUPPORTED_PLATFORMS = "macosx iphoneos";
};
name = Release;
};
FCD7132E0E95D64E001B0111 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 4B1D127D1433E91D00A2BDED /* common.xcconfig */;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
GCC_DYNAMIC_NO_PIC = NO;
INSTALL_PATH = /bin;
PRODUCT_NAME = wait4path;
+ SUPPORTED_PLATFORMS = "macosx iphoneos";
};
name = Release;
};
+++ /dev/null
-Dave Zarzycki zarzycki@apple.com Most everything here.
-Fred Sanchez wsanchez@apple.com The original SystemStarter.
-BSD et. al. init
-CMU et. al. mach_init
+++ /dev/null
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- 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.
+++ /dev/null
-<html>
-<body>
-<h1>Getting Started With Launchd</h1>
-
-<p>Launchd is rather simple actually. Far simpler than you might think.</p>
-
-<h2>Before We Get Started</h2>
-
-<p>Launchd, in an effort to be consistent with other Apple software, uses the
-Property List file format for storing configuration information. Apple's
-Property List APIs provide for three different file formats for storing a
-property list to disk. A plain text option, a binary option, and an XML text
-option. For the remainder of this HOWTO, we will use the XML varient to show
-what a configuration file looks like.</p>
-
-<h3>The basics:</h3>
-
-<p>For the simplest of scenarios, launchd just keeps a process alive. A simple "hello
-world" example of that would be:</p>
-
-<blockquote><pre>
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>com.example.sleep</string>
- <key>ProgramArguments</key>
- <array>
- <string>sleep</string>
- <string>100</string>
- </array>
- <key>OnDemand</key>
- <false/>
-</dict>
-</plist>
-</pre></blockquote>
-
-<p>In the above example, we have three keys to our top level dictionary. The first
-is the <code>Label</code> which is what is used to uniquely identify jobs when interacting
-with launchd. The second is <code>ProgramArguments</code> which for its value, we have an
-array of strings which represent the tokenized arguments and the program to
-run. The third and final key is <code>OnDemand</code> which overrides the default value of
-true with false thereby instructing launchd to always try and keep this job
-running. That's it! A <code>Label</code>, some <code>ProgramArguments</code> and <code>OnDemand</code> set to false is all
-you need to keep a daemon alive with launchd!</p>
-
-<p>Now if you've ever written a daemon before, you've either called the
-<code>daemon()</code> function or written one yourself. With launchd, that is
-not only unnecessary, but unsupported. If you try and run a daemon you didn't
-write under launchd, you must, at the very least, find a configuration option to
-keep the daemon from daemonizing itself so that launchd can monitor it.</p>
-
-<h3>Going beyond the basics with optional keys:</h3>
-
-<p>There are many optional keys available and documented in the launchd.plist
-man page, so we'll only give a few optional, but common examples:</p>
-
-<blockquote><pre>
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>com.example.sleep</string>
- <key>ProgramArguments</key>
- <array>
- <string>sleep</string>
- <string>100</string>
- </array>
- <key>OnDemand</key>
- <false/>
- <key>UserName</key>
- <string>daemon</string>
- <key>GroupName</key>
- <string>daemon</string>
- <key>EnvironmentVariables</key>
- <dict>
- <key>FOO</key>
- <string>bar</string>
- </dict>
-</dict>
-</plist>
-</pre></blockquote>
-
-<p>In the above example, we see that the job is run as a certain user and group, and additionally has an extra environment variable set.</p>
-
-<h3>Debugging tricks:</h3>
-
-<p>The following example will enable core dumps, set standard out and error to
-go to a log file and to instruct launchd to temporarily bump up the debug level
-of launchd's loggging while acting on behave of your job (remember to adjust
-your syslog.conf accordingly):</p>
-
-<blockquote><pre>
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>com.example.sleep</string>
- <key>ProgramArguments</key>
- <array>
- <string>sleep</string>
- <string>100</string>
- </array>
- <key>OnDemand</key>
- <false/>
- <key>StandardOutPath</key>
- <string>/var/log/myjob.log</string>
- <key>StandardErrorPath</key>
- <string>/var/log/myjob.log</string>
- <key>Debug</key>
- <true/>
- <key>SoftResourceLimits</key>
- <dict>
- <key>Core</key>
- <integer>9223372036854775807</integer>
- </dict>
- <key>HardResourceLimits</key>
- <dict>
- <key>Core</key>
- <integer>9223372036854775807</integer>
- </dict>
-</dict>
-</plist>
-</pre></blockquote>
-
-<h2>But what if I don't want or expect my job to run continuously?</h2>
-
-<h3>The basics of on demand launching:</h3>
-
-<p>Launchd provides a multitude of different criteria that can be used to specify
-when a job should be started. It is important to note that launchd will only
-run one instance of your job though. Therefore, if your on demand job
-malfunctions, you are guaranteed that launchd will not spawn additional copies
-when your criteria is satisfied once more in the future.</p>
-
-<h3>Starting a job periodically:</h3>
-
-<p>Here is an example on how to have a job start every five minutes (300 seconds):</p>
-
-<blockquote><pre>
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>com.example.touchsomefile</string>
- <key>ProgramArguments</key>
- <array>
- <string>touch</string>
- <string>/tmp/helloworld</string>
- </array>
- <key>StartInterval</key>
- <integer>300</integer>
-</dict>
-</plist>
-</pre></blockquote>
-
-<p>Sometimes you want a job started on a calendar based interval. The following example will start the job on the 11th minute of the 11th hour every day (using a 24 hour clock system) on the 11th day of the month. Like the Unix cron subsystem, any missing key of the <code>StartCalendarInterval</code> dictionary is treated as a wildcard:</p>
-
-<blockquote><pre>
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>com.example.touchsomefile</string>
- <key>ProgramArguments</key>
- <array>
- <string>touch</string>
- <string>/tmp/helloworld</string>
- </array>
- <key>StartCalendarInterval</key>
- <dict>
- <key>Minute</key>
- <integer>11</integer>
- <key>Hour</key>
- <integer>11</integer>
- <key>Day</key>
- <integer>11</integer>
- </dict>
-</dict>
-</pre></blockquote>
-
-<h3>Starting a job based on file system activity:</h3>
-
-<p>The following example will start the job whenever any of the paths being watched change for whatever reason:</p>
-
-<blockquote><pre>
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>com.example.watchetchostconfig</string>
- <key>ProgramArguments</key>
- <array>
- <string>syslog</string>
- <string>-s</string>
- <string>-l</string>
- <string>notice</string>
- <string>somebody touched /etc/hostconfig</string>
- </array>
- <key>WatchPaths</key>
- <array>
- <string>/etc/hostconfig</string>
- </array>
-</dict>
-</pre></blockquote>
-
-<p>An additional file system trigger is the notion of a queue directory. Launchd will star your job whenever
-the given directories are non-empty and will keep your job running as long as those directories are not empty:</p>
-
-<blockquote><pre>
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>com.example.mailpush</string>
- <key>ProgramArguments</key>
- <array>
- <string>my_custom_mail_push_tool</string>
- </array>
- <key>QueueDirectories</key>
- <array>
- <string>/var/spool/mymailqdir</string>
- </array>
-</dict>
-</pre></blockquote>
-
-<h3>Inetd Emulation</h3>
-
-<p>Launchd will happily emulate inetd style daemon semantics:</p>
-
-<blockquote><pre>
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>com.example.telnetd</string>
- <key>ProgramArguments</key>
- <array>
- <string>/usr/libexec/telnetd</string>
- </array>
- <key>inetdCompatibility</key>
- <dict>
- <key>Wait</key>
- <false/>
- </dict>
- <key>Sockets</key>
- <dict>
- <key>Listeners</key>
- <dict>
- <key>SockServiceName</key>
- <string>telnet</string>
- <key>SockType</key>
- <string>stream</string>
- </dict>
- </dict>
-</dict>
-</pre></blockquote>
-
-<h1>TBD</h1>
-
-</body>
-</html>
+++ /dev/null
-{\rtf1\mac\ansicpg10000\cocoartf100
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;\f1\fswiss\fcharset77 Helvetica;\f2\fmodern\fcharset77 Courier;
-\f3\fswiss\fcharset77 Helvetica-Oblique;}
-{\colortbl;\red255\green255\blue255;}
-\vieww12840\viewh12820\viewkind0
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f0\b\fs24 \cf0 Logistics of Startup Items:\
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f1\b0 \cf0 \
- Startup items are directory bundles which contain, at a minimum, an executable file and a property list text file. For a startup item named "Foo", the bundle will be a directory named "Foo" containing (at a minimum) an executable "Foo" and a plist file "StartupParameters.plist".\
-\
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f0\b \cf0 Search Paths for Startup Items:\
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f1\b0 \cf0 \
- Startup items may be placed in the "Library" subdirectory of the primary file domains ("System", "Local", and "Network"). The search order is defined by routines defined in NSSystemDirectories.h: Local, then Network, then System. However, because the Network mounts have not been established at the beginning of system startup, bundles in /Network is currently not searched; this may be fixed later such that /Network is searched when ti becomes available. This search order does not define the startup order, but it does effect the handling of conflicts between bundles which provide the same services.\
-\
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f0\b \cf0 Startup Actions:\
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f1\b0 \cf0 \
- Presently, SystemStarter looks for an executable file with the name of the bundle (eg. Foo/Foo), and runs that file with a single argument.\
-\
- \'a5 A "start" argument indicates that the service(s) provided by the item are expected to start if they are configured to run. The program may opt to do nothing if the service is not configured to run.\
-\
- \'a5 A "stop" argument indicates that the service(s) provided by the item are expected to stop if it is running, regardless of whether it is configured to run.\
-\
- \'a5 A "restart" argument indicates that the service(s) provided by the item are expected to one of two things:\
- \'a5 Stop if the service is running, then start if the service is configured to run. (Same as stop followed by start.)\
- \'a5 If the service is running and not configured to run, stop the service; if the service is not running and configured to run, start the service; if the service is running and it is configured to run, reconfigure the service (eg. send the server a HUP signal).\
-\
-\13 Startup items should take no action if they receive an unknown request and should therefore take care not to ignore the argument altogether; for example, a "stop" argument should most certainly not cause the item to start a service.\
-\
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f0\b \cf0 Item Launch Ordering:\
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f1\b0 \cf0 \
- The plist file contains parameters which tell SystemStarter some information about the executable, such as what services is provides, which services are prerequisites to its use, and so on. The plist contains the following attributes:\
-\
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f2 \cf0 \{\
- Description = "blah blah";\
- Provides = ("service", ...);\
- Requires = ("service", ...);\
- Uses = ("service", ...);\
- OrderPreference = "time";\
- Messages =\
- \{\
- start = "Starting blah.";\
- stop = "Stopping blah.";\
- \}\
- \}\
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f1 \cf0 \
- Note that while the above example is writing in the old NeXT-style property list format for compactness, the new XML property lists are also handled. You may prefer using PropertyListEditor.app to editing the property list files manually.\
-\
- Provides is an array that declares the services are provided by this bundle. A typical bundle provides a single service. Two bundles may not provide the same service; should multiple bundles which provide the same service be installed on a system, the first one encountered will be run, while the others will be disabled. It is therefore undesireable to provide multiple services in a single bundle unless they are co-dependent, as the overriding of one will effectively override all services in a given bundle (see also "Search Paths for Startup Items").\
-\
- Requires and Uses comprise the primary method for sorting bundles into the startup order. Requires is an array of services, provided by other bundles, that must be successfully started before the bundle can be run. If no such service is provided by any other bundle, the requiring bundle will not run. Uses is similar to Requires in that the bundle will attempt wait for the listed services before running, but it will still launch even if no such service can be provided by another bundle.\
-\
- OrderPreference provides a hint as to the ordering used when a set of bundles are all ready to load. Bundles which have their prerequisites met (that is, all Requires services are launched and all Uses services are either launched or deemed unavailable) are prioritized in this order, based on OrderPreference:\
-\
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f2 \cf0 First\
- Early\
- None (default)\
- Late\
- Last\
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f1 \cf0 \
- Note that
-\f3\i other than the above ordering rules, there are no guarantees about the startup order of items
-\f1\i0 . That is, if multiple items are prioritized equally given the above constraints, there is no rule for which starts first. You must use the dependency mechanism to ensure the correct dependencies have been met. Note also that OrderPreference is merely a suggestion, and that SystemStarter may opt to disregard it. In particular, startup items are run parallel, and items which have dependencies met will be run without waiting for items of a lower OrderPreference to complete.\
-\
- Description is a general-use string describing the item, for use by Admin tools. The Messages property provides strings which are displayed by SystemStarter during startup and shutdown.\
-\
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f0\b \cf0 Shutdown:\
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f1\b0 \cf0 \
- The intent is to add a shutdown sequence in the future so that the computer can be brought down more cleanly and give services a change to store state before exiting. The mechanism for this is still in the design stage.\
-}
\ No newline at end of file
+++ /dev/null
-#include <mach/mach.h>
-#include <sys/types.h>
-#include <sys/event.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <syslog.h>
-#include <libgen.h>
-
-#include "launch.h"
-
-static void
-ack_mach_port(launch_data_t o, const char *name, void *context __attribute__((unused)))
-{
- mach_port_t p = launch_data_get_machport(o);
-
- mach_port_deallocate(mach_task_self(), p);
-
- syslog(LOG_NOTICE, "Ignoring Mach service: %s", name);
-}
-
-int
-main(void)
-{
- struct timespec timeout = { 60, 0 };
- struct sockaddr_storage ss;
- socklen_t slen = sizeof(ss);
- struct kevent kev;
- launch_data_t tmp, resp, msg = launch_data_new_string(LAUNCH_KEY_CHECKIN);
- size_t i;
- int kq;
-
- openlog(getprogname(), LOG_PERROR|LOG_PID|LOG_CONS, LOG_DAEMON);
-
- if (-1 == (kq = kqueue())) {
- syslog(LOG_ERR, "kqueue(): %m");
- exit(EXIT_FAILURE);
- }
-
- if ((resp = launch_msg(msg)) == NULL) {
- syslog(LOG_ERR, "launch_msg(\"" LAUNCH_KEY_CHECKIN "\") IPC failure: %m");
- exit(EXIT_FAILURE);
- }
-
- if (LAUNCH_DATA_ERRNO == launch_data_get_type(resp)) {
- errno = launch_data_get_errno(resp);
- if (errno == EACCES)
- syslog(LOG_ERR, "Check-in failed. Did you forget to set ServiceIPC == true in your plist?");
- else
- syslog(LOG_ERR, "Check-in failed: %m");
- exit(EXIT_FAILURE);
- }
-
- tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_TIMEOUT);
- if (tmp)
- timeout.tv_sec = launch_data_get_integer(tmp);
-
- tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_MACHSERVICES);
- if (tmp) {
- launch_data_dict_iterate(tmp, ack_mach_port, NULL);
- }
-
- tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_SOCKETS);
- if (NULL == tmp) {
- syslog(LOG_ERR, "No sockets found to answer requests on!");
- exit(EXIT_FAILURE);
- }
-
- if (launch_data_dict_get_count(tmp) > 1) {
- syslog(LOG_WARNING, "Some sockets will be ignored!");
- }
-
- tmp = launch_data_dict_lookup(tmp, "SampleListeners");
- if (NULL == tmp) {
- syslog(LOG_ERR, "No known sockets found to answer requests on!");
- exit(EXIT_FAILURE);
- }
-
- for (i = 0; i < launch_data_array_get_count(tmp); i++) {
- launch_data_t tmpi = launch_data_array_get_index(tmp, i);
-
- EV_SET(&kev, launch_data_get_fd(tmpi), EVFILT_READ, EV_ADD, 0, 0, NULL);
- if (kevent(kq, &kev, 1, NULL, 0, NULL) == -1) {
- syslog(LOG_DEBUG, "kevent(): %m");
- exit(EXIT_FAILURE);
- }
- }
-
- launch_data_free(msg);
- launch_data_free(resp);
-
- for (;;) {
- FILE *c;
- int r;
-
- if ((r = kevent(kq, NULL, 0, &kev, 1, &timeout)) == -1) {
- syslog(LOG_ERR, "kevent(): %m");
- exit(EXIT_FAILURE);
- } else if (r == 0) {
- exit(EXIT_SUCCESS);
- }
-
- if ((r = accept(kev.ident, (struct sockaddr *)&ss, &slen)) == -1) {
- syslog(LOG_ERR, "accept(): %m");
- continue; /* this isn't fatal */
- }
-
- c = fdopen(r, "r+");
-
- if (c) {
- fprintf(c, "hello world!\n");
- fclose(c);
- } else {
- close(r);
- }
- }
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <!-- Labels are used to uniquely identify jobs when talking to launchd -->
- <key>Label</key>
- <string>com.example.sampled</string>
- <!-- This array of strings maps one-to-one with your main()'s argv -->
- <key>ProgramArguments</key>
- <array>
- <!-- This assumes sampled is in launchd's PATH -->
- <!-- "launchctl getenv PATH" to check the PATH -->
- <string>sampled</string>
- </array>
- <key>MachServices</key>
- <dict>
- <!-- This key maps to what you pass to bootstrap_look_up() -->
- <!-- Always set the value to true. -->
- <key>com.apple.sampled.something</key>
- <true/>
- </dict>
- <key>Sockets</key>
- <dict>
- <!-- This key is completely up to you. -->
- <!-- Use it to differentiate protocols if your code supports more than one. -->
- <key>SampleListeners</key>
- <dict>
- <!-- This dictionary revolves around getaddrinfo(3) -->
- <!-- Please read up on that API, then look at launchd.plist(5). -->
- <!-- Please also make sure your code can deal with more than one FD being allocated. -->
- <key>SockServiceName</key>
- <string>12345</string>
- </dict>
- </dict>
- <!-- This launchd that we will "check in" -->
- <!-- Checking in with launchd is how we get our file descriptors. -->
- <key>ServiceIPC</key>
- <true/>
-</dict>
-</plist>
+++ /dev/null
-/**
- * IPC.c - System Starter IPC routines
- * Wilfredo Sanchez | wsanchez@opensource.apple.com
- * Kevin Van Vechten | kevinvv@uclink4.berkeley.edu
- * $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 <sys/wait.h>
-#include <mach/mach.h>
-#include <mach/message.h>
-#include <mach/mach_error.h>
-#include <CoreFoundation/CoreFoundation.h>
-#include <syslog.h>
-
-#include "bootstrap.h"
-
-#include "IPC.h"
-#include "StartupItems.h"
-#include "SystemStarter.h"
-#include "SystemStarterIPC.h"
-
-/* Structure to pass StartupContext and anItem to the termination handler. */
-typedef struct TerminationContextStorage {
- StartupContext aStartupContext;
- CFMutableDictionaryRef anItem;
-} *TerminationContext;
-
-/**
- * A CFMachPort invalidation callback that records the termination of
- * a startup item task. Stops the current run loop to give system_starter
- * another go at running items.
- **/
-static void
-startupItemTerminated(CFMachPortRef aMachPort, void *anInfo)
-{
- TerminationContext aTerminationContext = (TerminationContext) anInfo;
-
- if (aMachPort) {
- mach_port_deallocate(mach_task_self(), CFMachPortGetPort(aMachPort));
- }
- if (aTerminationContext && aTerminationContext->anItem) {
- pid_t aPID = 0;
- pid_t rPID = 0;
- int aStatus = 0;
- CFMutableDictionaryRef anItem = aTerminationContext->anItem;
- StartupContext aStartupContext = aTerminationContext->aStartupContext;
-
- /* Get the exit status */
- if (anItem) {
- aPID = StartupItemGetPID(anItem);
- if (aPID > 0)
- rPID = waitpid(aPID, &aStatus, 0);
- }
- if (aStartupContext) {
- --aStartupContext->aRunningCount;
-
- /* Record the item's status */
- if (aStartupContext->aStatusDict) {
- StartupItemExit(aStartupContext->aStatusDict, anItem, (WIFEXITED(aStatus) && WEXITSTATUS(aStatus) == 0));
- if (aStatus) {
- CF_syslog(LOG_WARNING, CFSTR("%@ (%d) did not complete successfully"), CFDictionaryGetValue(anItem, CFSTR("Description")), aPID);
- } else {
- CF_syslog(LOG_DEBUG, CFSTR("Finished %@ (%d)"), CFDictionaryGetValue(anItem, CFSTR("Description")), aPID);
- }
- }
- /*
- * If the item failed to start, then add it to the
- * failed list
- */
- if (WEXITSTATUS(aStatus) || WTERMSIG(aStatus) || WCOREDUMP(aStatus)) {
- CFDictionarySetValue(anItem, kErrorKey, kErrorReturnNonZero);
- AddItemToFailedList(aStartupContext, anItem);
- }
- /*
- * Remove the item from the waiting list regardless
- * if it was successful or it failed.
- */
- RemoveItemFromWaitingList(aStartupContext, anItem);
- }
- }
- if (aTerminationContext)
- free(aTerminationContext);
-}
-
-void
-MonitorStartupItem(StartupContext aStartupContext, CFMutableDictionaryRef anItem)
-{
- pid_t aPID = StartupItemGetPID(anItem);
- if (anItem && aPID > 0) {
- mach_port_t aPort;
- kern_return_t aResult;
- CFMachPortContext aContext;
- CFMachPortRef aMachPort;
- CFRunLoopSourceRef aSource;
- TerminationContext aTerminationContext = (TerminationContext) malloc(sizeof(struct TerminationContextStorage));
-
- aTerminationContext->aStartupContext = aStartupContext;
- aTerminationContext->anItem = anItem;
-
- aContext.version = 0;
- aContext.info = aTerminationContext;
- aContext.retain = 0;
- aContext.release = 0;
-
- if ((aResult = task_name_for_pid(mach_task_self(), aPID, &aPort)) != KERN_SUCCESS)
- goto out_bad;
-
- if (!(aMachPort = CFMachPortCreateWithPort(NULL, aPort, NULL, &aContext, NULL)))
- goto out_bad;
-
- if (!(aSource = CFMachPortCreateRunLoopSource(NULL, aMachPort, 0))) {
- CFRelease(aMachPort);
- goto out_bad;
- }
- CFMachPortSetInvalidationCallBack(aMachPort, startupItemTerminated);
- CFRunLoopAddSource(CFRunLoopGetCurrent(), aSource, kCFRunLoopCommonModes);
- CFRelease(aSource);
- CFRelease(aMachPort);
- return;
-out_bad:
- /*
- * The assumption is something failed, the task already
- * terminated.
- */
- startupItemTerminated(NULL, aTerminationContext);
- }
-}
+++ /dev/null
-/**
- * IPC.h - System Starter IPC routines
- * Wilfredo Sanchez | wsanchez@opensource.apple.com
- * Kevin Van Vechten | kevinvv@uclink4.berkeley.edu
- * $Apple$
- **
- * Copyright (c) 1999-2001 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@
- **/
-
-#ifndef _IPC_H_
-#define _IPC_H_
-
-#include "SystemStarter.h"
-
-/**
- * Monitor a startup item task. Creates a mach port and uses the
- * invalidation callback to notify system starter when the process
- * terminates.
- **/
-void MonitorStartupItem (StartupContext aStartupContext, CFMutableDictionaryRef anItem);
-
-#endif /* _IPC_H_ */
+++ /dev/null
-#!/bin/sh
-
-unset LAUNCHD_SOCKET
-
-exec launchctl bsexec / "$@"
+++ /dev/null
-.Dd July 7, 2002
-.Dt StartupItemContext 8
-.Os Darwin
-.Sh NAME
-.Nm StartupItemContext
-.\" The following lines are read in generating the apropos(man -k) database. Use only key
-.\" words here as the database is built based on the words here and in the .ND line.
-.\" Use .Nm macro to designate other names for the documented program.
-.Nd Execute a program in StartupItem context
-.Sh SYNOPSIS
-.Nm
-.Op Ar program Op Ar arguments
-.Sh DESCRIPTION
-The
-.Nm
-utility launches the specified program in StartupItem bootstrap context. Each Darwin
-and Mac OS X login creates a unique bootstrap subset context to contain login specific
-Mach port registrations with the bootstrap server. All such registrations performed
-within the context of that subset are only visible to other processes within that
-context or subsequent subsets of it. Therefore, a Mach port based service/daemon
-launched within a login context will not be visible to other such contexts.
-.Pp
-To override this, a root user can use the
-.Nm
-utility to launch the program within the same bootstrap context as all other
-StartupItems. All subsequent Mach port bootstrap registrations perfomed by the program
-will be visible system-wide.
-.Sh NOTES
-All bootstrap port lookups will also be resticted
-to the StartupItem context. The services provided on a per-login basis (clipboard,
-etc...) will not be available to the program.
-.Sh SEE ALSO
-.\" List links in ascending order by section, alphabetically within a section.
-.\" Please do not reference files that do not exist without filing a bug report
-.Xr SystemStarter 8
+++ /dev/null
-/**
- * StartupItems.c - Startup Item management routines
- * Wilfredo Sanchez | wsanchez@opensource.apple.com
- * Kevin Van Vechten | kevinvv@uclink4.berkeley.edu
- * $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 <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/sysctl.h>
-#include <sys/mman.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <limits.h>
-#include <errno.h>
-#include <string.h>
-#include <sysexits.h>
-#include <syslog.h>
-#include <CoreFoundation/CoreFoundation.h>
-#include "StartupItems.h"
-
-#define kStartupItemsPath "/StartupItems"
-#define kParametersFile "StartupParameters.plist"
-#define kDisabledFile ".disabled"
-
-#define kRunSuccess CFSTR("success")
-#define kRunFailure CFSTR("failure")
-
-static const char *argumentForAction(Action anAction)
-{
- switch (anAction) {
- case kActionStart:
- return "start";
- case kActionStop:
- return "stop";
- case kActionRestart:
- return "restart";
- default:
- return NULL;
- }
-}
-
-#define checkTypeOfValue(aKey,aTypeID) \
- { \
- CFStringRef aProperty = CFDictionaryGetValue(aConfig, aKey); \
- if (aProperty && CFGetTypeID(aProperty) != aTypeID) \
- return FALSE; \
- }
-
-static int StartupItemValidate(CFDictionaryRef aConfig)
-{
- if (aConfig && CFGetTypeID(aConfig) == CFDictionaryGetTypeID()) {
- checkTypeOfValue(kProvidesKey, CFArrayGetTypeID());
- checkTypeOfValue(kRequiresKey, CFArrayGetTypeID());
-
- return TRUE;
- }
- return FALSE;
-}
-
-/*
- * remove item from waiting list
- */
-void RemoveItemFromWaitingList(StartupContext aStartupContext, CFMutableDictionaryRef anItem)
-{
- /* Remove the item from the waiting list. */
- if (aStartupContext && anItem && aStartupContext->aWaitingList) {
- CFRange aRange = { 0, CFArrayGetCount(aStartupContext->aWaitingList) };
- CFIndex anIndex = CFArrayGetFirstIndexOfValue(aStartupContext->aWaitingList, aRange, anItem);
-
- if (anIndex >= 0) {
- CFArrayRemoveValueAtIndex(aStartupContext->aWaitingList, anIndex);
- }
- }
-}
-
-/*
- * add item to failed list, create list if it doesn't exist
- * return and fail quietly if it can't create list
- */
-void AddItemToFailedList(StartupContext aStartupContext, CFMutableDictionaryRef anItem)
-{
- if (aStartupContext && anItem) {
- /* create the failed list if it doesn't exist */
- if (!aStartupContext->aFailedList) {
- aStartupContext->aFailedList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
- }
- if (aStartupContext->aFailedList) {
- CFArrayAppendValue(aStartupContext->aFailedList, anItem);
- }
- }
-}
-
-/**
- * startupItemListCopyMatches returns an array of items which contain the string aService in the key aKey
- **/
-static CFMutableArrayRef startupItemListCopyMatches(CFArrayRef anItemList, CFStringRef aKey, CFStringRef aService)
-{
- CFMutableArrayRef aResult = NULL;
-
- if (anItemList && aKey && aService) {
- CFIndex anItemCount = CFArrayGetCount(anItemList);
- CFIndex anItemIndex = 0;
-
- aResult = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
-
- for (anItemIndex = 0; anItemIndex < anItemCount; ++anItemIndex) {
- CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(anItemList, anItemIndex);
- CFArrayRef aList = CFDictionaryGetValue(anItem, aKey);
-
- if (aList) {
- if (CFArrayContainsValue(aList, CFRangeMake(0, CFArrayGetCount(aList)), aService) &&
- !CFArrayContainsValue(aResult, CFRangeMake(0, CFArrayGetCount(aResult)), anItem)) {
- CFArrayAppendValue(aResult, anItem);
- }
- }
- }
- }
- return aResult;
-}
-
-static void SpecialCasesStartupItemHandler(CFMutableDictionaryRef aConfig)
-{
- static const CFStringRef stubitems[] = {
- CFSTR("Accounting"),
- CFSTR("System Tuning"),
- CFSTR("SecurityServer"),
- CFSTR("Portmap"),
- CFSTR("System Log"),
- CFSTR("Resolver"),
- CFSTR("LDAP"),
- CFSTR("NetInfo"),
- CFSTR("NetworkExtensions"),
- CFSTR("DirectoryServices"),
- CFSTR("Network Configuration"),
- CFSTR("mDNSResponder"),
- CFSTR("Cron"),
- CFSTR("Core Graphics"),
- CFSTR("Core Services"),
- CFSTR("Network"),
- CFSTR("TIM"),
- CFSTR("Disks"),
- CFSTR("NIS"),
- NULL
- };
- CFMutableArrayRef aList, aNewList;
- CFIndex i, aCount;
- CFStringRef ci, type = kRequiresKey;
- const CFStringRef *c;
-
- again:
- aList = (CFMutableArrayRef) CFDictionaryGetValue(aConfig, type);
- if (aList) {
- aCount = CFArrayGetCount(aList);
-
- aNewList = CFArrayCreateMutable(kCFAllocatorDefault, aCount, &kCFTypeArrayCallBacks);
-
- for (i = 0; i < aCount; i++) {
- ci = CFArrayGetValueAtIndex(aList, i);
- CF_syslog(LOG_DEBUG, CFSTR("%@: Evaluating %@"), type, ci);
- for (c = stubitems; *c; c++) {
- if (CFEqual(*c, ci))
- break;
- }
- if (*c == NULL) {
- CFArrayAppendValue(aNewList, ci);
- CF_syslog(LOG_DEBUG, CFSTR("%@: Keeping %@"), type, ci);
- }
- }
-
- CFDictionaryReplaceValue(aConfig, type, aNewList);
- CFRelease(aNewList);
- }
- if (type == kUsesKey)
- return;
- type = kUsesKey;
- goto again;
-}
-
-CFIndex StartupItemListCountServices(CFArrayRef anItemList)
-{
- CFIndex aResult = 0;
-
- if (anItemList) {
- CFIndex anItemCount = CFArrayGetCount(anItemList);
- CFIndex anItemIndex = 0;
-
- for (anItemIndex = 0; anItemIndex < anItemCount; ++anItemIndex) {
- CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
- CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
-
- if (aProvidesList)
- aResult += CFArrayGetCount(aProvidesList);
- }
- }
- return aResult;
-}
-
-bool StartupItemSecurityCheck(const char *aPath)
-{
- static struct timeval boot_time;
- struct stat aStatBuf;
- bool r = true;
-
- if (boot_time.tv_sec == 0) {
- int mib[] = { CTL_KERN, KERN_BOOTTIME };
- size_t boot_time_sz = sizeof(boot_time);
- int rv;
-
- rv = sysctl(mib, sizeof(mib) / sizeof(mib[0]), &boot_time, &boot_time_sz, NULL, 0);
-
- assert(rv != -1);
- assert(boot_time_sz == sizeof(boot_time));
- }
-
- /* should use lstatx_np() on Tiger? */
- if (lstat(aPath, &aStatBuf) == -1) {
- if (errno != ENOENT)
- syslog(LOG_ERR, "lstat(\"%s\"): %m", aPath);
- return false;
- }
- /*
- * We check the boot time because of 5409386.
- * We ignore the boot time if PPID != 1 because of 5503536.
- */
- if ((aStatBuf.st_ctimespec.tv_sec > boot_time.tv_sec) && (getppid() == 1)) {
- syslog(LOG_WARNING, "\"%s\" failed sanity check: path was created after boot up", aPath);
- return false;
- }
- if (!(S_ISREG(aStatBuf.st_mode) || S_ISDIR(aStatBuf.st_mode))) {
- syslog(LOG_WARNING, "\"%s\" failed security check: not a directory or regular file", aPath);
- r = false;
- }
- if (aStatBuf.st_mode & S_IWOTH) {
- syslog(LOG_WARNING, "\"%s\" failed security check: world writable", aPath);
- r = false;
- }
- if (aStatBuf.st_mode & S_IWGRP) {
- syslog(LOG_WARNING, "\"%s\" failed security check: group writable", aPath);
- r = false;
- }
- if (aStatBuf.st_uid != 0) {
- syslog(LOG_WARNING, "\"%s\" failed security check: not owned by UID 0", aPath);
- r = false;
- }
- if (aStatBuf.st_gid != 0) {
- syslog(LOG_WARNING, "\"%s\" failed security check: not owned by GID 0", aPath);
- r = false;
- }
- if (r == false) {
- mkdir(kFixerDir, ACCESSPERMS);
- close(open(kFixerPath, O_RDWR|O_CREAT|O_NOCTTY, DEFFILEMODE));
- }
- return r;
-}
-
-CFMutableArrayRef StartupItemListCreateWithMask(NSSearchPathDomainMask aMask)
-{
- CFMutableArrayRef anItemList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
-
- char aPath[PATH_MAX];
- CFIndex aDomainIndex = 0;
-
- NSSearchPathEnumerationState aState = NSStartSearchPathEnumeration(NSLibraryDirectory, aMask);
-
- while ((aState = NSGetNextSearchPathEnumeration(aState, aPath))) {
- DIR *aDirectory;
-
- strlcat(aPath, kStartupItemsPath, sizeof(aPath));
- ++aDomainIndex;
-
- /* 5485016
- *
- * Just in case...
- */
- mkdir(aPath, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
-
- if (!StartupItemSecurityCheck(aPath))
- continue;
-
- if ((aDirectory = opendir(aPath))) {
- struct dirent *aBundle;
-
- while ((aBundle = readdir(aDirectory))) {
- struct stat aStatBuf;
- char *aBundleName = aBundle->d_name;
- char aBundlePath[PATH_MAX];
- char aBundleExecutablePath[PATH_MAX];
- char aConfigFile[PATH_MAX];
- char aDisabledFile[PATH_MAX];
-
- if (aBundleName[0] == '.')
- continue;
-
- syslog(LOG_DEBUG, "Found item: %s", aBundleName);
-
- sprintf(aBundlePath, "%s/%s", aPath, aBundleName);
- sprintf(aBundleExecutablePath, "%s/%s", aBundlePath, aBundleName);
- sprintf(aConfigFile, "%s/%s", aBundlePath, kParametersFile);
- sprintf(aDisabledFile, "%s/%s", aBundlePath, kDisabledFile);
-
- if (lstat(aDisabledFile, &aStatBuf) == 0) {
- syslog(LOG_NOTICE, "Skipping disabled StartupItem: %s", aBundlePath);
- continue;
- }
- if (!StartupItemSecurityCheck(aBundlePath))
- continue;
- if (!StartupItemSecurityCheck(aBundleExecutablePath))
- continue;
- if (!StartupItemSecurityCheck(aConfigFile))
- continue;
-
- /* Stow away the plist data for each bundle */
- {
- int aConfigFileDescriptor;
-
- if ((aConfigFileDescriptor = open(aConfigFile, O_RDONLY|O_NOCTTY, (mode_t) 0)) != -1) {
- struct stat aConfigFileStatBuffer;
-
- if (stat(aConfigFile, &aConfigFileStatBuffer) != -1) {
- off_t aConfigFileContentsSize = aConfigFileStatBuffer.st_size;
- char *aConfigFileContentsBuffer;
-
- if ((aConfigFileContentsBuffer =
- mmap((caddr_t) 0, aConfigFileContentsSize,
- PROT_READ, MAP_FILE | MAP_PRIVATE,
- aConfigFileDescriptor, (off_t) 0)) != (caddr_t) - 1) {
- CFDataRef aConfigData = NULL;
- CFMutableDictionaryRef aConfig = NULL;
-
- aConfigData =
- CFDataCreateWithBytesNoCopy(NULL,
- (const UInt8 *)aConfigFileContentsBuffer,
- aConfigFileContentsSize,
- kCFAllocatorNull);
-
- if (aConfigData) {
- aConfig = (CFMutableDictionaryRef)
- CFPropertyListCreateFromXMLData(NULL, aConfigData,
- kCFPropertyListMutableContainers,
- NULL);
- }
- if (StartupItemValidate(aConfig)) {
- CFStringRef aBundlePathString =
- CFStringCreateWithCString(NULL, aBundlePath,
- kCFStringEncodingUTF8);
-
- CFNumberRef aDomainNumber =
- CFNumberCreate(NULL, kCFNumberCFIndexType,
- &aDomainIndex);
-
- CFDictionarySetValue(aConfig, kBundlePathKey,
- aBundlePathString);
- CFDictionarySetValue(aConfig, kDomainKey, aDomainNumber);
- CFRelease(aDomainNumber);
- SpecialCasesStartupItemHandler(aConfig);
- CFArrayAppendValue(anItemList, aConfig);
-
- CFRelease(aBundlePathString);
- } else {
- syslog(LOG_ERR, "Malformatted parameters file: %s",
- aConfigFile);
- }
-
- if (aConfig)
- CFRelease(aConfig);
- if (aConfigData)
- CFRelease(aConfigData);
-
- if (munmap(aConfigFileContentsBuffer, aConfigFileContentsSize) ==
- -1) {
- syslog(LOG_WARNING,
- "Unable to unmap parameters file %s for item %s: %m",
- aConfigFile, aBundleName);
- }
- } else {
- syslog(LOG_ERR,
- "Unable to map parameters file %s for item %s: %m",
- aConfigFile, aBundleName);
- }
- } else {
- syslog(LOG_ERR, "Unable to stat parameters file %s for item %s: %m",
- aConfigFile, aBundleName);
- }
-
- if (close(aConfigFileDescriptor) == -1) {
- syslog(LOG_ERR, "Unable to close parameters file %s for item %s: %m",
- aConfigFile, aBundleName);
- }
- } else {
- syslog(LOG_ERR, "Unable to open parameters file %s for item %s: %m", aConfigFile,
- aBundleName);
- }
- }
- }
- if (closedir(aDirectory) == -1) {
- syslog(LOG_WARNING, "Unable to directory bundle %s: %m", aPath);
- }
- } else {
- if (errno != ENOENT) {
- syslog(LOG_WARNING, "Open on directory %s failed: %m", aPath);
- return (NULL);
- }
- }
- }
-
- return anItemList;
-}
-
-CFMutableDictionaryRef StartupItemListGetProvider(CFArrayRef anItemList, CFStringRef aService)
-{
- CFMutableDictionaryRef aResult = NULL;
- CFMutableArrayRef aList = startupItemListCopyMatches(anItemList, kProvidesKey, aService);
-
- if (aList && CFArrayGetCount(aList) > 0)
- aResult = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aList, 0);
-
- if (aList) CFRelease(aList);
-
- return aResult;
-}
-
-CFArrayRef StartupItemListCreateFromRunning(CFArrayRef anItemList)
-{
- CFMutableArrayRef aResult = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
- if (aResult) {
- CFIndex anIndex, aCount = CFArrayGetCount(anItemList);
- for (anIndex = 0; anIndex < aCount; ++anIndex) {
- CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anIndex);
- if (anItem) {
- CFNumberRef aPID = CFDictionaryGetValue(anItem, kPIDKey);
- if (aPID)
- CFArrayAppendValue(aResult, anItem);
- }
- }
- }
- return aResult;
-}
-
-/*
- * Append items in anItemList to aDependents which depend on
- * aParentItem.
- * If anAction is kActionStart, dependent items are those which
- * require any service provided by aParentItem.
- * If anAction is kActionStop, dependent items are those which provide
- * any service required by aParentItem.
- */
-static void appendDependents(CFMutableArrayRef aDependents, CFArrayRef anItemList, CFDictionaryRef aParentItem, Action anAction)
-{
- CFStringRef anInnerKey, anOuterKey;
- CFArrayRef anOuterList;
-
- /* Append the parent item to the list (avoiding duplicates) */
- if (!CFArrayContainsValue(aDependents, CFRangeMake(0, CFArrayGetCount(aDependents)), aParentItem))
- CFArrayAppendValue(aDependents, aParentItem);
-
- /**
- * Recursively append any children of the parent item for kStartAction and kStopAction.
- * Do nothing for other actions.
- **/
- switch (anAction) {
- case kActionStart:
- anInnerKey = kProvidesKey;
- anOuterKey = kRequiresKey;
- break;
- case kActionStop:
- anInnerKey = kRequiresKey;
- anOuterKey = kProvidesKey;
- break;
- default:
- return;
- }
-
- anOuterList = CFDictionaryGetValue(aParentItem, anOuterKey);
-
- if (anOuterList) {
- CFIndex anOuterCount = CFArrayGetCount(anOuterList);
- CFIndex anOuterIndex;
-
- for (anOuterIndex = 0; anOuterIndex < anOuterCount; anOuterIndex++) {
- CFStringRef anOuterElement = CFArrayGetValueAtIndex(anOuterList, anOuterIndex);
- CFIndex anItemCount = CFArrayGetCount(anItemList);
- CFIndex anItemIndex;
-
- for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
- CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
- CFArrayRef anInnerList = CFDictionaryGetValue(anItem, anInnerKey);
-
- if (anInnerList &&
- CFArrayContainsValue(anInnerList, CFRangeMake(0, CFArrayGetCount(anInnerList)),
- anOuterElement)
- && !CFArrayContainsValue(aDependents, CFRangeMake(0, CFArrayGetCount(aDependents)), anItem))
- appendDependents(aDependents, anItemList, anItem, anAction);
- }
- }
- }
-}
-
-CFMutableArrayRef StartupItemListCreateDependentsList(CFMutableArrayRef anItemList, CFStringRef aService, Action anAction)
-{
- CFMutableArrayRef aDependents = NULL;
- CFMutableDictionaryRef anItem = NULL;
-
- if (anItemList && aService)
- anItem = StartupItemListGetProvider(anItemList, aService);
-
- if (anItem) {
- switch (anAction) {
- case kActionRestart:
- case kActionStart:
- case kActionStop:
- aDependents = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
-
- if (!aDependents) {
- CF_syslog(LOG_EMERG, CFSTR("Failed to allocate dependancy list for item %@"), anItem);
- return NULL;
- }
- appendDependents(aDependents, anItemList, anItem, anAction);
- break;
-
- default:
- break;
- }
- }
- return aDependents;
-}
-
-/**
- * countUnmetRequirements counts the number of items in anItemList
- * which are pending in aStatusDict.
- **/
-static int countUnmetRequirements(CFDictionaryRef aStatusDict, CFArrayRef anItemList)
-{
- int aCount = 0;
- CFIndex anItemCount = CFArrayGetCount(anItemList);
- CFIndex anItemIndex;
-
- for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
- CFStringRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
- CFStringRef aStatus = CFDictionaryGetValue(aStatusDict, anItem);
-
- if (!aStatus || !CFEqual(aStatus, kRunSuccess)) {
- CF_syslog(LOG_DEBUG, CFSTR("\tFailed requirement/uses: %@"), anItem);
- aCount++;
- }
- }
-
- return aCount;
-}
-
-/**
- * countDependantsPresent counts the number of items in aWaitingList
- * which depend on items in anItemList.
- **/
-static int countDependantsPresent(CFArrayRef aWaitingList, CFArrayRef anItemList, CFStringRef aKey)
-{
- int aCount = 0;
- CFIndex anItemCount = CFArrayGetCount(anItemList);
- CFIndex anItemIndex;
-
- for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
- CFStringRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
- CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, aKey, anItem);
-
- if (aMatchesList) {
- aCount = aCount + CFArrayGetCount(aMatchesList);
- CFRelease(aMatchesList);
- }
- }
-
- return aCount;
-}
-
-/**
- * pendingAntecedents returns TRUE if any antecedents of this item
- * are currently running, have not yet run, or none exist.
- **/
-static Boolean
-pendingAntecedents(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, CFArrayRef anAntecedentList, Action anAction)
-{
- int aPendingFlag = FALSE;
-
- CFIndex anAntecedentCount = CFArrayGetCount(anAntecedentList);
- CFIndex anAntecedentIndex;
-
- for (anAntecedentIndex = 0; anAntecedentIndex < anAntecedentCount; ++anAntecedentIndex) {
- CFStringRef anAntecedent = CFArrayGetValueAtIndex(anAntecedentList, anAntecedentIndex);
- CFStringRef aKey = (anAction == kActionStart) ? kProvidesKey : kUsesKey;
- CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, aKey, anAntecedent);
-
- if (aMatchesList) {
- CFIndex aMatchesListCount = CFArrayGetCount(aMatchesList);
- CFIndex aMatchesListIndex;
-
- for (aMatchesListIndex = 0; aMatchesListIndex < aMatchesListCount; ++aMatchesListIndex) {
- CFDictionaryRef anItem = CFArrayGetValueAtIndex(aMatchesList, aMatchesListIndex);
-
- if (!anItem ||
- !CFDictionaryGetValue(anItem, kPIDKey) || !CFDictionaryGetValue(aStatusDict, anAntecedent)) {
- aPendingFlag = TRUE;
- break;
- }
- }
-
- CFRelease(aMatchesList);
-
- if (aPendingFlag)
- break;
- }
- }
- return (aPendingFlag);
-}
-
-/**
- * checkForDuplicates returns TRUE if an item provides the same service as a
- * pending item, or an item that already succeeded.
- **/
-static Boolean checkForDuplicates(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, CFDictionaryRef anItem)
-{
- int aDuplicateFlag = FALSE;
-
- CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
- CFIndex aProvidesCount = aProvidesList ? CFArrayGetCount(aProvidesList) : 0;
- CFIndex aProvidesIndex;
-
- for (aProvidesIndex = 0; aProvidesIndex < aProvidesCount; ++aProvidesIndex) {
- CFStringRef aProvides = CFArrayGetValueAtIndex(aProvidesList, aProvidesIndex);
-
- /* If the service succeeded, return true. */
- CFStringRef aStatus = CFDictionaryGetValue(aStatusDict, aProvides);
- if (aStatus && CFEqual(aStatus, kRunSuccess)) {
- aDuplicateFlag = TRUE;
- break;
- }
- /*
- * Otherwise test if any item is currently running which
- * might provide that service.
- */
- else {
- CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, kProvidesKey, aProvides);
- if (aMatchesList) {
- CFIndex aMatchesListCount = CFArrayGetCount(aMatchesList);
- CFIndex aMatchesListIndex;
-
- for (aMatchesListIndex = 0; aMatchesListIndex < aMatchesListCount; ++aMatchesListIndex) {
- CFDictionaryRef anDupItem = CFArrayGetValueAtIndex(aMatchesList, aMatchesListIndex);
- if (anDupItem && CFDictionaryGetValue(anDupItem, kPIDKey)) {
- /*
- * Item is running, avoid
- * race condition.
- */
- aDuplicateFlag = TRUE;
- break;
- } else {
- CFNumberRef anItemDomain = CFDictionaryGetValue(anItem, kDomainKey);
- CFNumberRef anotherItemDomain = CFDictionaryGetValue(anDupItem, kDomainKey);
- /*
- * If anItem was found later
- * than aDupItem, stall
- * anItem until aDupItem
- * runs.
- */
- if (anItemDomain &&
- anotherItemDomain &&
- CFNumberCompare(anItemDomain, anotherItemDomain,
- NULL) == kCFCompareGreaterThan) {
- /*
- * Item not running,
- * but takes
- * precedence.
- */
- aDuplicateFlag = TRUE;
- break;
- }
- }
- }
-
- CFRelease(aMatchesList);
- if (aDuplicateFlag)
- break;
- }
- }
- }
- return (aDuplicateFlag);
-}
-
-CFMutableDictionaryRef StartupItemListGetNext(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, Action anAction)
-{
- CFMutableDictionaryRef aNextItem = NULL;
- CFIndex aWaitingCount = CFArrayGetCount(aWaitingList);
- int aMinFailedAntecedents = INT_MAX;
- CFIndex aWaitingIndex;
-
- switch (anAction) {
- case kActionStart:
- break;
- case kActionStop:
- break;
- case kActionRestart:
- break;
- default:
- return NULL;
- }
-
- if (!aWaitingList || !aStatusDict || aWaitingCount <= 0)
- return NULL;
-
- /**
- * Iterate through the items in aWaitingList and look for an optimally ready item.
- **/
- for (aWaitingIndex = 0; aWaitingIndex < aWaitingCount; aWaitingIndex++) {
- CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aWaitingList, aWaitingIndex);
- CFArrayRef anAntecedentList;
- int aFailedAntecedentsCount = 0; /* Number of unmet soft
- * depenancies */
- Boolean aBestPick = FALSE; /* Is this the best pick
- * so far? */
-
- /* Filter out running items. */
- if (CFDictionaryGetValue(anItem, kPIDKey))
- continue;
-
- /*
- * Filter out dupilicate services; if someone has
- * provided what we provide, we don't run.
- */
- if (checkForDuplicates(aWaitingList, aStatusDict, anItem)) {
- CF_syslog(LOG_DEBUG, CFSTR("Skipping %@ because of duplicate service."),
- CFDictionaryGetValue(anItem, kDescriptionKey));
- continue;
- }
- /*
- * Dependencies don't matter when restarting an item;
- * stop here.
- */
- if (anAction == kActionRestart) {
- aNextItem = anItem;
- break;
- }
- anAntecedentList = CFDictionaryGetValue(anItem, ((anAction == kActionStart) ? kRequiresKey : kProvidesKey));
-
- CF_syslog(LOG_DEBUG, CFSTR("Checking %@"), CFDictionaryGetValue(anItem, kDescriptionKey));
-
- if (anAntecedentList)
- CF_syslog(LOG_DEBUG, CFSTR("Antecedents: %@"), anAntecedentList);
- else
- syslog(LOG_DEBUG, "No antecedents");
-
- /**
- * Filter out the items which have unsatisfied antecedents.
- **/
- if (anAntecedentList &&
- ((anAction == kActionStart) ?
- countUnmetRequirements(aStatusDict, anAntecedentList) :
- countDependantsPresent(aWaitingList, anAntecedentList, kRequiresKey)))
- continue;
-
- /**
- * anItem has all hard dependancies met; check for soft dependancies.
- * We'll favor the item with the fewest unmet soft dependancies here.
- **/
- anAntecedentList = CFDictionaryGetValue(anItem, ((anAction == kActionStart) ? kUsesKey : kProvidesKey));
-
- if (anAntecedentList)
- CF_syslog(LOG_DEBUG, CFSTR("Soft dependancies: %@"), anAntecedentList);
- else
- syslog(LOG_DEBUG, "No soft dependancies");
-
- if (anAntecedentList) {
- aFailedAntecedentsCount =
- ((anAction == kActionStart) ?
- countUnmetRequirements(aStatusDict, anAntecedentList) :
- countDependantsPresent(aWaitingList, anAntecedentList, kUsesKey));
- } else {
- if (aMinFailedAntecedents > 0)
- aBestPick = TRUE;
- }
-
- /*
- * anItem has unmet dependencies that will
- * likely be met in the future, so delay it
- */
- if (aFailedAntecedentsCount > 0 && pendingAntecedents(aWaitingList, aStatusDict, anAntecedentList, anAction)) {
- continue;
- }
- if (aFailedAntecedentsCount > 0)
- syslog(LOG_DEBUG, "Total: %d", aFailedAntecedentsCount);
-
- if (aFailedAntecedentsCount > aMinFailedAntecedents)
- continue; /* Another item already won out */
-
- if (aFailedAntecedentsCount < aMinFailedAntecedents)
- aBestPick = TRUE;
-
- if (!aBestPick)
- continue;
-
- /*
- * anItem has less unmet
- * dependancies than any
- * other item so far, so it
- * wins.
- */
- syslog(LOG_DEBUG, "Best pick so far, based on failed dependancies (%d->%d)",
- aMinFailedAntecedents, aFailedAntecedentsCount);
-
- /*
- * We have a winner! Update success
- * parameters to match anItem.
- */
- aMinFailedAntecedents = aFailedAntecedentsCount;
- aNextItem = anItem;
-
- } /* End of waiting list loop. */
-
- return aNextItem;
-}
-
-CFStringRef StartupItemCreateDescription(CFMutableDictionaryRef anItem)
-{
- CFStringRef aString = NULL;
-
- if (anItem)
- aString = CFDictionaryGetValue(anItem, kDescriptionKey);
- if (aString)
- CFRetain(aString);
- return aString;
-}
-
-pid_t StartupItemGetPID(CFDictionaryRef anItem)
-{
- CFIndex anItemPID = 0;
- CFNumberRef aPIDNumber = anItem ? CFDictionaryGetValue(anItem, kPIDKey) : NULL;
- if (aPIDNumber && CFNumberGetValue(aPIDNumber, kCFNumberCFIndexType, &anItemPID))
- return (pid_t) anItemPID;
- else
- return 0;
-}
-
-CFMutableDictionaryRef StartupItemWithPID(CFArrayRef anItemList, pid_t aPID)
-{
- CFIndex anItemCount = CFArrayGetCount(anItemList);
- CFIndex anItemIndex;
-
- for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
- CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(anItemList, anItemIndex);
- CFNumberRef aPIDNumber = CFDictionaryGetValue(anItem, kPIDKey);
- CFIndex anItemPID;
-
- if (aPIDNumber) {
- CFNumberGetValue(aPIDNumber, kCFNumberCFIndexType, &anItemPID);
-
- if ((pid_t) anItemPID == aPID)
- return anItem;
- }
- }
-
- return NULL;
-}
-
-int StartupItemRun(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Action anAction)
-{
- int anError = -1;
- CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
- static const CFStringRef stubitems[] = {
- CFSTR("BootROMUpdater"), /* 3893064 */
- CFSTR("FCUUpdater"), /* 3893064 */
- CFSTR("AutoProtect Daemon"), /* 3965785 */
- CFSTR("Check For Missed Tasks"), /* 3965785 */
- CFSTR("Privacy"), /* 3933484 */
- CFSTR("Firmware Update Checking"), /* 4001504 */
-
- CFSTR("M-Audio FireWire Audio Support"), /* 3931757 */
- CFSTR("help for M-Audio Delta Family"), /* 3931757 */
- CFSTR("help for M-Audio Devices"), /* 3931757 */
- CFSTR("help for M-Audio Revo 5.1"), /* 3931757 */
- CFSTR("M-Audio USB Duo Configuration Service"), /* 3931757 */
- CFSTR("firmware loader for M-Audio devices"), /* 3931757 */
- CFSTR("M-Audio MobilePre USB Configuration Service"), /* 3931757 */
- CFSTR("M-Audio OmniStudio USB Configuration Service"), /* 3931757 */
- CFSTR("M-Audio Transit USB Configuration Service"), /* 3931757 */
- CFSTR("M-Audio Audiophile USB Configuration Service"), /* 3931757 */
- NULL
- };
- const CFStringRef *c;
-
- if (aProvidesList && anAction == kActionStop) {
- CFIndex aProvidesCount = CFArrayGetCount(aProvidesList);
- for (c = stubitems; *c; c++) {
- if (CFArrayContainsValue(aProvidesList, CFRangeMake(0, aProvidesCount), *c)) {
- CFIndex aPID = -1;
- CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
-
- CFDictionarySetValue(anItem, kPIDKey, aProcessNumber);
- CFRelease(aProcessNumber);
-
- StartupItemExit(aStatusDict, anItem, TRUE);
- return -1;
- }
- }
- }
-
- if (anAction == kActionNone) {
- StartupItemExit(aStatusDict, anItem, TRUE);
- anError = 0;
- } else {
- CFStringRef aBundlePathString = CFDictionaryGetValue(anItem, kBundlePathKey);
- char aBundlePath[PATH_MAX];
- char anExecutable[PATH_MAX];
- char *tmp;
-
- if (!CFStringGetCString(aBundlePathString, aBundlePath, sizeof(aBundlePath), kCFStringEncodingUTF8)) {
- CF_syslog(LOG_EMERG, CFSTR("Internal error while running item %@"), aBundlePathString);
- return (anError);
- }
- /* Compute path to excecutable */
- tmp = rindex(aBundlePath, '/');
- snprintf(anExecutable, sizeof(anExecutable), "%s%s", aBundlePath, tmp);
-
- /**
- * Run the bundle
- **/
-
- if (access(anExecutable, X_OK)) {
- /*
- * Add PID key so that this item is marked as having
- * been run.
- */
- CFIndex aPID = -1;
- CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
-
- CFDictionarySetValue(anItem, kPIDKey, aProcessNumber);
- CFRelease(aProcessNumber);
-
- CFDictionarySetValue(anItem, kErrorKey, kErrorPermissions);
- StartupItemExit(aStatusDict, anItem, FALSE);
- syslog(LOG_ERR, "No executable file %s", anExecutable);
- } else {
- pid_t aProccessID = fork();
-
- switch (aProccessID) {
- case -1: /* SystemStarter (fork failed) */
- CFDictionarySetValue(anItem, kErrorKey, kErrorFork);
- StartupItemExit(aStatusDict, anItem, FALSE);
-
- CF_syslog(LOG_ERR, CFSTR("Failed to fork for item %@: %s"), aBundlePathString, strerror(errno));
-
- break;
-
- default: /* SystemStarter (fork succeeded) */
- {
- CFIndex aPID = (CFIndex) aProccessID;
- CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
-
- CFDictionarySetValue(anItem, kPIDKey, aProcessNumber);
- CFRelease(aProcessNumber);
-
- syslog(LOG_DEBUG, "Running command (%d): %s %s",
- aProccessID, anExecutable, argumentForAction(anAction));
- anError = 0;
- }
- break;
-
- case 0: /* Child */
- {
- if (setsid() == -1)
- syslog(LOG_WARNING, "Unable to create session for item %s: %m", anExecutable);
-
- anError = execl(anExecutable, anExecutable, argumentForAction(anAction), NULL);
-
- /* We shouldn't get here. */
-
- syslog(LOG_ERR, "execl(\"%s\"): %m", anExecutable);
-
- exit(anError);
- }
- }
- }
- }
-
- return (anError);
-}
-
-void
-StartupItemSetStatus(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, CFStringRef aServiceName,
- Boolean aSuccess, Boolean aReplaceFlag)
-{
- void (*anAction) (CFMutableDictionaryRef, const void *, const void *) = aReplaceFlag ?
- CFDictionarySetValue : CFDictionaryAddValue;
-
- if (aStatusDict && anItem) {
- CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
- if (aProvidesList) {
- CFIndex aProvidesCount = CFArrayGetCount(aProvidesList);
- CFIndex aProvidesIndex;
-
- /*
- * If a service name was specified, and it is valid,
- * use only it.
- */
- if (aServiceName && CFArrayContainsValue(aProvidesList, CFRangeMake(0, aProvidesCount), aServiceName)) {
- aProvidesList = CFArrayCreate(NULL, (const void **)&aServiceName, 1, &kCFTypeArrayCallBacks);
- aProvidesCount = 1;
- } else {
- CFRetain(aProvidesList);
- }
-
- for (aProvidesIndex = 0; aProvidesIndex < aProvidesCount; aProvidesIndex++) {
- CFStringRef aService = CFArrayGetValueAtIndex(aProvidesList, aProvidesIndex);
-
- if (aSuccess)
- anAction(aStatusDict, aService, kRunSuccess);
- else
- anAction(aStatusDict, aService, kRunFailure);
- }
-
- CFRelease(aProvidesList);
- }
- }
-}
-
-void StartupItemExit(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Boolean aSuccess)
-{
- StartupItemSetStatus(aStatusDict, anItem, NULL, aSuccess, FALSE);
-}
+++ /dev/null
-/**
- * StartupItems.h - Startup Item management routines
- * Wilfredo Sanchez | wsanchez@opensource.apple.com
- * Kevin Van Vechten | kevinvv@uclink4.berkeley.edu
- * $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@
- **/
-
-#ifndef _StartupItems_H_
-#define _StartupItems_H_
-
-#include <NSSystemDirectories.h>
-
-#include <CoreFoundation/CFArray.h>
-#include <CoreFoundation/CFDictionary.h>
-
-#include "SystemStarter.h"
-
-#define kProvidesKey CFSTR("Provides")
-#define kRequiresKey CFSTR("Requires")
-#define kDescriptionKey CFSTR("Description")
-#define kUsesKey CFSTR("Uses")
-#define kErrorKey CFSTR("Error")
-#define kBundlePathKey CFSTR("PathToBundle")
-#define kPIDKey CFSTR("ProcessID")
-#define kDomainKey CFSTR("Domain")
-
-
-#define kErrorPermissions CFSTR("incorrect permissions")
-#define kErrorInternal CFSTR("SystemStarter internal error")
-#define kErrorReturnNonZero CFSTR("execution of Startup script failed")
-#define kErrorFork CFSTR("could not fork() StartupItem")
-
-
-/*
- * Find all available startup items in NSDomains specified by aMask.
- */
-CFMutableArrayRef StartupItemListCreateWithMask (NSSearchPathDomainMask aMask);
-
-/*
- * Returns the item responsible for providing aService.
- */
-CFMutableDictionaryRef StartupItemListGetProvider (CFArrayRef anItemList, CFStringRef aService);
-
-/*
- * Creates a list of items in anItemList which depend on anItem, given anAction.
- */
-CFMutableArrayRef StartupItemListCreateDependentsList (CFMutableArrayRef anItemList,
- CFStringRef aService ,
- Action anAction );
-
-/*
- * Given aWaitingList of startup items, and aStatusDict describing the current
- * startup state, returns the next startup item to run, if any. Returns nil if
- * none is available.
- * Note that this is not necessarily deterministic; if more than one startup
- * item is ready to run, which item gets returned is not specified. An item is
- * not ready to run if the specified dependencies are not satisfied yet.
- */
-CFMutableDictionaryRef StartupItemListGetNext (CFArrayRef aWaitingList,
- CFDictionaryRef aStatusDict ,
- Action anAction );
-
-CFMutableDictionaryRef StartupItemWithPID (CFArrayRef anItemList, pid_t aPID);
-pid_t StartupItemGetPID(CFDictionaryRef anItem);
-
-CFStringRef StartupItemCreateDescription(CFMutableDictionaryRef anItem);
-
-/*
- * Returns a list of currently executing startup items.
- */
-CFArrayRef StartupItemListCreateFromRunning(CFArrayRef anItemList);
-
-/*
- * Returns the total number of "Provides" entries of all loaded items.
- */
-CFIndex StartupItemListCountServices (CFArrayRef anItemList);
-
-
-/*
- * Utility functions
- */
-void RemoveItemFromWaitingList(StartupContext aStartupContext, CFMutableDictionaryRef anItem);
-void AddItemToFailedList(StartupContext aStartupContext, CFMutableDictionaryRef anItem);
-
-/*
- * Run the startup item.
- */
-int StartupItemRun (CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Action anAction);
-void StartupItemExit (CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Boolean aSuccess);
-void StartupItemSetStatus(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, CFStringRef aServiceName, Boolean aSuccess, Boolean aReplaceFlag);
-
-/*
- * Check whether file was created before boot and has proper permissions to run.
- */
-bool StartupItemSecurityCheck(const char *aPath);
-
-#endif /* _StartupItems_H_ */
+++ /dev/null
-.Dd April 12, 2002
-.Dt SystemStarter 8
-.Os Darwin
-.Sh NAME
-.Nm SystemStarter
-.\" The following lines are read in generating the apropos(man -k) database. Use only key
-.\" words here as the database is built based on the words here and in the .ND line.
-.\" Use .Nm macro to designate other names for the documented program.
-.Nd Start, stop, and restart system services
-.Sh SYNOPSIS
-.Nm
-.Op Fl gvxdDqn
-.Op Ar action Op Ar service
-.Sh DESCRIPTION
-The
-.Nm
-utility is deprecated. System services should instead be described by a
-.Xr launchd.plist 5 .
-See
-.Xr launchd 8
-for more details.
-The
-.Nm launchd
-utility is available on Mac OS X 10.4 and later.
-.Pp
-In earlier versions of Mac OS X, the
-.Nm
-utility is used to start, stop, and restart the system services which
-are described in the
-.Pa /Library/StartupItems/
-and
-.Pa /System/Library/StartupItems/
-paths.
-.Pp
-The optional
-.Ar action
-argument specifies which action
-.Nm
-performs on the startup items. The optional
-.Ar service
-argument specifies which startup items to perform the action on. If no
-.Ar service
-is specified, all startup items will be acted on; otherwise, only the item providing the
-.Ar service ,
-any items it requires, or any items that depend on it will be acted on.
-.Pp
-During boot
-.Nm
-is invoked by
-.Xr launchd 8
-and is responsible for
-starting all startup items in an order that satisfies each item's
-requirements.
-.Sh ACTIONS
-.Bl -tag -width -indent
-.It Nm start
-start all items, or start the item that provides the specified
-.Ar service
-and all items providing services it requires.
-.It Nm stop
-stop all items, or stop the item that provides the specified
-.Ar service
-and all items that depend on it.
-.It Nm restart
-restart all items, or restart the item providing the specified
-.Ar service .
-.El
-.Sh OPTIONS
-.Bl -tag -width -indent
-.It Fl g
-(ignored)
-.It Fl v
-verbose (text mode) startup
-.It Fl x
-(ignored)
-.It Fl r
-(ignored)
-.It Fl d
-print debugging output
-.It Fl D
-print debugging output and dependencies
-.It Fl q
-be quiet (disable debugging output)
-.It Fl n
-don't actually perform action on items (no-run mode)
-.El
-.Sh NOTES
-Unless an explicit call to
-.Nm ConsoleMessage
-is made,
-.Nm
-examines the exit status of the startup item scripts to determine the success or failure of the services provided by that script.
-.Pp
-.Sh FILES
-.Bl -tag -width -/System/Library/StartupItems -compact
-.It Pa /Library/StartupItems/
-User-installed startup items.
-.It Pa /System/Library/StartupItems/
-System-provided startup items.
-.El
-.Sh SEE ALSO
-.\" List links in ascending order by section, alphabetically within a section.
-.\" Please do not reference files that do not exist without filing a bug report
-.Xr ConsoleMessage 8 ,
-.Xr launchd 8 ,
-.Xr launchd.plist 5 ,
-.Xr rc 8
-.\" .Sh BUGS \" Document known, unremedied bugs
-.Sh HISTORY
-The
-.Nm
-utility appeared in Darwin 1.0 and
-was extended in Darwin 6.0 to support partial startup and interprocess communication.
-.Nm
-was deprecated by
-.Xr launchd 8
-in Darwin 8.0.
+++ /dev/null
-/**
- * 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 <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;
-
- /*
- * 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);
- autodiskmount(); /* wait for Disk Arbitration to report idle */
-
- 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);
-}
-
-
-/**
- * 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 = 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;
- }
-}
-
-/*
- * print out any error messages to the log regarding non starting StartupItems
- */
-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);
-
- /**
- * 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, anAction);
-
- /* 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, "%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 {
- /* must have died due to signal */
- 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);
- }
-}
+++ /dev/null
-/**
- * SystemStarter.h - System Starter driver
- * 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@
- **/
-
-#ifndef _SYSTEM_STARTER_H_
-#define _SYSTEM_STARTER_H_
-
-/* Structure to pass common objects from system_starter to the IPC handlers */
-typedef struct StartupContextStorage {
- CFMutableArrayRef aWaitingList;
- CFMutableArrayRef aFailedList;
- CFMutableDictionaryRef aStatusDict;
- int aServicesCount;
- int aRunningCount;
-} *StartupContext;
-
-#define kFixerDir "/var/db/fixer"
-#define kFixerPath "/var/db/fixer/StartupItems"
-
-/* Action types */
-typedef enum {
- kActionNone = 0,
- kActionStart,
- kActionStop,
- kActionRestart
-} Action;
-
-void CF_syslog(int level, CFStringRef message, ...);
-extern bool gVerboseFlag;
-
-#endif /* _SYSTEM_STARTER_H_ */
+++ /dev/null
-/**
- * SystemStarterIPC.h - System Starter IPC definitions
- * Wilfredo Sanchez | wsanchez@opensource.apple.com
- * Kevin Van Vechten | kevinvv@uclink4.berkeley.edu
- **
- * Copyright (c) 1999-2001 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@
- **
- * Definitions used for IPC communications with SystemStarter.
- * SystemStarter listens on a CFMessagePort with the name defined by
- * kSystemStarterMessagePort. The messageID of each message should
- * be set to the kIPCProtocolVersion constant. The contents of each
- * message should be an XML plist containing a dictionary using
- * the keys defined in this file.
- **/
-
-#ifndef _SYSTEM_STARTER_IPC_H
-#define _SYSTEM_STARTER_IPC_H
-
-#include <CoreFoundation/CFString.h>
-#include <mach/message.h>
-
-/* Compatible with inline CFMessagePort messages. */
-typedef struct SystemStarterIPCMessage {
- mach_msg_header_t aHeader;
- mach_msg_body_t aBody;
- SInt32 aProtocol;
- SInt32 aByteLength;
- /* Data follows. */
-} SystemStarterIPCMessage;
-
-/* Name of the CFMessagePort SystemStarter listens on. */
-#define kSystemStarterMessagePort "com.apple.SystemStarter"
-
-/* kIPCProtocolVersion should be passed as the messageID of the CFMessage. */
-#define kIPCProtocolVersion 0
-
-/* kIPCTypeKey should be provided for all messages. */
-#define kIPCMessageKey CFSTR("Message")
-
-/* Messages are one of the following types: */
-#define kIPCConsoleMessage CFSTR("ConsoleMessage")
-#define kIPCStatusMessage CFSTR("StatusMessage")
-#define kIPCQueryMessage CFSTR("QueryMessage")
-#define kIPCLoadDisplayBundleMessage CFSTR("LoadDisplayBundle")
-#define kIPCUnloadDisplayBundleMessage CFSTR("UnloadDisplayBundle")
-
-/* kIPCServiceNameKey identifies a startup item by one of the services it provides. */
-#define kIPCServiceNameKey CFSTR("ServiceName")
-
-/* kIPCProcessIDKey identifies a running startup item by its process id. */
-#define kIPCProcessIDKey CFSTR("ProcessID")
-
-/* kIPCConsoleMessageKey contains the non-localized string to
- * display for messages of type kIPCTypeConsoleMessage.
- */
-#define kIPCConsoleMessageKey CFSTR("ConsoleMessage")
-
-/* kIPCStatus key contains a boolean value. True for success, false for failure. */
-#define kIPCStatusKey CFSTR("StatusKey")
-
-/* kIPCDisplayBundlePathKey contains a string path to the display bundle
- SystemStarter should attempt to load. */
-#define kIPCDisplayBundlePathKey CFSTR("DisplayBundlePath")
-
-/* kIPCConfigNamegKey contains the name of a config setting to query */
-#define kIPCConfigSettingKey CFSTR("ConfigSetting")
-
-/* Some config settings */
-#define kIPCConfigSettingVerboseFlag CFSTR("VerboseFlag")
-#define kIPCConfigSettingNetworkUp CFSTR("NetworkUp")
-
-#endif /* _SYSTEM_STARTER_IPC_H */
+++ /dev/null
-#ifndef _BOOTSTRAP_H_
-#define _BOOTSTRAP_H_
-/*
- * Copyright (c) 1999-2005 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@
- */
-
-/*
- * bootstrap -- fundamental service initiator and port server
- * Mike DeMoney, NeXT, Inc.
- * Copyright, 1990. All rights reserved.
- */
-
-/*
- * Interface: Bootstrap server
- *
- * The bootstrap server is the first user-mode task initiated by the Mach
- * kernel at system boot time. The bootstrap server provides two services,
- * it initiates other system tasks, and manages a table of name-port bindings
- * for fundamental system services (e.g. lookupd, Window Manager, etc...).
- *
- * Name-port bindings can be established with the bootstrap server by either
- * of two mechanisms:
- *
- * 1. The binding can be indicated, in advance of the service that backs it
- * being available, via a "service create" request. In this case, bootstrap
- * will immediately create a port and bind the indicated name with that port.
- * At a later time, a service may "checkin" for the name-port
- * binding and will be returned receive rights for the bound port. Lookup's
- * on bindings created by this mechanism will return send rights to the port,
- * even if no service has "checked-in". In this case, requests sent to the
- * bound port will be queued until a server has checked-in and can satisfy the
- * request.
- *
- * 2. Bindings can be established dynamically via a "register" request. In
- * this case, the register request provides bootstrap with a name and send
- * rights for a port. Bootstrap will provide send rights for the bound port
- * to any requestor via the lookup request.
- *
- * Bootstrap provides its service port to descendant tasks via the Mach
- * "bootstrap" special task port. All direct descendants of bootstrap receive
- * a "privileged" bootstrap service port. System services that initiate
- * untrusted tasks should replace the Mach bootstrap task special port with
- * a subset bootstrap port to prevent them from infecting the namespace.
- *
- * The bootstrap server creates a "backup" port for each service that it
- * creates. This is used to detect when a checked out service is no longer
- * being served. The bootstrap server regains all rights to the port and
- * it is marked available for check-out again. This allows crashed servers to
- * resume service to previous clients. Lookup's on this named port will
- * continue to be serviced by bootstrap while holding receive rights for the
- * bound port. A client may detect that the service is inactive via the
- * bootstrap status request. If an inactive service re-registers rather
- * than "checking-in" the original bound port is destroyed.
- *
- * The status of a named service may be obtained via the "status" request.
- * A service is "active" if a name-port binding exists and receive rights
- * to the bound port are held by a task other than bootstrap.
- *
- * The bootstrap server may also (re)start server processes associated with
- * with a set of services. The definition of the server process is done
- * through the "create server" request. The server will be launched in the
- * same bootstrap context in which it was registered.
- */
-#include <AvailabilityMacros.h>
-#include <mach/std_types.h>
-#include <mach/message.h>
-#include <sys/types.h>
-#include <sys/cdefs.h>
-#include <stdbool.h>
-
-__BEGIN_DECLS
-
-#pragma GCC visibility push(default)
-
-#define BOOTSTRAP_MAX_NAME_LEN 128
-#define BOOTSTRAP_MAX_CMD_LEN 512
-
-typedef char name_t[BOOTSTRAP_MAX_NAME_LEN];
-typedef char cmd_t[BOOTSTRAP_MAX_CMD_LEN];
-typedef name_t *name_array_t;
-typedef int bootstrap_status_t;
-typedef bootstrap_status_t *bootstrap_status_array_t;
-typedef unsigned int bootstrap_property_t;
-typedef bootstrap_property_t * bootstrap_property_array_t;
-
-typedef boolean_t *bool_array_t;
-
-#define BOOTSTRAP_MAX_LOOKUP_COUNT 20
-
-#define BOOTSTRAP_SUCCESS 0
-#define BOOTSTRAP_NOT_PRIVILEGED 1100
-#define BOOTSTRAP_NAME_IN_USE 1101
-#define BOOTSTRAP_UNKNOWN_SERVICE 1102
-#define BOOTSTRAP_SERVICE_ACTIVE 1103
-#define BOOTSTRAP_BAD_COUNT 1104
-#define BOOTSTRAP_NO_MEMORY 1105
-#define BOOTSTRAP_NO_CHILDREN 1106
-
-#define BOOTSTRAP_STATUS_INACTIVE 0
-#define BOOTSTRAP_STATUS_ACTIVE 1
-#define BOOTSTRAP_STATUS_ON_DEMAND 2
-
-/*
- * After main() starts, it is safe to assume that this variable is always set.
- */
-extern mach_port_t bootstrap_port;
-
-/*
- * bootstrap_create_server()
- *
- * Declares a server that mach_init will re-spawn within the specified
- * bootstrap context. The server is considered already "active"
- * (i.e. will not be re-spawned) until the returned server_port is
- * deallocated.
- *
- * In the meantime, services can be declared against the server,
- * by using the server_port as the privileged bootstrap target of
- * subsequent bootstrap_create_service() calls.
- *
- * When mach_init re-spawns the server, its task bootstrap port
- * is set to the privileged sever_port. Through this special
- * bootstrap port, it can access all of parent bootstrap's context
- * (and all services are created in the parent's namespace). But
- * all additional service declarations (and declaration removals)
- * will be associated with this particular server.
- *
- * Only a holder of the server_port privilege bootstrap port can
- * check in or register over those services.
- *
- * When all services associated with a server are deleted, and the server
- * exits, it will automatically be deleted itself.
- *
- * If the server is declared "on_demand," then a non-running server
- * will be re-launched on first use of one of the service ports
- * registered against it. Otherwise, it will be re-launched
- * immediately upon exiting (whether any client is actively using
- * any of the service ports or not).
- *
- * Errors: Returns appropriate kernel errors on rpc failure.
- * Returns BOOTSTRAP_NOT_PRIVILEGED, bootstrap or uid invalid.
- */
-kern_return_t bootstrap_create_server(
- mach_port_t bp,
- cmd_t server_cmd,
- uid_t server_uid,
- boolean_t on_demand,
- mach_port_t *server_port);
-
-/*
- * bootstrap_subset()
- *
- * Returns a new port to use as a bootstrap port. This port behaves
- * exactly like the previous bootstrap_port, except that ports dynamically
- * registered via bootstrap_register() are available only to users of this
- * specific subset_port. Lookups on the subset_port will return ports
- * registered with this port specifically, and ports registered with
- * ancestors of this subset_port. Duplications of services already
- * registered with an ancestor port may be registered with the subset port
- * are allowed. Services already advertised may then be effectively removed
- * by registering PORT_NULL for the service.
- * When it is detected that the requestor_port is destroyed the subset
- * port and all services advertized by it are destroyed as well.
- *
- * Errors: Returns appropriate kernel errors on rpc failure.
- */
-kern_return_t bootstrap_subset(
- mach_port_t bp,
- mach_port_t requestor_port,
- mach_port_t *subset_port);
-
-/*
- * bootstrap_unprivileged()
- *
- * Given a bootstrap port, return its unprivileged equivalent. If
- * the port is already unprivileged, another reference to the same
- * port is returned.
- *
- * This is most often used by servers, which are launched with their
- * bootstrap port set to the privileged port for the server, to get
- * an unprivileged version of the same port for use by its unprivileged
- * children (or any offspring that it does not want to count as part
- * of the "server" for mach_init registration and re-launch purposes).
- *
- * Native launchd jobs are always started with an unprivileged port.
- */
-kern_return_t bootstrap_unprivileged(
- mach_port_t bp,
- mach_port_t *unpriv_port)
- AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5;
-
-/*
- * bootstrap_parent()
- *
- * Given a bootstrap subset port, return the parent bootstrap port.
- * If the specified bootstrap port is already the root subset,
- * the same port will be returned. Much like "." and ".." are the same
- * in the file system name space for the root directory ("/").
- *
- * Errors:
- * Returns BOOTSTRAP_NOT_PRIVILEGED if the caller is not running
- * with an effective user id of root (as determined by the security
- * token in the message trailer).
- */
-kern_return_t bootstrap_parent(
- mach_port_t bp,
- mach_port_t *parent_port);
-
-/*
- * bootstrap_register()
- *
- * Registers a send right for service_port with the service identified by
- * service_name. Attempts to register a service where an active binding
- * already exists are rejected.
- *
- * If the service was previously declared with bootstrap_create_service(),
- * but is not currently active, this call can be used to undeclare the
- * service. The bootstrap port used must have sufficient privilege to
- * do so. (Registering MACH_PORT_NULL is especially useful for shutting
- * down declared services).
- *
- * This API is deprecated. Old scenarios and recommendations:
- *
- * 1) Code that used to call bootstrap_check_in() and then bootstrap_register()
- * can now always call bootstrap_check_in().
- *
- * 2) If the code was registering a well known name, please switch to launchd.
- *
- * 3) If the code was registering a dynamically generated string and passing
- * the string to other applications, please rewrite the code to send a Mach
- * send-right directly.
- *
- * 4) If the launchd job maintained an optional Mach service, please reserve
- * the name with launchd and control the presense of the service through
- * ownership of the Mach receive right like so.
- *
- * <key>MachServices</key>
- * <dict>
- * <key>com.apple.windowserver</key>
- * <true/>
- * <key>com.apple.windowserver.active</key>
- * <dict>
- * <key>HideUntilCheckIn</key>
- * <true/>
- * </dict>
- * </dict>
- *
- *
- * Errors: Returns appropriate kernel errors on rpc failure.
- * Returns BOOTSTRAP_NOT_PRIVILEGED, if request directed to
- * bootstrap port without privilege.
- * Returns BOOTSTRAP_NAME_IN_USE, if service has already been
- * register or checked-in.
- */
-AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5
-kern_return_t
-bootstrap_register(mach_port_t bp, name_t service_name, mach_port_t sp);
-
-/*
- * bootstrap_create_service()
- *
- * Creates a service named "service_name" and returns a send right to that
- * port in "service_port." The port may later be checked in as if this
- * port were configured in the bootstrap configuration file.
- *
- * This API is deprecated. Please call bootstrap_check_in() instead.
- *
- * Errors: Returns appropriate kernel errors on rpc failure.
- * Returns BOOTSTRAP_SERVICE_ACTIVE, if service already exists.
- */
-#ifdef AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6
-AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6
-#endif
-kern_return_t
-bootstrap_create_service(mach_port_t bp, name_t service_name, mach_port_t *sp);
-
-/*
- * bootstrap_check_in()
- *
- * Returns the receive right for the service named by service_name. The
- * service must have been declared in the launchd.plist(5) file associated
- * with the job. Attempts to check_in a service which is already active
- * are not allowed.
- *
- * If the service was declared as being associated with a server, the
- * check_in must come from the server's privileged port (server_port).
- *
- * Errors: Returns appropriate kernel errors on rpc failure.
- * Returns BOOTSTRAP_UNKNOWN_SERVICE, if service does not exist.
- * Returns BOOTSTRAP_NOT_PRIVILEGED, if request directed to
- * bootstrap port without privilege.
- * Returns BOOTSTRAP_SERVICE_ACTIVE, if service has already been
- * registered or checked-in.
- */
-kern_return_t bootstrap_check_in(
- mach_port_t bp,
- const name_t service_name,
- mach_port_t *sp);
-
-/*
- * bootstrap_look_up()
- *
- * Returns a send right for the service port declared/registered under the
- * name service_name. The service is not guaranteed to be active. Use the
- * bootstrap_status call to determine the status of the service.
- *
- * Errors: Returns appropriate kernel errors on rpc failure.
- * Returns BOOTSTRAP_UNKNOWN_SERVICE, if service does not exist.
- */
-kern_return_t bootstrap_look_up(
- mach_port_t bp,
- const name_t service_name,
- mach_port_t *sp);
-
-/*
- * bootstrap_status()
- *
- * In practice, this call was used to preflight whether the following two
- * APIs would succeed.
- *
- * bootstrap_look_up()
- * bootstrap_check_in()
- *
- * Please don't bother. Just call the above two APIs directly and check
- * for failure.
- */
-kern_return_t bootstrap_status(
- mach_port_t bp,
- name_t service_name,
- bootstrap_status_t *service_active)
- AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5;
-
-/* bootstrap_strerror()
- *
- * Translate a return value from the bootstrap_*() APIs to a string.
- */
-const char *bootstrap_strerror(kern_return_t r) __attribute__((__nothrow__, __pure__, __warn_unused_result__));
-
-#pragma GCC visibility pop
-
-__END_DECLS
-
-#endif
+++ /dev/null
-#ifndef _BOOTSTRAP_PRIVATE_H_
-#define _BOOTSTRAP_PRIVATE_H_
-/*
- * Copyright (c) 2007 Apple 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 <servers/bootstrap.h>
-#include <sys/types.h>
-#include <uuid/uuid.h>
-
-__BEGIN_DECLS
-
-#pragma GCC visibility push(default)
-
-#define BOOTSTRAP_PER_PID_SERVICE (1 << 0)
-#define BOOTSTRAP_ALLOW_LOOKUP (1 << 1)
-#define BOOTSTRAP_DENY_JOB_CREATION (1 << 2)
-#define BOOTSTRAP_PRIVILEGED_SERVER (1 << 3)
-#define BOOTSTRAP_FORCE_LOCAL (1 << 4)
-#define BOOTSTRAP_SPECIFIC_INSTANCE (1 << 5)
-#define BOOTSTRAP_STRICT_CHECKIN (1 << 6)
-#define BOOTSTRAP_STRICT_LOOKUP (1 << 7)
-
-#define BOOTSTRAP_PROPERTY_EXPLICITSUBSET (1 << 0) /* Created via bootstrap_subset(). */
-#define BOOTSTRAP_PROPERTY_IMPLICITSUBSET (1 << 1) /* Created via _vprocmgr_switch_to_session(). */
-#define BOOTSTRAP_PROPERTY_MOVEDSUBSET (1 << 2) /* Created via _vprocmgr_move_subset_to_user(). */
-#define BOOTSTRAP_PROPERTY_PERUSER (1 << 3) /* A per-user launchd's root bootstrap. */
-#define BOOTSTRAP_PROPERTY_XPC_DOMAIN (1 << 4) /* An XPC domain. Duh. */
-#define BOOTSTRAP_PROPERTY_XPC_SINGLETON (1 << 5) /* A singleton XPC domain. */
-
-void bootstrap_init(void);
-
-kern_return_t bootstrap_register2(mach_port_t bp, name_t service_name, mach_port_t sp, uint64_t flags);
-
-kern_return_t bootstrap_look_up2(mach_port_t bp, const name_t service_name, mach_port_t *sp, pid_t target_pid, uint64_t flags);
-
-kern_return_t bootstrap_check_in2(mach_port_t bp, const name_t service_name, mach_port_t *sp, uint64_t flags);
-
-kern_return_t bootstrap_look_up_per_user(mach_port_t bp, const name_t service_name, uid_t target_user, mach_port_t *sp);
-
-kern_return_t bootstrap_lookup_children(mach_port_t bp, mach_port_array_t *children, name_array_t *names, bootstrap_property_array_t *properties, mach_msg_type_number_t *n_children);
-
-kern_return_t bootstrap_look_up3(mach_port_t bp, const name_t service_name, mach_port_t *sp, pid_t target_pid, const uuid_t instance_id, uint64_t flags);
-
-kern_return_t bootstrap_check_in3(mach_port_t bp, const name_t service_name, mach_port_t *sp, uuid_t instance_id, uint64_t flags);
-
-kern_return_t bootstrap_get_root(mach_port_t bp, mach_port_t *root);
-
-#pragma GCC visibility pop
-
-__END_DECLS
-
-#endif
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>KeepAlive</key>
- <dict>
- <key>PathState</key>
- <dict>
- <key>/etc/rc.local</key>
- <true/>
- <key>/etc/rc.shutdown.local</key>
- <true/>
- </dict>
- </dict>
- <key>Label</key>
- <string>com.apple.SystemStarter</string>
- <key>Program</key>
- <string>/sbin/SystemStarter</string>
- <key>QueueDirectories</key>
- <array>
- <string>/Library/StartupItems</string>
- <string>/System/Library/StartupItems</string>
- </array>
-</dict>
-</plist>
+++ /dev/null
-#ifndef __CONFIG_H__
-#define __CONFIG_H__
-#include <TargetConfig.h>
-#define HAVE_QUARANTINE TARGET_HAVE_QUARANTINE
-#define HAVE_SANDBOX TARGET_HAVE_SANDBOX
-#define HAVE_LIBAUDITD !TARGET_OS_EMBEDDED
-#endif /* __CONFIG_H__ */
+++ /dev/null
-# This file is going away
-
-AFPSERVER=-NO-
-AUTHSERVER=-NO-
-TIMESYNC=-NO-
-QTSSERVER=-NO-
+++ /dev/null
-/*
- * Copyright (c) 2005 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@
- */
-#ifndef _LAUNCH_H_
-#define _LAUNCH_H_
-
-#include <mach/mach.h>
-#include <sys/cdefs.h>
-#include <stddef.h>
-#include <stdbool.h>
-
-#pragma GCC visibility push(default)
-
-__BEGIN_DECLS
-
-#ifdef __GNUC__
-#define __ld_normal __attribute__((__nothrow__))
-#define __ld_setter __attribute__((__nothrow__, __nonnull__))
-#define __ld_getter __attribute__((__nothrow__, __nonnull__, __pure__, __warn_unused_result__))
-#define __ld_iterator(x, y) __attribute__((__nonnull__(x, y)))
-#define __ld_allocator __attribute__((__nothrow__, __malloc__, __nonnull__, __warn_unused_result__))
-#else
-#define __ld_normal
-#define __ld_setter
-#define __ld_getter
-#define __ld_iterator(x, y)
-#define __ld_allocator
-#endif
-
-
-#define LAUNCH_KEY_SUBMITJOB "SubmitJob"
-#define LAUNCH_KEY_REMOVEJOB "RemoveJob"
-#define LAUNCH_KEY_STARTJOB "StartJob"
-#define LAUNCH_KEY_STOPJOB "StopJob"
-#define LAUNCH_KEY_GETJOB "GetJob"
-#define LAUNCH_KEY_GETJOBS "GetJobs"
-#define LAUNCH_KEY_CHECKIN "CheckIn"
-
-#define LAUNCH_JOBKEY_LABEL "Label"
-#define LAUNCH_JOBKEY_DISABLED "Disabled"
-#define LAUNCH_JOBKEY_USERNAME "UserName"
-#define LAUNCH_JOBKEY_GROUPNAME "GroupName"
-#define LAUNCH_JOBKEY_TIMEOUT "TimeOut"
-#define LAUNCH_JOBKEY_EXITTIMEOUT "ExitTimeOut"
-#define LAUNCH_JOBKEY_INITGROUPS "InitGroups"
-#define LAUNCH_JOBKEY_SOCKETS "Sockets"
-#define LAUNCH_JOBKEY_MACHSERVICES "MachServices"
-#define LAUNCH_JOBKEY_MACHSERVICELOOKUPPOLICIES "MachServiceLookupPolicies"
-#define LAUNCH_JOBKEY_INETDCOMPATIBILITY "inetdCompatibility"
-#define LAUNCH_JOBKEY_ENABLEGLOBBING "EnableGlobbing"
-#define LAUNCH_JOBKEY_PROGRAMARGUMENTS "ProgramArguments"
-#define LAUNCH_JOBKEY_PROGRAM "Program"
-#define LAUNCH_JOBKEY_ONDEMAND "OnDemand"
-#define LAUNCH_JOBKEY_KEEPALIVE "KeepAlive"
-#define LAUNCH_JOBKEY_LIMITLOADTOHOSTS "LimitLoadToHosts"
-#define LAUNCH_JOBKEY_LIMITLOADFROMHOSTS "LimitLoadFromHosts"
-#define LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE "LimitLoadToSessionType"
-#define LAUNCH_JOBKEY_LIMITLOADTOHARDWARE "LimitLoadToHardware"
-#define LAUNCH_JOBKEY_LIMITLOADFROMHARDWARE "LimitLoadFromHardware"
-#define LAUNCH_JOBKEY_RUNATLOAD "RunAtLoad"
-#define LAUNCH_JOBKEY_ROOTDIRECTORY "RootDirectory"
-#define LAUNCH_JOBKEY_WORKINGDIRECTORY "WorkingDirectory"
-#define LAUNCH_JOBKEY_ENVIRONMENTVARIABLES "EnvironmentVariables"
-#define LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES "UserEnvironmentVariables"
-#define LAUNCH_JOBKEY_UMASK "Umask"
-#define LAUNCH_JOBKEY_NICE "Nice"
-#define LAUNCH_JOBKEY_HOPEFULLYEXITSFIRST "HopefullyExitsFirst"
-#define LAUNCH_JOBKEY_HOPEFULLYEXITSLAST "HopefullyExitsLast"
-#define LAUNCH_JOBKEY_LOWPRIORITYIO "LowPriorityIO"
-#define LAUNCH_JOBKEY_SESSIONCREATE "SessionCreate"
-#define LAUNCH_JOBKEY_STARTONMOUNT "StartOnMount"
-#define LAUNCH_JOBKEY_SOFTRESOURCELIMITS "SoftResourceLimits"
-#define LAUNCH_JOBKEY_HARDRESOURCELIMITS "HardResourceLimits"
-#define LAUNCH_JOBKEY_STANDARDINPATH "StandardInPath"
-#define LAUNCH_JOBKEY_STANDARDOUTPATH "StandardOutPath"
-#define LAUNCH_JOBKEY_STANDARDERRORPATH "StandardErrorPath"
-#define LAUNCH_JOBKEY_DEBUG "Debug"
-#define LAUNCH_JOBKEY_WAITFORDEBUGGER "WaitForDebugger"
-#define LAUNCH_JOBKEY_QUEUEDIRECTORIES "QueueDirectories"
-#define LAUNCH_JOBKEY_WATCHPATHS "WatchPaths"
-#define LAUNCH_JOBKEY_STARTINTERVAL "StartInterval"
-#define LAUNCH_JOBKEY_STARTCALENDARINTERVAL "StartCalendarInterval"
-#define LAUNCH_JOBKEY_BONJOURFDS "BonjourFDs"
-#define LAUNCH_JOBKEY_LASTEXITSTATUS "LastExitStatus"
-#define LAUNCH_JOBKEY_PID "PID"
-#define LAUNCH_JOBKEY_THROTTLEINTERVAL "ThrottleInterval"
-#define LAUNCH_JOBKEY_LAUNCHONLYONCE "LaunchOnlyOnce"
-#define LAUNCH_JOBKEY_ABANDONPROCESSGROUP "AbandonProcessGroup"
-#define LAUNCH_JOBKEY_IGNOREPROCESSGROUPATSHUTDOWN "IgnoreProcessGroupAtShutdown"
-#define LAUNCH_JOBKEY_POLICIES "Policies"
-#define LAUNCH_JOBKEY_ENABLETRANSACTIONS "EnableTransactions"
-
-#define LAUNCH_JOBPOLICY_DENYCREATINGOTHERJOBS "DenyCreatingOtherJobs"
-
-#define LAUNCH_JOBINETDCOMPATIBILITY_WAIT "Wait"
-
-#define LAUNCH_JOBKEY_MACH_RESETATCLOSE "ResetAtClose"
-#define LAUNCH_JOBKEY_MACH_HIDEUNTILCHECKIN "HideUntilCheckIn"
-#define LAUNCH_JOBKEY_MACH_DRAINMESSAGESONCRASH "DrainMessagesOnCrash"
-#define LAUNCH_JOBKEY_MACH_PINGEVENTUPDATES "PingEventUpdates"
-
-#define LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT "SuccessfulExit"
-#define LAUNCH_JOBKEY_KEEPALIVE_NETWORKSTATE "NetworkState"
-#define LAUNCH_JOBKEY_KEEPALIVE_PATHSTATE "PathState"
-#define LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBACTIVE "OtherJobActive"
-#define LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBENABLED "OtherJobEnabled"
-#define LAUNCH_JOBKEY_KEEPALIVE_AFTERINITIALDEMAND "AfterInitialDemand"
-#define LAUNCH_JOBKEY_KEEPALIVE_CRASHED "Crashed"
-
-#define LAUNCH_JOBKEY_LAUNCHEVENTS "LaunchEvents"
-
-#define LAUNCH_JOBKEY_CAL_MINUTE "Minute"
-#define LAUNCH_JOBKEY_CAL_HOUR "Hour"
-#define LAUNCH_JOBKEY_CAL_DAY "Day"
-#define LAUNCH_JOBKEY_CAL_WEEKDAY "Weekday"
-#define LAUNCH_JOBKEY_CAL_MONTH "Month"
-
-#define LAUNCH_JOBKEY_RESOURCELIMIT_CORE "Core"
-#define LAUNCH_JOBKEY_RESOURCELIMIT_CPU "CPU"
-#define LAUNCH_JOBKEY_RESOURCELIMIT_DATA "Data"
-#define LAUNCH_JOBKEY_RESOURCELIMIT_FSIZE "FileSize"
-#define LAUNCH_JOBKEY_RESOURCELIMIT_MEMLOCK "MemoryLock"
-#define LAUNCH_JOBKEY_RESOURCELIMIT_NOFILE "NumberOfFiles"
-#define LAUNCH_JOBKEY_RESOURCELIMIT_NPROC "NumberOfProcesses"
-#define LAUNCH_JOBKEY_RESOURCELIMIT_RSS "ResidentSetSize"
-#define LAUNCH_JOBKEY_RESOURCELIMIT_STACK "Stack"
-
-#define LAUNCH_JOBKEY_DISABLED_MACHINETYPE "MachineType"
-#define LAUNCH_JOBKEY_DISABLED_MODELNAME "ModelName"
-
-#define LAUNCH_JOBSOCKETKEY_TYPE "SockType"
-#define LAUNCH_JOBSOCKETKEY_PASSIVE "SockPassive"
-#define LAUNCH_JOBSOCKETKEY_BONJOUR "Bonjour"
-#define LAUNCH_JOBSOCKETKEY_SECUREWITHKEY "SecureSocketWithKey"
-#define LAUNCH_JOBSOCKETKEY_PATHNAME "SockPathName"
-#define LAUNCH_JOBSOCKETKEY_PATHMODE "SockPathMode"
-#define LAUNCH_JOBSOCKETKEY_NODENAME "SockNodeName"
-#define LAUNCH_JOBSOCKETKEY_SERVICENAME "SockServiceName"
-#define LAUNCH_JOBSOCKETKEY_FAMILY "SockFamily"
-#define LAUNCH_JOBSOCKETKEY_PROTOCOL "SockProtocol"
-#define LAUNCH_JOBSOCKETKEY_MULTICASTGROUP "MulticastGroup"
-
-typedef struct _launch_data *launch_data_t;
-
-typedef enum {
- LAUNCH_DATA_DICTIONARY = 1,
- LAUNCH_DATA_ARRAY,
- LAUNCH_DATA_FD,
- LAUNCH_DATA_INTEGER,
- LAUNCH_DATA_REAL,
- LAUNCH_DATA_BOOL,
- LAUNCH_DATA_STRING,
- LAUNCH_DATA_OPAQUE,
- LAUNCH_DATA_ERRNO,
- LAUNCH_DATA_MACHPORT,
-} launch_data_type_t;
-
-launch_data_t launch_data_alloc(launch_data_type_t) __ld_allocator;
-launch_data_t launch_data_copy(launch_data_t) __ld_allocator;
-launch_data_type_t launch_data_get_type(const launch_data_t) __ld_getter;
-void launch_data_free(launch_data_t) __ld_setter;
-
-/* Generic Dictionaries */
-/* the value should not be changed while iterating */
-bool launch_data_dict_insert(launch_data_t, const launch_data_t, const char *) __ld_setter;
-launch_data_t launch_data_dict_lookup(const launch_data_t, const char *) __ld_getter;
-bool launch_data_dict_remove(launch_data_t, const char *) __ld_setter;
-void launch_data_dict_iterate(const launch_data_t, void (*)(const launch_data_t, const char *, void *), void *) __ld_iterator(1, 2);
-size_t launch_data_dict_get_count(const launch_data_t) __ld_getter;
-
-/* Generic Arrays */
-bool launch_data_array_set_index(launch_data_t, const launch_data_t, size_t) __ld_setter;
-launch_data_t launch_data_array_get_index(const launch_data_t, size_t) __ld_getter;
-size_t launch_data_array_get_count(const launch_data_t) __ld_getter;
-
-launch_data_t launch_data_new_fd(int) __ld_allocator;
-launch_data_t launch_data_new_machport(mach_port_t) __ld_allocator;
-launch_data_t launch_data_new_integer(long long) __ld_allocator;
-launch_data_t launch_data_new_bool(bool) __ld_allocator;
-launch_data_t launch_data_new_real(double) __ld_allocator;
-launch_data_t launch_data_new_string(const char *) __ld_allocator;
-launch_data_t launch_data_new_opaque(const void *, size_t) __ld_allocator;
-
-bool launch_data_set_fd(launch_data_t, int) __ld_setter;
-bool launch_data_set_machport(launch_data_t, mach_port_t) __ld_setter;
-bool launch_data_set_integer(launch_data_t, long long) __ld_setter;
-bool launch_data_set_bool(launch_data_t, bool) __ld_setter;
-bool launch_data_set_real(launch_data_t, double) __ld_setter;
-bool launch_data_set_string(launch_data_t, const char *) __ld_setter;
-bool launch_data_set_opaque(launch_data_t, const void *, size_t) __ld_setter;
-
-int launch_data_get_fd(const launch_data_t) __ld_getter;
-mach_port_t launch_data_get_machport(const launch_data_t) __ld_getter;
-long long launch_data_get_integer(const launch_data_t) __ld_getter;
-bool launch_data_get_bool(const launch_data_t) __ld_getter;
-double launch_data_get_real(const launch_data_t) __ld_getter;
-const char * launch_data_get_string(const launch_data_t) __ld_getter;
-void * launch_data_get_opaque(const launch_data_t) __ld_getter;
-size_t launch_data_get_opaque_size(const launch_data_t) __ld_getter;
-int launch_data_get_errno(const launch_data_t) __ld_getter;
-
-
-/* launch_get_fd()
- *
- * Use this to get the FD if you're doing asynchronous I/O with select(),
- * poll() or kevent().
- */
-int launch_get_fd(void) __ld_normal;
-
-/* launch_msg()
- *
- * Use this API to send and receive messages.
- * Calling launch_msg() with no message to send is a valid way to get
- * asynchronously received messages.
- *
- * If a message was to be sent, it returns NULL and errno on failure.
- *
- * If no messages were to be sent, it returns NULL and errno is set to zero if
- * no more asynchronous messages are available.
- */
-launch_data_t launch_msg(const launch_data_t) __ld_normal;
-
-__END_DECLS
-
-#pragma GCC visibility pop
-
-#endif
+++ /dev/null
-#ifndef _LAUNCH_INTERNAL_H_
-#define _LAUNCH_INTERNAL_H_
-/*
- * Copyright (c) 2007 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@
- */
-
-#pragma GCC visibility push(default)
-
-#define LAUNCHD_DB_PREFIX "/var/db/launchd.db"
-
-struct _launch_data {
- uint64_t type;
- union {
- struct {
- union {
- launch_data_t *_array;
- char *string;
- void *opaque;
- int64_t __junk;
- };
- union {
- uint64_t _array_cnt;
- uint64_t string_len;
- uint64_t opaque_size;
- };
- };
- int64_t fd;
- uint64_t mp;
- uint64_t err;
- int64_t number;
- uint64_t boolean; /* We'd use 'bool' but this struct needs to be used under Rosetta, and sizeof(bool) is different between PowerPC and Intel */
- double float_num;
- };
-};
-
-typedef struct _launch *launch_t;
-
-launch_t launchd_fdopen(int, int);
-int launchd_getfd(launch_t);
-void launchd_close(launch_t, __typeof__(close) closefunc);
-
-launch_data_t launch_data_new_errno(int);
-bool launch_data_set_errno(launch_data_t, int);
-
-int launchd_msg_send(launch_t, launch_data_t);
-int launchd_msg_recv(launch_t, void (*)(launch_data_t, void *), void *);
-
-size_t launch_data_pack(launch_data_t d, void *where, size_t len, int *fd_where, size_t *fdslotsleft);
-launch_data_t launch_data_unpack(void *data, size_t data_size, int *fds, size_t fd_cnt, size_t *data_offset, size_t *fdoffset);
-
-#pragma GCC visibility pop
-
-#endif
+++ /dev/null
-/*
- * Copyright (c) 2005 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@
- */
-#ifndef _LAUNCH_PRIV_H_
-#define _LAUNCH_PRIV_H_
-
-#include <mach/mach.h>
-#include <sys/types.h>
-#include <launch.h>
-#include <unistd.h>
-#include <paths.h>
-#include <uuid/uuid.h>
-
-#pragma GCC visibility push(default)
-
-__BEGIN_DECLS
-
-#define LAUNCH_KEY_SETUSERENVIRONMENT "SetUserEnvironment"
-#define LAUNCH_KEY_UNSETUSERENVIRONMENT "UnsetUserEnvironment"
-#define LAUNCH_KEY_SHUTDOWN "Shutdown"
-#define LAUNCH_KEY_SINGLEUSER "SingleUser"
-#define LAUNCH_KEY_GETRESOURCELIMITS "GetResourceLimits"
-#define LAUNCH_KEY_SETRESOURCELIMITS "SetResourceLimits"
-#define LAUNCH_KEY_GETRUSAGESELF "GetResourceUsageSelf"
-#define LAUNCH_KEY_GETRUSAGECHILDREN "GetResourceUsageChildren"
-#define LAUNCH_KEY_SETPRIORITYLIST "SetPriorityList"
-
-#define LAUNCHD_SOCKET_ENV "LAUNCHD_SOCKET"
-#define LAUNCHD_SOCK_PREFIX _PATH_VARTMP "launchd"
-#define LAUNCHD_TRUSTED_FD_ENV "__LAUNCHD_FD"
-#define LAUNCHD_ASYNC_MSG_KEY "_AsyncMessage"
-#define LAUNCH_KEY_BATCHCONTROL "BatchControl"
-#define LAUNCH_KEY_BATCHQUERY "BatchQuery"
-#define LAUNCHD_DO_APPLE_INTERNAL_LOGGING "__DoAppleInternalLogging__"
-
-#define LAUNCH_JOBKEY_TRANSACTIONCOUNT "TransactionCount"
-#define LAUNCH_JOBKEY_QUARANTINEDATA "QuarantineData"
-#define LAUNCH_JOBKEY_SANDBOXPROFILE "SandboxProfile"
-#define LAUNCH_JOBKEY_SANDBOXFLAGS "SandboxFlags"
-#define LAUNCH_JOBKEY_SANDBOX_NAMED "Named"
-#define LAUNCH_JOBKEY_JETSAMPROPERTIES "JetsamProperties"
-#define LAUNCH_JOBKEY_JETSAMPRIORITY "JetsamPriority"
-#define LAUNCH_JOBKEY_JETSAMMEMORYLIMIT "JetsamMemoryLimit"
-#define LAUNCH_JOBKEY_SECURITYSESSIONUUID "SecuritySessionUUID"
-#define LAUNCH_JOBKEY_DISABLEASLR "DisableASLR"
-#define LAUNCH_JOBKEY_XPCDOMAIN "XPCDomain"
-#define LAUNCH_JOBKEY_POSIXSPAWNTYPE "POSIXSpawnType"
-
-#define LAUNCH_KEY_JETSAMLABEL "JetsamLabel"
-#define LAUNCH_KEY_JETSAMFRONTMOST "JetsamFrontmost"
-#define LAUNCH_KEY_JETSAMPRIORITY LAUNCH_JOBKEY_JETSAMPRIORITY
-#define LAUNCH_KEY_JETSAMMEMORYLIMIT LAUNCH_JOBKEY_JETSAMMEMORYLIMIT
-
-#define LAUNCH_KEY_POSIXSPAWNTYPE_TALAPP "TALApp"
-#define LAUNCH_KEY_POSIXSPAWNTYPE_WIDGET "Widget"
-#define LAUNCH_KEY_POSIXSPAWNTYPE_IOSAPP "iOSApp"
-
-#define LAUNCH_JOBKEY_EMBEDDEDPRIVILEGEDISPENSATION "EmbeddedPrivilegeDispensation"
-#define LAUNCH_JOBKEY_EMBEDDEDMAINTHREADPRIORITY "EmbeddedMainThreadPriority"
-
-#define LAUNCH_JOBKEY_ENTERKERNELDEBUGGERBEFOREKILL "EnterKernelDebuggerBeforeKill"
-#define LAUNCH_JOBKEY_PERJOBMACHSERVICES "PerJobMachServices"
-#define LAUNCH_JOBKEY_SERVICEIPC "ServiceIPC"
-#define LAUNCH_JOBKEY_BINARYORDERPREFERENCE "BinaryOrderPreference"
-#define LAUNCH_JOBKEY_MACHEXCEPTIONHANDLER "MachExceptionHandler"
-#define LAUNCH_JOBKEY_MULTIPLEINSTANCES "MultipleInstances"
-#define LAUNCH_JOBKEY_EVENTMONITOR "EventMonitor"
-#define LAUNCH_JOBKEY_SHUTDOWNMONITOR "ShutdownMonitor"
-#define LAUNCH_JOBKEY_BEGINTRANSACTIONATSHUTDOWN "BeginTransactionAtShutdown"
-#define LAUNCH_JOBKEY_XPCDOMAINBOOTSTRAPPER "XPCDomainBootstrapper"
-
-#define LAUNCH_JOBKEY_MACH_KUNCSERVER "kUNCServer"
-#define LAUNCH_JOBKEY_MACH_EXCEPTIONSERVER "ExceptionServer"
-#define LAUNCH_JOBKEY_MACH_TASKSPECIALPORT "TaskSpecialPort"
-#define LAUNCH_JOBKEY_MACH_HOSTSPECIALPORT "HostSpecialPort"
-#define LAUNCH_JOBKEY_MACH_ENTERKERNELDEBUGGERONCLOSE "EnterKernelDebuggerOnClose"
-
-#define LAUNCH_ENV_INSTANCEID "LaunchInstanceID"
-
-/* For LoginWindow.
- *
- * After this call, the task's bootstrap port is set to the per session launchd.
- *
- * This returns 1 on success (it used to return otherwise), and -1 on failure.
- */
-#define LOAD_ONLY_SAFEMODE_LAUNCHAGENTS 1 << 0
-#define LAUNCH_GLOBAL_ON_DEMAND 1 << 1
-pid_t create_and_switch_to_per_session_launchd(const char * /* loginname */, int flags, ...);
-
-/* Also for LoginWindow.
- *
- * This is will load jobs at the LoginWindow prompt.
- */
-void load_launchd_jobs_at_loginwindow_prompt(int flags, ...);
-
-/* For CoreProcesses
- */
-
-#define SPAWN_VIA_LAUNCHD_STOPPED 0x0001
-#define SPAWN_VIA_LAUNCHD_TALAPP 0x0002
-#define SPAWN_VIA_LAUNCHD_WIDGET 0x0004
-#define SPAWN_VIA_LAUNCHD_DISABLE_ASLR 0x0008
-
-struct spawn_via_launchd_attr {
- uint64_t spawn_flags;
- const char * spawn_path;
- const char * spawn_chdir;
- const char *const * spawn_env;
- const mode_t * spawn_umask;
- mach_port_t * spawn_observer_port;
- const cpu_type_t * spawn_binpref;
- size_t spawn_binpref_cnt;
- void * spawn_quarantine;
- const char * spawn_seatbelt_profile;
- const uint64_t * spawn_seatbelt_flags;
-};
-
-#define spawn_via_launchd(a, b, c) _spawn_via_launchd(a, b, c, 3)
-pid_t _spawn_via_launchd(
- const char *label,
- const char *const *argv,
- const struct spawn_via_launchd_attr *spawn_attrs,
- int struct_version);
-
-int launch_wait(mach_port_t port);
-
-kern_return_t mpm_wait(mach_port_t ajob, int *wstatus);
-
-kern_return_t mpm_uncork_fork(mach_port_t ajob);
-
-__END_DECLS
-
-#pragma GCC visibility pop
-
-
-#endif
+++ /dev/null
-.Dd 1 May, 2009
-.Dt launchctl 1
-.Os Darwin
-.Sh NAME
-.Nm launchctl
-.Nd Interfaces with launchd
-.Sh SYNOPSIS
-.Nm
-.Op Ar subcommand Op Ar arguments ...
-.Sh DESCRIPTION
-.Nm
-interfaces with
-.Nm launchd
-to load, unload daemons/agents and generally control
-.Nm launchd .
-.Nm
-supports taking subcommands on the command line, interactively or even redirected from standard input.
-These commands can be stored in
-.Nm $HOME/.launchd.conf
-or
-.Nm /etc/launchd.conf
-to be read at the time
-.Nm launchd
-starts.
-.Sh SUBCOMMANDS
-.Bl -tag -width -indent
-.It Xo Ar load Op Fl wF
-.Op Fl S Ar sessiontype
-.Op Fl D Ar domain
-.Ar paths ...
-.Xc
-Load the specified configuration files or directories of configuration files.
-Jobs that are not on-demand will be started as soon as possible.
-All specified jobs will be loaded before any of them are allowed to start.
-Note that per-user configuration files (LaunchAgents) must be owned by the user
-loading them. All system-wide daemons (LaunchDaemons) must be owned by root. Configuration files
-must not be group- or world-writable. These restrictions are in place for security reasons,
-as allowing writability to a launchd configuration file allows one to specify which executable
-will be launched.
-.Pp
-Note that allowing non-root write access to the /System/Library/LaunchDaemons directory WILL render your system unbootable.
-.Bl -tag -width -indent
-.It Fl w
-Overrides the Disabled key and sets it to false. In previous versions, this option
-would modify the configuration file. Now the state of the Disabled key is stored
-elsewhere on-disk.
-.It Fl F
-Force the loading of the plist. Ignore the Disabled key.
-.It Fl S Ar sessiontype
-Some jobs only make sense in certain contexts. This flag instructs
-.Nm launchctl
-to look for jobs in a different location when using the -D flag, and allows
-.Nm launchctl
-to restrict which jobs are loaded into which session types. Currently known
-session types include: Aqua, LoginWindow, Background, StandardIO and System.
-.It Fl D Ar domain
-Look for
-.Xr plist 5 files ending in *.plist in the domain given. Valid domains include
-"system," "local," "network" and "all." When providing a session type, an additional
-domain is available for use called "user." For example, without a session type given,
-"-D system" would load from property list files from /System/Library/LaunchDaemons.
-With a session type passed, it would load from /System/Library/LaunchAgents.
-.El
-.It Xo Ar unload Op Fl w
-.Op Fl S Ar sessiontype
-.Op Fl D Ar domain
-.Ar paths ...
-.Xc
-Unload the specified configuration files or directories of configuration files.
-This will also stop the job if it is running.
-.Bl -tag -width -indent
-.It Fl w
-Overrides the Disabled key and sets it to true. In previous versions, this option
-would modify the configuration file. Now the state of the Disabled key is stored
-elsewhere on-disk.
-.It Fl S Ar sessiontype
-Some jobs only make sense in certain contexts. This flag instructs
-.Nm launchctl
-to look for jobs in a different location when using the -D flag, and allows
-.Nm launchctl
-to restrict which jobs are loaded into which session types. Currently known
-session types include: Aqua, LoginWindow, Background, StandardIO and System.
-.It Fl D Ar domain
-Look for
-.Xr plist 5 files ending in *.plist in the domain given. Valid domains include
-"system," "local," "network" and "all." When providing a session type, an additional
-domain is available for use called "user." For example, without a session type given,
-"-D system" would load from property list files from /System/Library/LaunchDaemons.
-With a session type passed, it would load from /System/Library/LaunchAgents.
-.El
-.It Xo Ar submit Fl l Ar label
-.Op Fl p Ar executable
-.Op Fl o Ar path
-.Op Fl e Ar path
-.Ar -- command
-.Op Ar args
-.Xc
-A simple way of submitting a program to run without a configuration file. This mechanism also tells launchd to keep the program alive in the event of failure.
-.Bl -tag -width -indent
-.It Fl l Ar label
-What unique label to assign this job to launchd.
-.It Fl p Ar program
-What program to really execute, regardless of what follows the -- in the submit sub-command.
-.It Fl o Ar path
-Where to send the stdout of the program.
-.It Fl e Ar path
-Where to send the stderr of the program.
-.El
-.It Ar remove Ar job_label
-Remove the job from launchd by label.
-.It Ar start Ar job_label
-Start the specified job by label. The expected use of this subcommand is for
-debugging and testing so that one can manually kick-start an on-demand server.
-.It Ar stop Ar job_label
-Stop the specified job by label. If a job is on-demand, launchd may immediately
-restart the job if launchd finds any criteria that is satisfied.
-Non-demand based jobs will always be restarted. Use of this subcommand is discouraged.
-Jobs should ideally idle timeout by themselves.
-.It Xo Ar list
-.Op Ar -x
-.Op Ar label
-.Xc
-With no arguments, list all of the jobs loaded into
-.Nm launchd
-in three columns. The first column displays the PID of the job if it is running.
-The second column displays the last exit status of the job. If the number in this
-column is negative, it represents the negative of the signal which killed the job.
-Thus, "-15" would indicate that the job was terminated with SIGTERM. The third column
-is the job's label.
-.Pp
-Note that you may see some jobs in the list whose labels are in the style "0xdeadbeef.anonymous.program".
-These are jobs which are not managed by
-.Nm launchd ,
-but, at one point, made a request to it.
-.Nm launchd
-claims no ownership and makes no guarantees regarding these jobs. They are stored purely for
-bookkeeping purposes.
-.Pp
-Similarly, you may see labels of the style "0xdeadbeef.mach_init.program". These are legacy jobs that run
-under mach_init emulation. This mechanism will be removed in future versions, and all remaining mach_init
-jobs should be converted over to
-.Nm launchd .
-.Pp
-If
-.Op Ar label
-is specified, prints information about the requested job. If
-.Op Ar -x
-is specified, the information for the specified job is output as an XML property list.
-.It Ar setenv Ar key Ar value
-Set an environmental variable inside of
-.Nm launchd .
-.It Ar unsetenv Ar key
-Unset an environmental variable inside of
-.Nm launchd .
-.It Ar getenv Ar key
-Get an environmental variable inside of
-.Nm launchd .
-.It Ar export
-Export all of the environmental variables of
-.Nm launchd
-for use in a shell eval statement.
-.It Ar getrusage self | children
-Get the resource utilization statistics for
-.Nm launchd
-or the children of
-.Nm launchd .
-.It Xo Ar log
-.Op Ar level loglevel
-.Op Ar only | mask loglevels...
-.Xc
-Get and set the
-.Xr syslog 3
-log level mask. The available log levels are: debug, info, notice, warning, error, critical, alert and emergency.
-.It Xo Ar limit
-.Op Ar cpu | filesize | data | stack | core | rss | memlock | maxproc | maxfiles
-.Op Ar both Op Ar soft | hard
-.Xc
-With no arguments, this command prints all the resource limits of
-.Nm launchd
-as found via
-.Xr getrlimit 2 .
-When a given resource is specified, it prints the limits for that resource.
-With a third argument, it sets both the hard and soft limits to that value.
-With four arguments, the third and forth argument represent the soft and hard limits respectively.
-See
-.Xr setrlimit 2 .
-.It Ar shutdown
-Tell
-.Nm launchd
-to prepare for shutdown by removing all jobs.
-.It Ar umask Op Ar newmask
-Get or optionally set the
-.Xr umask 2
-of
-.Nm launchd .
-.It Xo Ar bslist
-.Op Ar PID | ..
-.Op Ar -j
-.Xc
-This prints out Mach bootstrap services and their respective states. While the
-namespace appears flat, it is in fact hierarchical, thus allowing for certain
-services to be only available to a subset of processes. The three states a
-service can be in are active ("A"), inactive ("I") and on-demand ("D").
-.Pp
-If
-.Op Ar PID
-is specified, print the Mach bootstrap services available to that PID. If
-.Op Ar ..
-is specified, print the Mach bootstrap services available in the parent of the
-current bootstrap. Note that in Mac OS X v10.6, the per-user Mach bootstrap namespace
-is flat, so you will only see a different set of services in a per-user bootstrap
-if you are in an explicitly-created bootstrap subset.
-.Pp
-If
-.Op Ar -j
-is specified, each service name will be followed by the name of the job which registered
-it.
-.It Ar bsexec Ar PID command Op Ar args
-This executes the given command in the same Mach bootstrap namespace hierachy
-as the given PID.
-.It Ar bstree Op Ar -j
-This prints a hierarchical view of the entire Mach bootstrap tree. If
-.Op Ar -j
-is specified, each service name will be followed by the name of the job which registered it.
-Requires root
-privileges.
-.It Ar managerpid
-This prints the PID of the launchd which manages the current bootstrap.
-.It Ar manageruid
-This prints the UID of the launchd which manages the current bootstrap.
-.It Ar managername
-This prints the name of the launchd job manager which manages the current bootstrap.
-See LimitLoadToSessionType in
-.Xr launchd.plist 5
-for more details.
-.It Ar help
-Print out a quick usage statement.
-.El
-.Sh ENVIRONMENTAL VARIABLES
-.Bl -tag -width -indent
-.It Pa LAUNCHD_SOCKET
-This variable informs launchctl how to find the correct launchd to talk to. If it is missing, launchctl will use a built-in default.
-.El
-.Sh FILES
-.Bl -tag -width "/System/Library/LaunchDaemons" -compact
-.It Pa ~/Library/LaunchAgents
-Per-user agents provided by the user.
-.It Pa /Library/LaunchAgents
-Per-user agents provided by the administrator.
-.It Pa /Library/LaunchDaemons
-System wide daemons provided by the administrator.
-.It Pa /System/Library/LaunchAgents
-Mac OS X Per-user agents.
-.It Pa /System/Library/LaunchDaemons
-Mac OS X System wide daemons.
-.El
-.Sh SEE ALSO
-.Xr launchd.plist 5 ,
-.Xr launchd.conf 5 ,
-.Xr launchd 8
+++ /dev/null
-/*
- * Copyright (c) 2005-2010 Apple 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@
- */
-
-static const char *const __rcs_file_version__ = "$Revision: 25957 $";
-
-#include "config.h"
-#include "launch.h"
-#include "launch_priv.h"
-#include "bootstrap.h"
-#include "vproc.h"
-#include "vproc_priv.h"
-#include "vproc_internal.h"
-#include "bootstrap_priv.h"
-#include "launch_internal.h"
-
-#include <CoreFoundation/CoreFoundation.h>
-#include <CoreFoundation/CFPriv.h>
-#include <CoreFoundation/CFLogUtilities.h>
-#include <ServiceManagement/ServiceManagement_Private.h>
-#include <TargetConditionals.h>
-#include <IOKit/IOKitLib.h>
-#include <NSSystemDirectories.h>
-#include <mach/mach.h>
-#include <sys/types.h>
-#include <sys/sysctl.h>
-#include <sys/time.h>
-#include <sys/sysctl.h>
-#include <sys/stat.h>
-#include <sys/socket.h>
-#ifndef SO_EXECPATH
-/* This is just so it's easy for me to compile launchctl without buildit. */
- #define SO_EXECPATH 0x1085
-#endif
-#include <sys/un.h>
-#include <sys/fcntl.h>
-#include <sys/event.h>
-#include <sys/resource.h>
-#include <sys/param.h>
-#include <sys/mount.h>
-#include <sys/reboot.h>
-#include <net/if.h>
-#include <netinet/in.h>
-#include <netinet/in_var.h>
-#include <netinet6/nd6.h>
-#include <unistd.h>
-#include <dirent.h>
-#include <libgen.h>
-#include <libinfo.h>
-#include <pwd.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <pwd.h>
-#include <grp.h>
-#include <netdb.h>
-#include <syslog.h>
-#include <glob.h>
-#include <readline/readline.h>
-#include <readline/history.h>
-#include <dns_sd.h>
-#include <paths.h>
-#include <utmpx.h>
-#include <bootfiles.h>
-#include <sysexits.h>
-#include <util.h>
-#include <spawn.h>
-#include <sys/syslimits.h>
-#include <fnmatch.h>
-
-#if HAVE_LIBAUDITD
-#include <bsm/auditd_lib.h>
-#ifndef AUDITD_PLIST_FILE
-#define AUDITD_PLIST_FILE "/System/Library/LaunchDaemons/com.apple.auditd.plist"
-#endif
-#endif
-
-extern char **environ;
-
-
-#define LAUNCH_SECDIR _PATH_TMP "launch-XXXXXX"
-#define LAUNCH_ENV_KEEPCONTEXT "LaunchKeepContext"
-
-#define MACHINIT_JOBKEY_ONDEMAND "OnDemand"
-#define MACHINIT_JOBKEY_SERVICENAME "ServiceName"
-#define MACHINIT_JOBKEY_COMMAND "Command"
-#define MACHINIT_JOBKEY_SERVERPORT "ServerPort"
-#define MACHINIT_JOBKEY_SERVICEPORT "ServicePort"
-
-#define assumes(e) \
- (__builtin_expect(!(e), 0) ? _log_launchctl_bug(__rcs_file_version__, __FILE__, __LINE__, #e), false : true)
-
-#define CFTypeCheck(cf, type) (CFGetTypeID(cf) == type ## GetTypeID())
-
-struct load_unload_state {
- launch_data_t pass1;
- launch_data_t pass2;
- char *session_type;
- bool editondisk:1, load:1, forceload:1;
-};
-
-static void myCFDictionaryApplyFunction(const void *key, const void *value, void *context);
-static void job_override(CFTypeRef key, CFTypeRef val, CFMutableDictionaryRef job);
-static CFTypeRef CFTypeCreateFromLaunchData(launch_data_t obj);
-static CFArrayRef CFArrayCreateFromLaunchArray(launch_data_t arr);
-static CFDictionaryRef CFDictionaryCreateFromLaunchDictionary(launch_data_t dict);
-static bool launch_data_array_append(launch_data_t a, launch_data_t o);
-static void distill_jobs(launch_data_t);
-static void distill_config_file(launch_data_t);
-static void sock_dict_cb(launch_data_t what, const char *key, void *context);
-static void sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data_t fdarray, launch_data_t thejob);
-static launch_data_t CF2launch_data(CFTypeRef);
-static launch_data_t read_plist_file(const char *file, bool editondisk, bool load);
-static CFPropertyListRef CreateMyPropertyListFromFile(const char *);
-static CFPropertyListRef CFPropertyListCreateFromFile(CFURLRef plistURL);
-static void WriteMyPropertyListToFile(CFPropertyListRef, const char *);
-static bool path_goodness_check(const char *path, bool forceload);
-static void readpath(const char *, struct load_unload_state *);
-static void readfile(const char *, struct load_unload_state *);
-static int _fd(int);
-static int demux_cmd(int argc, char *const argv[]);
-static launch_data_t do_rendezvous_magic(const struct addrinfo *res, const char *serv);
-static void submit_job_pass(launch_data_t jobs);
-static void do_mgroup_join(int fd, int family, int socktype, int protocol, const char *mgroup);
-static mach_port_t str2bsport(const char *s);
-static void print_jobs(launch_data_t j, const char *key, void *context);
-static void print_obj(launch_data_t obj, const char *key, void *context);
-static bool delay_to_second_pass(launch_data_t o);
-static void delay_to_second_pass2(launch_data_t o, const char *key, void *context);
-static bool str2lim(const char *buf, rlim_t *res);
-static const char *lim2str(rlim_t val, char *buf);
-static const char *num2name(int n);
-static ssize_t name2num(const char *n);
-static void unloadjob(launch_data_t job);
-static void print_key_value(launch_data_t obj, const char *key, void *context);
-static void print_launchd_env(launch_data_t obj, const char *key, void *context);
-static void _log_launchctl_bug(const char *rcs_rev, const char *path, unsigned int line, const char *test);
-static void loopback_setup_ipv4(void);
-static void loopback_setup_ipv6(void);
-static pid_t fwexec(const char *const *argv, int *wstatus);
-static void do_potential_fsck(void);
-static bool path_check(const char *path);
-static bool is_safeboot(void);
-static bool is_netboot(void);
-static void apply_sysctls_from_file(const char *thefile);
-static void empty_dir(const char *thedir, struct stat *psb);
-static int touch_file(const char *path, mode_t m);
-static void do_sysversion_sysctl(void);
-static void do_application_firewall_magic(int sfd, launch_data_t thejob);
-static void preheat_page_cache_hack(void);
-static void do_bootroot_magic(void);
-static void do_single_user_mode(bool);
-static bool do_single_user_mode2(void);
-static void do_crash_debug_mode(void);
-static bool do_crash_debug_mode2(void);
-static void read_launchd_conf(void);
-static void read_environment_dot_plist(void);
-static bool job_disabled_logic(launch_data_t obj);
-static void fix_bogus_file_metadata(void);
-static void do_file_init(void) __attribute__((constructor));
-static void setup_system_context(void);
-static void handle_system_bootstrapper_crashes_separately(void);
-static void fatal_signal_handler(int sig, siginfo_t *si, void *uap);
-
-typedef enum {
- BOOTCACHE_START = 1,
- BOOTCACHE_TAG,
- BOOTCACHE_STOP,
-} BootCache_action_t;
-
-static void do_BootCache_magic(BootCache_action_t what);
-
-static int bootstrap_cmd(int argc, char *const argv[]);
-static int load_and_unload_cmd(int argc, char *const argv[]);
-//static int reload_cmd(int argc, char *const argv[]);
-static int start_stop_remove_cmd(int argc, char *const argv[]);
-static int submit_cmd(int argc, char *const argv[]);
-static int list_cmd(int argc, char *const argv[]);
-
-static int setenv_cmd(int argc, char *const argv[]);
-static int unsetenv_cmd(int argc, char *const argv[]);
-static int getenv_and_export_cmd(int argc, char *const argv[]);
-static int wait4debugger_cmd(int argc, char *const argv[]);
-
-static int limit_cmd(int argc, char *const argv[]);
-static int stdio_cmd(int argc, char *const argv[]);
-static int fyi_cmd(int argc, char *const argv[]);
-static int logupdate_cmd(int argc, char *const argv[]);
-static int umask_cmd(int argc, char *const argv[]);
-static int getrusage_cmd(int argc, char *const argv[]);
-static int bsexec_cmd(int argc, char *const argv[]);
-static int _bslist_cmd(mach_port_t bport, unsigned int depth, bool show_job, bool local_only);
-static int bslist_cmd(int argc, char *const argv[]);
-static int _bstree_cmd(mach_port_t bsport, unsigned int depth, bool show_jobs);
-static int bstree_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused)));
-static int managerpid_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused)));
-static int manageruid_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused)));
-static int managername_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused)));
-static int asuser_cmd(int argc, char * const argv[]);
-static int exit_cmd(int argc, char *const argv[]) __attribute__((noreturn));
-static int help_cmd(int argc, char *const argv[]);
-
-static const struct {
- const char *name;
- int (*func)(int argc, char *const argv[]);
- const char *desc;
-} cmds[] = {
- { "load", load_and_unload_cmd, "Load configuration files and/or directories" },
- { "unload", load_and_unload_cmd, "Unload configuration files and/or directories" },
-// { "reload", reload_cmd, "Reload configuration files and/or directories" },
- { "start", start_stop_remove_cmd, "Start specified job" },
- { "stop", start_stop_remove_cmd, "Stop specified job" },
- { "submit", submit_cmd, "Submit a job from the command line" },
- { "remove", start_stop_remove_cmd, "Remove specified job" },
- { "bootstrap", bootstrap_cmd, "Bootstrap launchd" },
- { "list", list_cmd, "List jobs and information about jobs" },
- { "setenv", setenv_cmd, "Set an environmental variable in launchd" },
- { "unsetenv", unsetenv_cmd, "Unset an environmental variable in launchd" },
- { "getenv", getenv_and_export_cmd, "Get an environmental variable from launchd" },
- { "export", getenv_and_export_cmd, "Export shell settings from launchd" },
- { "debug", wait4debugger_cmd, "Set the WaitForDebugger flag for the target job to true." },
- { "limit", limit_cmd, "View and adjust launchd resource limits" },
- { "stdout", stdio_cmd, "Redirect launchd's standard out to the given path" },
- { "stderr", stdio_cmd, "Redirect launchd's standard error to the given path" },
- { "shutdown", fyi_cmd, "Prepare for system shutdown" },
- { "singleuser", fyi_cmd, "Switch to single-user mode" },
- { "getrusage", getrusage_cmd, "Get resource usage statistics from launchd" },
- { "log", logupdate_cmd, "Adjust the logging level or mask of launchd" },
- { "umask", umask_cmd, "Change launchd's umask" },
- { "bsexec", bsexec_cmd, "Execute a process within a different Mach bootstrap subset" },
- { "bslist", bslist_cmd, "List Mach bootstrap services and optional servers" },
- { "bstree", bstree_cmd, "Show the entire Mach bootstrap tree. Requires root privileges." },
- { "managerpid", managerpid_cmd, "Print the PID of the launchd managing this Mach bootstrap." },
- { "manageruid", manageruid_cmd, "Print the UID of the launchd managing this Mach bootstrap." },
- { "managername", managername_cmd, "Print the name of this Mach bootstrap." },
- { "asuser", asuser_cmd, "Execute a subcommand in the given user's context." },
- { "exit", exit_cmd, "Exit the interactive invocation of launchctl" },
- { "quit", exit_cmd, "Quit the interactive invocation of launchctl" },
- { "help", help_cmd, "This help output" },
-};
-
-static bool istty;
-static bool verbose;
-static bool is_managed;
-static bool do_apple_internal_magic;
-static bool system_context;
-static bool rootuser_context;
-static bool bootstrapping_system;
-static bool bootstrapping_peruser;
-static bool g_verbose_boot = false;
-static bool g_startup_debugging = false;
-
-static bool g_job_overrides_db_has_changed = false;
-static CFMutableDictionaryRef g_job_overrides_db = NULL;
-static char g_job_overrides_db_path[PATH_MAX];
-
-#if 0
-static bool g_job_cache_db_has_changed = false;
-static launch_data_t g_job_cache_db = NULL;
-static char g_job_cache_db_path[PATH_MAX];
-#endif
-
-int
-main(int argc, char *const argv[])
-{
- int64_t is_managed_val = 0;
- char *l;
-
- if (vproc_swap_integer(NULL, VPROC_GSK_IS_MANAGED, NULL, &is_managed_val) == NULL && is_managed_val) {
- is_managed = true;
- }
-
- istty = isatty(STDIN_FILENO);
- argc--, argv++;
-
- if (argc > 0 && argv[0][0] == '-') {
- char *flago;
-
- for (flago = argv[0] + 1; *flago; flago++) {
- switch (*flago) {
- case 'v':
- verbose = true;
- break;
- case 'u':
- if (argc > 1) {
- if (strncmp(argv[1], "root", sizeof("root")) == 0) {
- rootuser_context = true;
- } else {
- fprintf(stderr, "Unknown user: %s\n", argv[1]);
- exit(EXIT_FAILURE);
- }
- argc--, argv++;
- } else {
- fprintf(stderr, "-u option requires an argument.\n");
- }
- break;
- case '1':
- system_context = true;
- break;
- default:
- fprintf(stderr, "Unknown argument: '-%c'\n", *flago);
- break;
- }
- }
- argc--, argv++;
- }
-
- /* Running in the context of the root user's per-user launchd is only supported ... well
- * in the root user's per-user context. I know it's confusing. I'm genuinely sorry.
- */
- if (rootuser_context) {
- int64_t manager_uid = -1, manager_pid = -1;
- if (vproc_swap_integer(NULL, VPROC_GSK_MGR_UID, NULL, &manager_uid) == NULL) {
- if (vproc_swap_integer(NULL, VPROC_GSK_MGR_PID, NULL, &manager_pid) == NULL) {
- if (manager_uid || manager_pid == 1) {
- fprintf(stderr, "Running in the root user's per-user context is not supported outside of the root user's bootstrap.\n");
- exit(EXIT_FAILURE);
- }
- }
- }
- } else if (!(system_context || rootuser_context)) {
- /* Running in the system context is implied when we're running as root and not running as a bootstrapper. */
- system_context = (!is_managed && getuid() == 0);
- }
-
- if (system_context) {
- if (getuid() == 0) {
- setup_system_context();
- } else {
- fprintf(stderr, "You must be root to run in the system context.\n");
- exit(EXIT_FAILURE);
- }
- } else if (rootuser_context) {
- if (getuid() != 0) {
- fprintf(stderr, "You must be root to run in the root user context.\n");
- exit(EXIT_FAILURE);
- }
- }
-
- if (NULL == readline) {
- fprintf(stderr, "missing library: readline\n");
- exit(EXIT_FAILURE);
- }
-
- if (argc == 0) {
- while ((l = readline(istty ? "launchd% " : NULL))) {
- char *inputstring = l, *argv2[100], **ap = argv2;
- int i = 0;
-
- while ((*ap = strsep(&inputstring, " \t"))) {
- if (**ap != '\0') {
- ap++;
- i++;
- }
- }
-
- if (i > 0) {
- demux_cmd(i, argv2);
- }
-
- free(l);
- }
-
- if (istty) {
- fputc('\n', stdout);
- }
- }
-
- if (argc > 0) {
- exit(demux_cmd(argc, argv));
- }
-
- exit(EXIT_SUCCESS);
-}
-
-int
-demux_cmd(int argc, char *const argv[])
-{
- size_t i;
-
- optind = 1;
- optreset = 1;
-
- for (i = 0; i < (sizeof cmds / sizeof cmds[0]); i++) {
- if (!strcmp(cmds[i].name, argv[0])) {
- return cmds[i].func(argc, argv);
- }
- }
-
- fprintf(stderr, "%s: unknown subcommand \"%s\"\n", getprogname(), argv[0]);
- return 1;
-}
-
-void
-read_launchd_conf(void)
-{
- char s[1000], *c, *av[100];
- const char *file;
- size_t len;
- int i;
- FILE *f;
-
- if (getppid() == 1) {
- file = "/etc/launchd.conf";
- } else {
- file = "/etc/launchd-user.conf";
- }
-
- if (!(f = fopen(file, "r"))) {
- return;
- }
-
- while ((c = fgets(s, (int) sizeof s, f))) {
- len = strlen(c);
- if (len && c[len - 1] == '\n') {
- c[len - 1] = '\0';
- }
-
- i = 0;
-
- while ((av[i] = strsep(&c, " \t"))) {
- if (*(av[i]) != '\0') {
- i++;
- }
- }
-
- if (i > 0) {
- demux_cmd(i, av);
- }
- }
-
- fclose(f);
-}
-
-CFPropertyListRef CFPropertyListCreateFromFile(CFURLRef plistURL)
-{
- CFReadStreamRef plistReadStream = CFReadStreamCreateWithFile(NULL, plistURL);
-
- CFErrorRef streamErr = NULL;
- if (!CFReadStreamOpen(plistReadStream)) {
- streamErr = CFReadStreamCopyError(plistReadStream);
- CFStringRef errString = CFErrorCopyDescription(streamErr);
-
- CFShow(errString);
-
- CFRelease(errString);
- CFRelease(streamErr);
- }
-
- CFPropertyListRef plist = NULL;
- if (plistReadStream) {
- CFStringRef errString = NULL;
- CFPropertyListFormat plistFormat = 0;
- plist = CFPropertyListCreateFromStream(NULL, plistReadStream, 0, kCFPropertyListImmutable, &plistFormat, &errString);
- if (!plist) {
- CFShow(errString);
- CFRelease(errString);
- }
- }
-
- CFReadStreamClose(plistReadStream);
- CFRelease(plistReadStream);
-
- return plist;
-}
-
-static void
-sanitize_environment_dot_plist(const launch_data_t val, const char *key, void *ctx)
-{
- launch_data_t copy = (launch_data_t)ctx;
- if (strncmp("DYLD_", key, sizeof("DYLD_") - 1) != 0) {
- launch_data_t copyi = launch_data_copy(val);
- launch_data_dict_insert(copy, copyi, key);
- }
-}
-
-#define CFReleaseIfNotNULL(cf) if (cf) CFRelease(cf);
-void
-read_environment_dot_plist(void)
-{
- CFStringRef plistPath = NULL;
- CFURLRef plistURL = NULL;
- CFDictionaryRef envPlist = NULL;
- launch_data_t req = NULL, launch_env_dict = NULL, resp = NULL;
-
- char plist_path_str[PATH_MAX];
- plist_path_str[PATH_MAX - 1] = 0;
- snprintf(plist_path_str, sizeof(plist_path_str), "%s/.MacOSX/environment.plist", getenv("HOME"));
-
- struct stat sb;
- if (stat(plist_path_str, &sb) == -1) {
- goto out;
- }
-
- plistPath = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), plist_path_str);
- if (!assumes(plistPath != NULL)) {
- goto out;
- }
-
- plistURL = CFURLCreateWithFileSystemPath(NULL, plistPath, kCFURLPOSIXPathStyle, false);
- if (!assumes(plistURL != NULL)) {
- goto out;
- }
-
- envPlist = (CFDictionaryRef)CFPropertyListCreateFromFile(plistURL);
- if (!assumes(envPlist != NULL)) {
- goto out;
- }
-
- launch_env_dict = CF2launch_data(envPlist);
- if (!assumes(launch_env_dict != NULL)) {
- goto out;
- }
-
- launch_data_t sanitized = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
- if (!assumes(sanitized != NULL)) {
- goto out;
- }
-
- launch_data_dict_iterate(launch_env_dict, sanitize_environment_dot_plist, sanitized);
- launch_data_free(launch_env_dict);
-
- req = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
- if (!assumes(req != NULL)) {
- goto out;
- }
-
- launch_data_dict_insert(req, sanitized, LAUNCH_KEY_SETUSERENVIRONMENT);
-
- resp = launch_msg(req);
- if (!assumes(resp != NULL)) {
- goto out;
- }
-
- if (!assumes(launch_data_get_type(resp) == LAUNCH_DATA_ERRNO)) {
- goto out;
- }
-
- (void)assumes(launch_data_get_errno(resp) == 0);
-out:
- CFReleaseIfNotNULL(plistPath);
- CFReleaseIfNotNULL(plistURL);
- CFReleaseIfNotNULL(envPlist);
- if (req) {
- launch_data_free(req);
- }
-
- if (resp) {
- launch_data_free(resp);
- }
-}
-
-int
-unsetenv_cmd(int argc, char *const argv[])
-{
- launch_data_t resp, tmp, msg;
-
- if (argc != 2) {
- fprintf(stderr, "%s usage: unsetenv <key>\n", getprogname());
- return 1;
- }
-
- msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
-
- tmp = launch_data_new_string(argv[1]);
- launch_data_dict_insert(msg, tmp, LAUNCH_KEY_UNSETUSERENVIRONMENT);
-
- resp = launch_msg(msg);
-
- launch_data_free(msg);
-
- if (resp) {
- launch_data_free(resp);
- } else {
- fprintf(stderr, "launch_msg(\"%s\"): %s\n", LAUNCH_KEY_UNSETUSERENVIRONMENT, strerror(errno));
- }
-
- return 0;
-}
-
-int
-setenv_cmd(int argc, char *const argv[])
-{
- launch_data_t resp, tmp, tmpv, msg;
-
- if (argc != 3) {
- fprintf(stderr, "%s usage: setenv <key> <value>\n", getprogname());
- return 1;
- }
-
- msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
- tmp = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
-
- tmpv = launch_data_new_string(argv[2]);
- launch_data_dict_insert(tmp, tmpv, argv[1]);
- launch_data_dict_insert(msg, tmp, LAUNCH_KEY_SETUSERENVIRONMENT);
-
- resp = launch_msg(msg);
- launch_data_free(msg);
-
- if (resp) {
- launch_data_free(resp);
- } else {
- fprintf(stderr, "launch_msg(\"%s\"): %s\n", LAUNCH_KEY_SETUSERENVIRONMENT, strerror(errno));
- }
-
- return 0;
-}
-
-void
-print_launchd_env(launch_data_t obj, const char *key, void *context)
-{
- bool *is_csh = context;
-
- /* XXX escape the double quotes */
- if (*is_csh) {
- fprintf(stdout, "setenv %s \"%s\";\n", key, launch_data_get_string(obj));
- } else {
- fprintf(stdout, "%s=\"%s\"; export %s;\n", key, launch_data_get_string(obj), key);
- }
-}
-
-void
-print_key_value(launch_data_t obj, const char *key, void *context)
-{
- const char *k = context;
-
- if (!strcmp(key, k)) {
- fprintf(stdout, "%s\n", launch_data_get_string(obj));
- }
-}
-
-int
-getenv_and_export_cmd(int argc, char *const argv[])
-{
- launch_data_t resp;
- bool is_csh = false;
- char *k;
-
- if (!strcmp(argv[0], "export")) {
- char *s = getenv("SHELL");
- if (s) {
- is_csh = strstr(s, "csh") ? true : false;
- }
- } else if (argc != 2) {
- fprintf(stderr, "%s usage: getenv <key>\n", getprogname());
- return 1;
- }
-
- k = argv[1];
-
- if (vproc_swap_complex(NULL, VPROC_GSK_ENVIRONMENT, NULL, &resp) == NULL) {
- if (!strcmp(argv[0], "export")) {
- launch_data_dict_iterate(resp, print_launchd_env, &is_csh);
- } else {
- launch_data_dict_iterate(resp, print_key_value, k);
- }
- launch_data_free(resp);
- return 0;
- } else {
- return 1;
- }
-
- return 0;
-}
-
-int
-wait4debugger_cmd(int argc, char * const argv[])
-{
- if (argc != 3) {
- fprintf(stderr, "%s usage: debug <label> <value>\n", argv[0]);
- return 1;
- }
-
- int result = 1;
- int64_t inval = 0;
- if (strncmp(argv[2], "true", sizeof("true")) == 0) {
- inval = 1;
- } else if (strncmp(argv[2], "false", sizeof("false")) != 0) {
- inval = atoi(argv[2]);
- inval &= 1;
- }
-
- vproc_t vp = vprocmgr_lookup_vproc(argv[1]);
- if (vp) {
- vproc_err_t verr = vproc_swap_integer(vp, VPROC_GSK_WAITFORDEBUGGER, &inval, NULL);
- if (verr) {
- fprintf(stderr, "Failed to set WaitForDebugger flag on %s.\n", argv[1]);
- } else {
- result = 0;
- }
- vproc_release(vp);
- }
-
- return result;
-}
-
-void
-unloadjob(launch_data_t job)
-{
- launch_data_t tmps;
-
- tmps = launch_data_dict_lookup(job, LAUNCH_JOBKEY_LABEL);
-
- if (!tmps) {
- fprintf(stderr, "%s: Error: Missing Key: %s\n", getprogname(), LAUNCH_JOBKEY_LABEL);
- return;
- }
-
- if (_vproc_send_signal_by_label(launch_data_get_string(tmps), VPROC_MAGIC_UNLOAD_SIGNAL) != NULL) {
- fprintf(stderr, "%s: Error unloading: %s\n", getprogname(), launch_data_get_string(tmps));
- }
-}
-
-void
-job_override(CFTypeRef key, CFTypeRef val, CFMutableDictionaryRef job)
-{
- if (!CFTypeCheck(key, CFString)) {
- return;
- }
- if (CFStringCompare(key, CFSTR(LAUNCH_JOBKEY_LABEL), kCFCompareCaseInsensitive) == 0) {
- return;
- }
-
- CFDictionarySetValue(job, key, val);
-}
-
-launch_data_t
-read_plist_file(const char *file, bool editondisk, bool load)
-{
- CFPropertyListRef plist = CreateMyPropertyListFromFile(file);
- launch_data_t r = NULL;
-
- if (NULL == plist) {
- fprintf(stderr, "%s: no plist was returned for: %s\n", getprogname(), file);
- return NULL;
- }
-
- CFStringRef label = CFDictionaryGetValue(plist, CFSTR(LAUNCH_JOBKEY_LABEL));
- if (g_job_overrides_db && label && CFTypeCheck(label, CFString)) {
- CFDictionaryRef overrides = CFDictionaryGetValue(g_job_overrides_db, label);
- if (overrides && CFTypeCheck(overrides, CFDictionary)) {
- CFDictionaryApplyFunction(overrides, (CFDictionaryApplierFunction)job_override, (void *)plist);
- }
- }
-
- if (editondisk) {
- if (g_job_overrides_db) {
- CFMutableDictionaryRef job = (CFMutableDictionaryRef)CFDictionaryGetValue(g_job_overrides_db, label);
- if (!job || !CFTypeCheck(job, CFDictionary)) {
- job = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
- CFDictionarySetValue(g_job_overrides_db, label, job);
- CFRelease(job);
- }
-
- CFDictionarySetValue(job, CFSTR(LAUNCH_JOBKEY_DISABLED), load ? kCFBooleanFalse : kCFBooleanTrue);
- CFDictionarySetValue((CFMutableDictionaryRef)plist, CFSTR(LAUNCH_JOBKEY_DISABLED), load ? kCFBooleanFalse : kCFBooleanTrue);
- g_job_overrides_db_has_changed = true;
- } else {
- if (load) {
- CFDictionaryRemoveValue((CFMutableDictionaryRef)plist, CFSTR(LAUNCH_JOBKEY_DISABLED));
- } else {
- CFDictionarySetValue((CFMutableDictionaryRef)plist, CFSTR(LAUNCH_JOBKEY_DISABLED), kCFBooleanTrue);
- }
- WriteMyPropertyListToFile(plist, file);
- }
- }
-
- r = CF2launch_data(plist);
-
- CFRelease(plist);
-
- return r;
-}
-
-void
-delay_to_second_pass2(launch_data_t o, const char *key, void *context)
-{
- bool *res = context;
- size_t i;
-
- if (key && 0 == strcmp(key, LAUNCH_JOBSOCKETKEY_BONJOUR)) {
- *res = true;
- return;
- }
-
- switch (launch_data_get_type(o)) {
- case LAUNCH_DATA_DICTIONARY:
- launch_data_dict_iterate(o, delay_to_second_pass2, context);
- break;
- case LAUNCH_DATA_ARRAY:
- for (i = 0; i < launch_data_array_get_count(o); i++) {
- delay_to_second_pass2(launch_data_array_get_index(o, i), NULL, context);
- }
- break;
- default:
- break;
- }
-}
-
-bool
-delay_to_second_pass(launch_data_t o)
-{
- bool res = false;
-
- launch_data_t socks = launch_data_dict_lookup(o, LAUNCH_JOBKEY_SOCKETS);
-
- if (NULL == socks) {
- return false;
- }
-
- delay_to_second_pass2(socks, NULL, &res);
-
- return res;
-}
-
-static bool
-sysctl_hw_streq(int mib_slot, const char *str)
-{
- char buf[1000];
- size_t bufsz = sizeof(buf);
- int mib[] = { CTL_HW, mib_slot };
-
- if (sysctl(mib, 2, buf, &bufsz, NULL, 0) != -1) {
- if (strcmp(buf, str) == 0) {
- return true;
- }
- }
-
- return false;
-}
-
-static void
-limitloadtohardware_iterator(launch_data_t val, const char *key, void *ctx)
-{
- bool *result = ctx;
-
- char name[128];
- (void)snprintf(name, sizeof(name), "hw.%s", key);
-
- int mib[2];
- size_t sz = 2;
- if (*result != true && assumes(sysctlnametomib(name, mib, &sz) != -1)) {
- if (launch_data_get_type(val) == LAUNCH_DATA_ARRAY) {
- size_t c = launch_data_array_get_count(val);
-
- size_t i = 0;
- for (i = 0; i < c; i++) {
- launch_data_t oai = launch_data_array_get_index(val, i);
- if (sysctl_hw_streq(mib[1], launch_data_get_string(oai))) {
- *result = true;
- i = c;
- }
- }
- }
- }
-}
-
-void
-readfile(const char *what, struct load_unload_state *lus)
-{
- char ourhostname[1024];
- launch_data_t tmpd, tmps, thejob, tmpa;
- bool job_disabled = false;
- size_t i, c;
-
- gethostname(ourhostname, sizeof(ourhostname));
-
- if (NULL == (thejob = read_plist_file(what, lus->editondisk, lus->load))) {
- fprintf(stderr, "%s: no plist was returned for: %s\n", getprogname(), what);
- return;
- }
-
-
- if (NULL == launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LABEL)) {
- fprintf(stderr, "%s: missing the Label key: %s\n", getprogname(), what);
- goto out_bad;
- }
-
- if ((launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_PROGRAM) == NULL) &&
- (launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_PROGRAMARGUMENTS) == NULL)) {
- fprintf(stderr, "%s: neither a Program nor a ProgramArguments key was specified: %s", getprogname(), what);
- goto out_bad;
- }
-
- if (NULL != (tmpa = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADFROMHOSTS))) {
- c = launch_data_array_get_count(tmpa);
-
- for (i = 0; i < c; i++) {
- launch_data_t oai = launch_data_array_get_index(tmpa, i);
- if (!strcasecmp(ourhostname, launch_data_get_string(oai))) {
- goto out_bad;
- }
- }
- }
-
- if (NULL != (tmpa = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADTOHOSTS))) {
- c = launch_data_array_get_count(tmpa);
-
- for (i = 0; i < c; i++) {
- launch_data_t oai = launch_data_array_get_index(tmpa, i);
- if (!strcasecmp(ourhostname, launch_data_get_string(oai))) {
- break;
- }
- }
-
- if (i == c) {
- goto out_bad;
- }
- }
-
- if (NULL != (tmpd = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADTOHARDWARE))) {
- bool result = false;
- launch_data_dict_iterate(tmpd, limitloadtohardware_iterator, &result);
- if (!result) {
- goto out_bad;
- }
- }
-
- if (NULL != (tmpd = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADFROMHARDWARE))) {
- bool result = false;
- launch_data_dict_iterate(tmpd, limitloadtohardware_iterator, &result);
- if (result) {
- goto out_bad;
- }
- }
-
- // if the manager is Aqua, the LimitLoadToSessionType should default to 'Aqua'
- // fixes <rdar://problem/8297909>
- char *manager = "Bogus";
- vproc_swap_string(NULL, VPROC_GSK_MGR_NAME, NULL, &manager);
- if (!lus->session_type) {
- if (strcmp(manager, "Aqua") == 0) {
- lus->session_type = "Aqua";
- }
- }
-
- if (lus->session_type && !(tmpa = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE))) {
- tmpa = launch_data_new_string("Aqua");
- launch_data_dict_insert(thejob, tmpa, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE);
- }
-
- if ((tmpa = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE))) {
- const char *allowed_session;
- bool skipjob = true;
-
- /* My sincere apologies to anyone who has to deal with this
- * LimitLoadToSessionType madness. It was like this when I got here, but
- * I've knowingly made it worse, hopefully to the benefit of the end
- * user.
- *
- * See <rdar://problem/8769211> and <rdar://problem/7114980>.
- */
- if (!lus->session_type && launch_data_get_type(tmpa) == LAUNCH_DATA_STRING) {
- if (strcasecmp("System", manager) == 0 && strcasecmp("System", launch_data_get_string(tmpa)) == 0) {
- skipjob = false;
- }
- }
-
- if (lus->session_type) switch (launch_data_get_type(tmpa)) {
- case LAUNCH_DATA_ARRAY:
- c = launch_data_array_get_count(tmpa);
- for (i = 0; i < c; i++) {
- tmps = launch_data_array_get_index(tmpa, i);
- allowed_session = launch_data_get_string(tmps);
- if (strcasecmp(lus->session_type, allowed_session) == 0) {
- skipjob = false;
- /* we have to do the following so job_reparent_hack() works within launchd */
- tmpa = launch_data_new_string(lus->session_type);
- launch_data_dict_insert(thejob, tmpa, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE);
- break;
- }
- }
- break;
- case LAUNCH_DATA_STRING:
- allowed_session = launch_data_get_string(tmpa);
- if (strcasecmp(lus->session_type, allowed_session) == 0) {
- skipjob = false;
- }
- break;
- default:
- break;
- }
-
- if (skipjob) {
- goto out_bad;
- }
- }
-
- if ((tmpd = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_DISABLED))) {
- job_disabled = job_disabled_logic(tmpd);
- }
-
- if (lus->forceload) {
- job_disabled = false;
- }
-
- if (job_disabled && lus->load) {
- goto out_bad;
- }
-
- if (bootstrapping_system || bootstrapping_peruser) {
- uuid_t uuid;
- uuid_clear(uuid);
-
- launch_data_t uuid_d = launch_data_new_opaque(uuid, sizeof(uuid_t));
- launch_data_dict_insert(thejob, uuid_d, LAUNCH_JOBKEY_SECURITYSESSIONUUID);
- }
-
- if (delay_to_second_pass(thejob)) {
- launch_data_array_append(lus->pass2, thejob);
- } else {
- launch_data_array_append(lus->pass1, thejob);
- }
-
- if (verbose) {
- fprintf(stdout, "Will load: %s\n", what);
- }
-
- return;
-out_bad:
- if (verbose) {
- fprintf(stdout, "Ignored: %s\n", what);
- }
- launch_data_free(thejob);
-}
-
-static void
-job_disabled_dict_logic(launch_data_t obj, const char *key, void *context)
-{
- bool *r = context;
-
- if (launch_data_get_type(obj) != LAUNCH_DATA_STRING) {
- return;
- }
-
- if (strcasecmp(key, LAUNCH_JOBKEY_DISABLED_MACHINETYPE) == 0) {
- if (sysctl_hw_streq(HW_MACHINE, launch_data_get_string(obj))) {
- *r = true;
- }
- } else if (strcasecmp(key, LAUNCH_JOBKEY_DISABLED_MODELNAME) == 0) {
- if (sysctl_hw_streq(HW_MODEL, launch_data_get_string(obj))) {
- *r = true;
- }
- }
-}
-
-bool
-job_disabled_logic(launch_data_t obj)
-{
- bool r = false;
-
- switch (launch_data_get_type(obj)) {
- case LAUNCH_DATA_DICTIONARY:
- launch_data_dict_iterate(obj, job_disabled_dict_logic, &r);
- break;
- case LAUNCH_DATA_BOOL:
- r = launch_data_get_bool(obj);
- break;
- default:
- break;
- }
-
- return r;
-}
-
-bool
-path_goodness_check(const char *path, bool forceload)
-{
- struct stat sb;
-
- if (stat(path, &sb) == -1) {
- fprintf(stderr, "%s: Couldn't stat(\"%s\"): %s\n", getprogname(), path, strerror(errno));
- return false;
- }
-
- if (forceload) {
- return true;
- }
-
- if (sb.st_mode & (S_IWOTH|S_IWGRP)) {
- fprintf(stderr, "%s: Dubious permissions on file (skipping): %s\n", getprogname(), path);
- return false;
- }
-
- if (sb.st_uid != 0 && sb.st_uid != getuid()) {
- fprintf(stderr, "%s: Dubious ownership on file (skipping): %s\n", getprogname(), path);
- return false;
- }
-
- if (!(S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode))) {
- fprintf(stderr, "%s: Dubious path. Not a regular file or directory (skipping): %s\n", getprogname(), path);
- return false;
- }
-
- if ((!S_ISDIR(sb.st_mode)) && (fnmatch("*.plist", path, FNM_CASEFOLD) == FNM_NOMATCH)) {
- fprintf(stderr, "%s: Dubious file. Not of type .plist (skipping): %s\n", getprogname(), path);
- return false;
- }
-
- return true;
-}
-
-void
-readpath(const char *what, struct load_unload_state *lus)
-{
- char buf[MAXPATHLEN];
- struct stat sb;
- struct dirent *de;
- DIR *d;
-
- if (!path_goodness_check(what, lus->forceload)) {
- return;
- }
-
- if (stat(what, &sb) == -1) {
- return;
- }
-
- if (S_ISREG(sb.st_mode)) {
- readfile(what, lus);
- } else if (S_ISDIR(sb.st_mode)) {
- if ((d = opendir(what)) == NULL) {
- fprintf(stderr, "%s: opendir() failed to open the directory\n", getprogname());
- return;
- }
-
- while ((de = readdir(d))) {
- if ((de->d_name[0] == '.')) {
- continue;
- }
- snprintf(buf, sizeof(buf), "%s/%s", what, de->d_name);
-
- if (!path_goodness_check(buf, lus->forceload)) {
- continue;
- }
-
- readfile(buf, lus);
- }
- closedir(d);
- }
-}
-
-struct distill_context {
- launch_data_t base;
- launch_data_t newsockdict;
-};
-
-void
-distill_jobs(launch_data_t jobs)
-{
- size_t i, c = launch_data_array_get_count(jobs);
-
- for (i = 0; i < c; i++)
- distill_config_file(launch_data_array_get_index(jobs, i));
-}
-
-void
-distill_config_file(launch_data_t id_plist)
-{
- struct distill_context dc = { id_plist, NULL };
- launch_data_t tmp;
-
- if ((tmp = launch_data_dict_lookup(dc.base, LAUNCH_JOBKEY_SOCKETS))) {
- dc.newsockdict = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
- launch_data_dict_iterate(tmp, sock_dict_cb, &dc);
- launch_data_dict_insert(dc.base, dc.newsockdict, LAUNCH_JOBKEY_SOCKETS);
- }
-}
-
-void
-sock_dict_cb(launch_data_t what, const char *key, void *context)
-{
- struct distill_context *dc = context;
- launch_data_t fdarray = launch_data_alloc(LAUNCH_DATA_ARRAY);
-
- launch_data_dict_insert(dc->newsockdict, fdarray, key);
-
- if (launch_data_get_type(what) == LAUNCH_DATA_DICTIONARY) {
- sock_dict_edit_entry(what, key, fdarray, dc->base);
- } else if (launch_data_get_type(what) == LAUNCH_DATA_ARRAY) {
- launch_data_t tmp;
- size_t i;
-
- for (i = 0; i < launch_data_array_get_count(what); i++) {
- tmp = launch_data_array_get_index(what, i);
- sock_dict_edit_entry(tmp, key, fdarray, dc->base);
- }
- }
-}
-
-void
-sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data_t fdarray, launch_data_t thejob)
-{
- launch_data_t a, val;
- int sfd, st = SOCK_STREAM;
- bool passive = true;
-
- if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_TYPE))) {
- if (!strcasecmp(launch_data_get_string(val), "stream")) {
- st = SOCK_STREAM;
- } else if (!strcasecmp(launch_data_get_string(val), "dgram")) {
- st = SOCK_DGRAM;
- } else if (!strcasecmp(launch_data_get_string(val), "seqpacket")) {
- st = SOCK_SEQPACKET;
- }
- }
-
- if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_PASSIVE))) {
- passive = launch_data_get_bool(val);
- }
-
- if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_SECUREWITHKEY))) {
- char secdir[] = LAUNCH_SECDIR, buf[1024];
- launch_data_t uenv = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES);
-
- if (NULL == uenv) {
- uenv = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
- launch_data_dict_insert(thejob, uenv, LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES);
- }
-
- mkdtemp(secdir);
-
- sprintf(buf, "%s/%s", secdir, key);
-
- a = launch_data_new_string(buf);
- launch_data_dict_insert(tmp, a, LAUNCH_JOBSOCKETKEY_PATHNAME);
- a = launch_data_new_string(buf);
- launch_data_dict_insert(uenv, a, launch_data_get_string(val));
- }
-
- if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_PATHNAME))) {
- struct sockaddr_un sun;
- mode_t sun_mode = 0;
- mode_t oldmask;
- bool setm = false;
-
- memset(&sun, 0, sizeof(sun));
-
- sun.sun_family = AF_UNIX;
-
- strncpy(sun.sun_path, launch_data_get_string(val), sizeof(sun.sun_path));
-
- if ((sfd = _fd(socket(AF_UNIX, st, 0))) == -1) {
- return;
- }
-
- if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_PATHMODE))) {
- sun_mode = (mode_t)launch_data_get_integer(val);
- setm = true;
- }
-
- if (passive) {
- if (unlink(sun.sun_path) == -1 && errno != ENOENT) {
- close(sfd);
- return;
- }
- oldmask = umask(S_IRWXG|S_IRWXO);
- if (bind(sfd, (struct sockaddr *)&sun, (socklen_t) sizeof sun) == -1) {
- close(sfd);
- umask(oldmask);
- return;
- }
- umask(oldmask);
- if (setm) {
- chmod(sun.sun_path, sun_mode);
- }
- if ((st == SOCK_STREAM || st == SOCK_SEQPACKET) && listen(sfd, -1) == -1) {
- close(sfd);
- return;
- }
- } else if (connect(sfd, (struct sockaddr *)&sun, (socklen_t) sizeof sun) == -1) {
- close(sfd);
- return;
- }
-
- val = launch_data_new_fd(sfd);
- launch_data_array_append(fdarray, val);
- } else {
- launch_data_t rnames = NULL;
- const char *node = NULL, *serv = NULL, *mgroup = NULL;
- char servnbuf[50];
- struct addrinfo hints, *res0, *res;
- int gerr, sock_opt = 1;
- bool rendezvous = false;
-
- memset(&hints, 0, sizeof(hints));
-
- hints.ai_socktype = st;
- if (passive) {
- hints.ai_flags |= AI_PASSIVE;
- }
-
- if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_NODENAME))) {
- node = launch_data_get_string(val);
- }
- if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_MULTICASTGROUP))) {
- mgroup = launch_data_get_string(val);
- }
- if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_SERVICENAME))) {
- if (LAUNCH_DATA_INTEGER == launch_data_get_type(val)) {
- sprintf(servnbuf, "%lld", launch_data_get_integer(val));
- serv = servnbuf;
- } else {
- serv = launch_data_get_string(val);
- }
- }
- if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_FAMILY))) {
- if (!strcasecmp("IPv4", launch_data_get_string(val))) {
- hints.ai_family = AF_INET;
- } else if (!strcasecmp("IPv6", launch_data_get_string(val))) {
- hints.ai_family = AF_INET6;
- }
- }
- if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_PROTOCOL))) {
- if (!strcasecmp("TCP", launch_data_get_string(val))) {
- hints.ai_protocol = IPPROTO_TCP;
- } else if (!strcasecmp("UDP", launch_data_get_string(val))) {
- hints.ai_protocol = IPPROTO_UDP;
- }
- }
- if ((rnames = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_BONJOUR))) {
- rendezvous = true;
- if (LAUNCH_DATA_BOOL == launch_data_get_type(rnames)) {
- rendezvous = launch_data_get_bool(rnames);
- rnames = NULL;
- }
- }
-
- if ((gerr = getaddrinfo(node, serv, &hints, &res0)) != 0) {
- fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(gerr));
- return;
- }
-
- for (res = res0; res; res = res->ai_next) {
- launch_data_t rvs_fd = NULL;
- if ((sfd = _fd(socket(res->ai_family, res->ai_socktype, res->ai_protocol))) == -1) {
- fprintf(stderr, "socket(): %s\n", strerror(errno));
- return;
- }
-
- do_application_firewall_magic(sfd, thejob);
-
- if (hints.ai_flags & AI_PASSIVE) {
- if (AF_INET6 == res->ai_family && -1 == setsockopt(sfd, IPPROTO_IPV6, IPV6_V6ONLY,
- (void *)&sock_opt, (socklen_t) sizeof sock_opt)) {
- fprintf(stderr, "setsockopt(IPV6_V6ONLY): %m");
- return;
- }
- if (mgroup) {
- if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, (void *)&sock_opt, (socklen_t) sizeof sock_opt) == -1) {
- fprintf(stderr, "setsockopt(SO_REUSEPORT): %s\n", strerror(errno));
- return;
- }
- } else {
- if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&sock_opt, (socklen_t) sizeof sock_opt) == -1) {
- fprintf(stderr, "setsockopt(SO_REUSEADDR): %s\n", strerror(errno));
- return;
- }
- }
- if (bind(sfd, res->ai_addr, res->ai_addrlen) == -1) {
- fprintf(stderr, "bind(): %s\n", strerror(errno));
- return;
- }
- /* The kernel may have dynamically assigned some part of the
- * address. (The port being a common example.)
- */
- if (getsockname(sfd, res->ai_addr, &res->ai_addrlen) == -1) {
- fprintf(stderr, "getsockname(): %s\n", strerror(errno));
- return;
- }
-
- if (mgroup) {
- do_mgroup_join(sfd, res->ai_family, res->ai_socktype, res->ai_protocol, mgroup);
- }
- if ((res->ai_socktype == SOCK_STREAM || res->ai_socktype == SOCK_SEQPACKET) && listen(sfd, -1) == -1) {
- fprintf(stderr, "listen(): %s\n", strerror(errno));
- return;
- }
- if (rendezvous && (res->ai_family == AF_INET || res->ai_family == AF_INET6) &&
- (res->ai_socktype == SOCK_STREAM || res->ai_socktype == SOCK_DGRAM)) {
- launch_data_t rvs_fds = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_BONJOURFDS);
- if (NULL == rvs_fds) {
- rvs_fds = launch_data_alloc(LAUNCH_DATA_ARRAY);
- launch_data_dict_insert(thejob, rvs_fds, LAUNCH_JOBKEY_BONJOURFDS);
- }
- if (NULL == rnames) {
- rvs_fd = do_rendezvous_magic(res, serv);
- if (rvs_fd) {
- launch_data_array_append(rvs_fds, rvs_fd);
- }
- } else if (LAUNCH_DATA_STRING == launch_data_get_type(rnames)) {
- rvs_fd = do_rendezvous_magic(res, launch_data_get_string(rnames));
- if (rvs_fd) {
- launch_data_array_append(rvs_fds, rvs_fd);
- }
- } else if (LAUNCH_DATA_ARRAY == launch_data_get_type(rnames)) {
- size_t rn_i, rn_ac = launch_data_array_get_count(rnames);
-
- for (rn_i = 0; rn_i < rn_ac; rn_i++) {
- launch_data_t rn_tmp = launch_data_array_get_index(rnames, rn_i);
-
- rvs_fd = do_rendezvous_magic(res, launch_data_get_string(rn_tmp));
- if (rvs_fd) {
- launch_data_array_append(rvs_fds, rvs_fd);
- }
- }
- }
- }
- } else {
- if (connect(sfd, res->ai_addr, res->ai_addrlen) == -1) {
- fprintf(stderr, "connect(): %s\n", strerror(errno));
- return;
- }
- }
- val = launch_data_new_fd(sfd);
- if (rvs_fd) {
- /* <rdar://problem/3964648> Launchd should not register the same service more than once */
- /* <rdar://problem/3965154> Switch to DNSServiceRegisterAddrInfo() */
- rendezvous = false;
- }
- launch_data_array_append(fdarray, val);
- }
- }
-}
-
-void
-do_mgroup_join(int fd, int family, int socktype, int protocol, const char *mgroup)
-{
- struct addrinfo hints, *res0, *res;
- struct ip_mreq mreq;
- struct ipv6_mreq m6req;
- int gerr;
-
- memset(&hints, 0, sizeof(hints));
-
- hints.ai_flags |= AI_PASSIVE;
- hints.ai_family = family;
- hints.ai_socktype = socktype;
- hints.ai_protocol = protocol;
-
- if ((gerr = getaddrinfo(mgroup, NULL, &hints, &res0)) != 0) {
- fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(gerr));
- return;
- }
-
- for (res = res0; res; res = res->ai_next) {
- if (AF_INET == family) {
- memset(&mreq, 0, sizeof(mreq));
- mreq.imr_multiaddr = ((struct sockaddr_in *)res->ai_addr)->sin_addr;
- if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, (socklen_t) sizeof mreq) == -1) {
- fprintf(stderr, "setsockopt(IP_ADD_MEMBERSHIP): %s\n", strerror(errno));
- continue;
- }
- break;
- } else if (AF_INET6 == family) {
- memset(&m6req, 0, sizeof(m6req));
- m6req.ipv6mr_multiaddr = ((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
- if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &m6req, (socklen_t) sizeof m6req) == -1) {
- fprintf(stderr, "setsockopt(IPV6_JOIN_GROUP): %s\n", strerror(errno));
- continue;
- }
- break;
- } else {
- fprintf(stderr, "unknown family during multicast group bind!\n");
- break;
- }
- }
-
- freeaddrinfo(res0);
-}
-
-
-launch_data_t
-do_rendezvous_magic(const struct addrinfo *res, const char *serv)
-{
- struct stat sb;
- DNSServiceRef service;
- DNSServiceErrorType error;
- char rvs_buf[200];
- short port;
- static int statres = 1;
-
- if (1 == statres) {
- statres = stat("/usr/sbin/mDNSResponder", &sb);
- }
-
- if (-1 == statres) {
- return NULL;
- }
-
- sprintf(rvs_buf, "_%s._%s.", serv, res->ai_socktype == SOCK_STREAM ? "tcp" : "udp");
-
- if (res->ai_family == AF_INET) {
- port = ((struct sockaddr_in *)res->ai_addr)->sin_port;
- } else {
- port = ((struct sockaddr_in6 *)res->ai_addr)->sin6_port;
- }
-
- error = DNSServiceRegister(&service, 0, 0, NULL, rvs_buf, NULL, NULL, port, 0, NULL, NULL, NULL);
-
- if (error == kDNSServiceErr_NoError) {
- return launch_data_new_fd(DNSServiceRefSockFD(service));
- }
-
- fprintf(stderr, "DNSServiceRegister(\"%s\"): %d\n", serv, error);
- return NULL;
-}
-
-CFPropertyListRef
-CreateMyPropertyListFromFile(const char *posixfile)
-{
- CFPropertyListRef propertyList;
- CFStringRef errorString;
- CFDataRef resourceData;
- SInt32 errorCode;
- CFURLRef fileURL;
-
- fileURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)posixfile, strlen(posixfile), false);
- if (!fileURL) {
- fprintf(stderr, "%s: CFURLCreateFromFileSystemRepresentation(%s) failed\n", getprogname(), posixfile);
- }
- if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, fileURL, &resourceData, NULL, NULL, &errorCode)) {
- fprintf(stderr, "%s: CFURLCreateDataAndPropertiesFromResource(%s) failed: %d\n", getprogname(), posixfile, (int)errorCode);
- }
-
- propertyList = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, resourceData, kCFPropertyListMutableContainersAndLeaves, &errorString);
- if (fileURL) {
- CFRelease(fileURL);
- }
-
- if (resourceData) {
- CFRelease(resourceData);
- }
-
- return propertyList;
-}
-
-void
-WriteMyPropertyListToFile(CFPropertyListRef plist, const char *posixfile)
-{
- CFDataRef resourceData;
- CFURLRef fileURL;
- SInt32 errorCode;
-
- fileURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)posixfile, strlen(posixfile), false);
- if (!fileURL) {
- fprintf(stderr, "%s: CFURLCreateFromFileSystemRepresentation(%s) failed\n", getprogname(), posixfile);
- }
- resourceData = CFPropertyListCreateXMLData(kCFAllocatorDefault, plist);
- if (resourceData == NULL) {
- fprintf(stderr, "%s: CFPropertyListCreateXMLData(%s) failed", getprogname(), posixfile);
- }
- if (!CFURLWriteDataAndPropertiesToResource(fileURL, resourceData, NULL, &errorCode)) {
- fprintf(stderr, "%s: CFURLWriteDataAndPropertiesToResource(%s) failed: %d\n", getprogname(), posixfile, (int)errorCode);
- }
-
- if (resourceData) {
- CFRelease(resourceData);
- }
-}
-
-static inline Boolean __is_launch_data_t(launch_data_t obj)
-{
- Boolean result = true;
-
- switch (launch_data_get_type(obj)) {
- case LAUNCH_DATA_STRING : break;
- case LAUNCH_DATA_INTEGER : break;
- case LAUNCH_DATA_REAL : break;
- case LAUNCH_DATA_BOOL : break;
- case LAUNCH_DATA_ARRAY : break;
- case LAUNCH_DATA_DICTIONARY : break;
- case LAUNCH_DATA_FD : break;
- case LAUNCH_DATA_MACHPORT : break;
- default : result = false;
- }
-
- return result;
-}
-
-static void __launch_data_iterate(launch_data_t obj, const char *key, CFMutableDictionaryRef dict)
-{
- if (obj && __is_launch_data_t(obj)) {
- CFStringRef cfKey = CFStringCreateWithCString(NULL, key, kCFStringEncodingUTF8);
- CFTypeRef cfVal = CFTypeCreateFromLaunchData(obj);
-
- if (cfVal) {
- CFDictionarySetValue(dict, cfKey, cfVal);
- CFRelease(cfVal);
- }
- CFRelease(cfKey);
- }
-}
-
-static CFTypeRef CFTypeCreateFromLaunchData(launch_data_t obj)
-{
- CFTypeRef cfObj = NULL;
-
- switch (launch_data_get_type(obj)) {
- case LAUNCH_DATA_STRING :
- {
- const char *str = launch_data_get_string(obj);
- cfObj = CFStringCreateWithCString(NULL, str, kCFStringEncodingUTF8);
-
- break;
- }
- case LAUNCH_DATA_INTEGER :
- {
- long long integer = launch_data_get_integer(obj);
- cfObj = CFNumberCreate(NULL, kCFNumberLongLongType, &integer);
-
- break;
- }
- case LAUNCH_DATA_REAL :
- {
- double real = launch_data_get_real(obj);
- cfObj = CFNumberCreate(NULL, kCFNumberDoubleType, &real);
-
- break;
- }
- case LAUNCH_DATA_BOOL :
- {
- bool yesno = launch_data_get_bool(obj);
- cfObj = yesno ? kCFBooleanTrue : kCFBooleanFalse;
-
- break;
- }
- case LAUNCH_DATA_ARRAY :
- {
- cfObj = (CFTypeRef)CFArrayCreateFromLaunchArray(obj);
-
- break;
- }
- case LAUNCH_DATA_DICTIONARY :
- {
- cfObj = (CFTypeRef)CFDictionaryCreateFromLaunchDictionary(obj);
-
- break;
- }
- case LAUNCH_DATA_FD :
- {
- int fd = launch_data_get_fd(obj);
- cfObj = CFNumberCreate(NULL, kCFNumberIntType, &fd);
-
- break;
- }
- case LAUNCH_DATA_MACHPORT :
- {
- mach_port_t port = launch_data_get_machport(obj);
- cfObj = CFNumberCreate(NULL, kCFNumberIntType, &port);
-
- break;
- }
- default : break;
- }
-
- return cfObj;
-}
-
-#pragma mark CFArray
-CFArrayRef CFArrayCreateFromLaunchArray(launch_data_t arr)
-{
- CFArrayRef result = NULL;
- CFMutableArrayRef mutResult = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
-
- if (launch_data_get_type(arr) == LAUNCH_DATA_ARRAY) {
- unsigned int count = launch_data_array_get_count(arr);
- unsigned int i = 0;
-
- for (i = 0; i < count; i++) {
- launch_data_t launch_obj = launch_data_array_get_index(arr, i);
- CFTypeRef obj = CFTypeCreateFromLaunchData(launch_obj);
-
- if (obj) {
- CFArrayAppendValue(mutResult, obj);
- CFRelease(obj);
- }
- }
-
- result = CFArrayCreateCopy(NULL, mutResult);
- }
-
- if (mutResult) {
- CFRelease(mutResult);
- }
- return result;
-}
-
-#pragma mark CFDictionary / CFPropertyList
-static CFDictionaryRef CFDictionaryCreateFromLaunchDictionary(launch_data_t dict)
-{
- CFDictionaryRef result = NULL;
-
- if (launch_data_get_type(dict) == LAUNCH_DATA_DICTIONARY) {
- CFMutableDictionaryRef mutResult = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
-
- launch_data_dict_iterate(dict, (void (*)(launch_data_t, const char *, void *))__launch_data_iterate, mutResult);
-
- result = CFDictionaryCreateCopy(NULL, mutResult);
- CFRelease(mutResult);
- }
-
- return result;
-}
-
-void
-myCFDictionaryApplyFunction(const void *key, const void *value, void *context)
-{
- launch_data_t ik, iw, where = context;
-
- ik = CF2launch_data(key);
- iw = CF2launch_data(value);
-
- launch_data_dict_insert(where, iw, launch_data_get_string(ik));
- launch_data_free(ik);
-}
-
-launch_data_t
-CF2launch_data(CFTypeRef cfr)
-{
- launch_data_t r;
- CFTypeID cft = CFGetTypeID(cfr);
-
- if (cft == CFStringGetTypeID()) {
- char buf[4096];
- CFStringGetCString(cfr, buf, sizeof(buf), kCFStringEncodingUTF8);
- r = launch_data_alloc(LAUNCH_DATA_STRING);
- launch_data_set_string(r, buf);
- } else if (cft == CFBooleanGetTypeID()) {
- r = launch_data_alloc(LAUNCH_DATA_BOOL);
- launch_data_set_bool(r, CFBooleanGetValue(cfr));
- } else if (cft == CFArrayGetTypeID()) {
- CFIndex i, ac = CFArrayGetCount(cfr);
- r = launch_data_alloc(LAUNCH_DATA_ARRAY);
- for (i = 0; i < ac; i++) {
- CFTypeRef v = CFArrayGetValueAtIndex(cfr, i);
- if (v) {
- launch_data_t iv = CF2launch_data(v);
- launch_data_array_set_index(r, iv, i);
- }
- }
- } else if (cft == CFDictionaryGetTypeID()) {
- r = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
- CFDictionaryApplyFunction(cfr, myCFDictionaryApplyFunction, r);
- } else if (cft == CFDataGetTypeID()) {
- r = launch_data_alloc(LAUNCH_DATA_OPAQUE);
- launch_data_set_opaque(r, CFDataGetBytePtr(cfr), CFDataGetLength(cfr));
- } else if (cft == CFNumberGetTypeID()) {
- long long n;
- double d;
- CFNumberType cfnt = CFNumberGetType(cfr);
- switch (cfnt) {
- case kCFNumberSInt8Type:
- case kCFNumberSInt16Type:
- case kCFNumberSInt32Type:
- case kCFNumberSInt64Type:
- case kCFNumberCharType:
- case kCFNumberShortType:
- case kCFNumberIntType:
- case kCFNumberLongType:
- case kCFNumberLongLongType:
- CFNumberGetValue(cfr, kCFNumberLongLongType, &n);
- r = launch_data_alloc(LAUNCH_DATA_INTEGER);
- launch_data_set_integer(r, n);
- break;
- case kCFNumberFloat32Type:
- case kCFNumberFloat64Type:
- case kCFNumberFloatType:
- case kCFNumberDoubleType:
- CFNumberGetValue(cfr, kCFNumberDoubleType, &d);
- r = launch_data_alloc(LAUNCH_DATA_REAL);
- launch_data_set_real(r, d);
- break;
- default:
- r = NULL;
- break;
- }
- } else {
- r = NULL;
- }
- return r;
-}
-
-int
-help_cmd(int argc, char *const argv[])
-{
- FILE *where = stdout;
- size_t i, l, cmdwidth = 0;
-
- if (argc == 0 || argv == NULL)
- where = stderr;
-
- fprintf(where, "usage: %s <subcommand>\n", getprogname());
-
- for (i = 0; i < (sizeof cmds / sizeof cmds[0]); i++) {
- l = strlen(cmds[i].name);
- if (l > cmdwidth) {
- cmdwidth = l;
- }
- }
-
- for (i = 0; i < (sizeof cmds / sizeof cmds[0]); i++) {
- fprintf(where, "\t%-*s\t%s\n", (int)cmdwidth, cmds[i].name, cmds[i].desc);
- }
-
- return 0;
-}
-
-int
-exit_cmd(int argc __attribute__((unused)), char *const argv[] __attribute__((unused)))
-{
- exit(0);
-}
-
-int
-_fd(int fd)
-{
- if (fd >= 0)
- fcntl(fd, F_SETFD, 1);
- return fd;
-}
-
-void
-do_single_user_mode(bool sflag)
-{
- if (sflag) {
- while (!do_single_user_mode2()) {
- sleep(1);
- }
- }
-}
-
-bool
-do_single_user_mode2(void)
-{
- bool runcom_fsck = true; /* should_fsck(); */
- int wstatus;
- int fd;
- pid_t p;
-
- switch ((p = fork())) {
- case -1:
- syslog(LOG_ERR, "can't fork single-user shell, trying again: %m");
- return false;
- case 0:
- break;
- default:
- (void)assumes(waitpid(p, &wstatus, 0) != -1);
- if (WIFEXITED(wstatus)) {
- if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
- return true;
- } else {
- fprintf(stdout, "single user mode: exit status: %d\n", WEXITSTATUS(wstatus));
- }
- } else {
- fprintf(stdout, "single user mode shell: %s\n", strsignal(WTERMSIG(wstatus)));
- }
- return false;
- }
-
- revoke(_PATH_CONSOLE);
- if (!assumes((fd = open(_PATH_CONSOLE, O_RDWR)) != -1)) {
- _exit(EXIT_FAILURE);
- }
- if (!assumes(login_tty(fd) != -1)) {
- _exit(EXIT_FAILURE);
- }
-
- mach_timespec_t wt = { 5, 0 };
- IOKitWaitQuiet(kIOMasterPortDefault, &wt); /* This will hopefully return after all the kexts have shut up. */
-
- setenv("TERM", "vt100", 1);
- if (runcom_fsck) {
- fprintf(stdout, "Singleuser boot -- fsck not done\n");
- fprintf(stdout, "Root device is mounted read-only\n\n");
- fprintf(stdout, "If you want to make modifications to files:\n");
- fprintf(stdout, "\t/sbin/fsck -fy\n\t/sbin/mount -uw /\n\n");
- fprintf(stdout, "If you wish to boot the system:\n");
- fprintf(stdout, "\texit\n\n");
- fflush(stdout);
- }
-
- execl(_PATH_BSHELL, "-sh", NULL);
- syslog(LOG_ERR, "can't exec %s for single user: %m", _PATH_BSHELL);
- _exit(EXIT_FAILURE);
-}
-
-void
-do_crash_debug_mode(void)
-{
- while (!do_crash_debug_mode2()) {
- sleep(1);
- }
-}
-
-bool
-do_crash_debug_mode2(void)
-{
- int wstatus;
- int fd;
- pid_t p;
-
- switch ((p = fork())) {
- case -1:
- syslog(LOG_ERR, "can't fork crash debug shell, trying again: %m");
- return false;
- case 0:
- break;
- default:
- (void)assumes(waitpid(p, &wstatus, 0) != -1);
- if (WIFEXITED(wstatus)) {
- if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
- return true;
- } else {
- fprintf(stdout, "crash debug mode: exit status: %d\n", WEXITSTATUS(wstatus));
- }
- } else {
- fprintf(stdout, "crash debug mode shell: %s\n", strsignal(WTERMSIG(wstatus)));
- }
- return false;
- }
-
- revoke(_PATH_CONSOLE);
- if (!assumes((fd = open(_PATH_CONSOLE, O_RDWR)) != -1)) {
- _exit(EXIT_FAILURE);
- }
- if (!assumes(login_tty(fd) != -1)) {
- _exit(EXIT_FAILURE);
- }
-
- mach_timespec_t wt = { 5, 0 };
- IOKitWaitQuiet(kIOMasterPortDefault, &wt); /* This will hopefully return after all the kexts have shut up. */
-
- setenv("TERM", "vt100", 1);
- fprintf(stdout, "Entering boot-time debugging mode...\n");
- fprintf(stdout, "The system bootstrapper process has crashed. To debug:\n");
- fprintf(stdout, "\tgdb attach %i\n", getppid());
- fprintf(stdout, "You can try booting the system with:\n");
- fprintf(stdout, "\tlaunchctl load -S System -D All\n\n");
-
- execl(_PATH_BSHELL, "-sh", NULL);
- syslog(LOG_ERR, "can't exec %s for crash debug: %m", _PATH_BSHELL);
- _exit(EXIT_FAILURE);
-}
-
-static void
-exit_at_sigterm(int sig)
-{
- if (sig == SIGTERM) {
- _exit(EXIT_SUCCESS);
- }
-}
-
-void
-fatal_signal_handler(int sig __attribute__((unused)), siginfo_t *si __attribute__((unused)), void *uap __attribute__((unused)))
-{
- do_crash_debug_mode();
-}
-
-void
-handle_system_bootstrapper_crashes_separately(void)
-{
- if (!g_startup_debugging) {
- return;
- }
-
- fprintf(stdout, "com.apple.launchctl.System\t\t\t*** Handling system bootstrapper crashes separately. ***\n");
- struct sigaction fsa;
-
- fsa.sa_sigaction = fatal_signal_handler;
- fsa.sa_flags = SA_SIGINFO;
- sigemptyset(&fsa.sa_mask);
-
- (void)assumes(sigaction(SIGILL, &fsa, NULL) != -1);
- (void)assumes(sigaction(SIGFPE, &fsa, NULL) != -1);
- (void)assumes(sigaction(SIGBUS, &fsa, NULL) != -1);
- (void)assumes(sigaction(SIGSEGV, &fsa, NULL) != -1);
- (void)assumes(sigaction(SIGTRAP, &fsa, NULL) != -1);
- (void)assumes(sigaction(SIGABRT, &fsa, NULL) != -1);
-}
-
-static void
-system_specific_bootstrap(bool sflag)
-{
- int hnmib[] = { CTL_KERN, KERN_HOSTNAME };
- struct kevent kev;
- int kq;
-#if HAVE_LIBAUDITD
- launch_data_t lda, ldb;
-#endif
-
- handle_system_bootstrapper_crashes_separately();
-
- // Disable Libinfo lookups to mdns and ds while bootstrapping (8698260)
- si_search_module_set_flags("mdns", 1);
- si_search_module_set_flags("ds", 1);
-
- do_sysversion_sysctl();
-
- do_single_user_mode(sflag);
-
- (void)assumes((kq = kqueue()) != -1);
-
- EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, 60, 0);
- (void)assumes(kevent(kq, &kev, 1, NULL, 0, NULL) != -1);
-
- EV_SET(&kev, SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, 0);
- (void)assumes(kevent(kq, &kev, 1, NULL, 0, NULL) != -1);
- (void)assumes(signal(SIGTERM, SIG_IGN) != SIG_ERR);
-
- (void)assumes(sysctl(hnmib, 2, NULL, NULL, "localhost", sizeof("localhost")) != -1);
-
- loopback_setup_ipv4();
- loopback_setup_ipv6();
-
- apply_sysctls_from_file("/etc/sysctl.conf");
-
-#if TARGET_OS_EMBEDDED
- if (path_check("/etc/rc.boot")) {
- const char *rcboot_tool[] = { "/etc/rc.boot", NULL };
-
- (void)assumes(signal(SIGTERM, exit_at_sigterm) != SIG_ERR);
- (void)assumes(fwexec(rcboot_tool, NULL) != -1);
- }
-#endif
-
- if (path_check("/etc/rc.cdrom")) {
- const char *rccdrom_tool[] = { _PATH_BSHELL, "/etc/rc.cdrom", "multiuser", NULL };
-
- /* The bootstrapper should always be killable during install-time (rdar://problem/6103485).
- * This is a special case for /etc/rc.cdrom, which runs a process and never exits.
- */
- (void)assumes(signal(SIGTERM, exit_at_sigterm) != SIG_ERR);
- (void)assumes(fwexec(rccdrom_tool, NULL) != -1);
- (void)assumes(reboot(RB_HALT) != -1);
- _exit(EXIT_FAILURE);
- } else if (is_netboot()) {
- const char *rcnetboot_tool[] = { _PATH_BSHELL, "/etc/rc.netboot", "init", NULL };
- if (!assumes(fwexec(rcnetboot_tool, NULL) != -1)) {
- (void)assumes(reboot(RB_HALT) != -1);
- _exit(EXIT_FAILURE);
- }
- } else {
- do_potential_fsck();
- }
-
-#if TARGET_OS_EMBEDDED
- if (path_check("/usr/libexec/cc_fips_test")) {
- const char *fips_tool[] = { "/usr/libexec/cc_fips_test", "-P", NULL };
- if (fwexec(fips_tool, NULL) == -1) {
- printf("FIPS self check failure\n");
- (void)assumes(reboot(RB_HALT) != -1);
- _exit(EXIT_FAILURE);
- }
- }
-#endif
-
- if (path_check("/etc/rc.server")) {
- const char *rcserver_tool[] = { _PATH_BSHELL, "/etc/rc.server", NULL };
- (void)assumes(fwexec(rcserver_tool, NULL) != -1);
- }
-
- read_launchd_conf();
-
- if (path_check("/var/account/acct")) {
- (void)assumes(acct("/var/account/acct") != -1);
- }
-
-#if !TARGET_OS_EMBEDDED
- if (path_check("/etc/fstab")) {
- const char *mount_tool[] = { "mount", "-vat", "nonfs", NULL };
- (void)assumes(fwexec(mount_tool, NULL) != -1);
- }
-#endif
-
- if (path_check("/etc/rc.installer_cleanup")) {
- const char *rccleanup_tool[] = { _PATH_BSHELL, "/etc/rc.installer_cleanup", "multiuser", NULL };
- (void)assumes(fwexec(rccleanup_tool, NULL) != -1);
- }
-
- if (path_check("/etc/rc.deferred_install")) {
- int status = 0;
- const char *deferredinstall_tool[] = { _PATH_BSHELL, "/etc/rc.deferred_install", NULL };
- if (assumes(fwexec(deferredinstall_tool, &status) != -1)) {
- if (WEXITSTATUS(status) == EXIT_SUCCESS) {
- if (do_apple_internal_magic) {
- fprintf(stdout, "Deferred install script completed successfully. Rebooting in 3 seconds...\n");
- sleep(3);
- }
-
- (void)assumes(remove(deferredinstall_tool[1]) != -1);
- (void)assumes(reboot(RB_AUTOBOOT) != -1);
- exit(EXIT_FAILURE);
- } else {
- fprintf(stdout, "Deferred install script exited with status %i. Continuing boot and hoping it'll work...\n", WEXITSTATUS(status));
- (void)assumes(remove(deferredinstall_tool[1]) != -1);
- }
- }
- }
-
- empty_dir(_PATH_VARRUN, NULL);
- empty_dir(_PATH_TMP, NULL);
- remove(_PATH_NOLOGIN);
-
- if (path_check("/usr/libexec/dirhelper")) {
- const char *dirhelper_tool[] = { "/usr/libexec/dirhelper", "-machineBoot", NULL };
- (void)assumes(fwexec(dirhelper_tool, NULL) != -1);
- }
-
- (void)assumes(touch_file(_PATH_UTMPX, DEFFILEMODE) != -1);
-#if !TARGET_OS_EMBEDDED
- (void)assumes(touch_file(_PATH_VARRUN "/.systemStarterRunning", DEFFILEMODE) != -1);
-#endif
-
-#if HAVE_LIBAUDITD
- /*
- * Only start auditing if not "Disabled" in auditd plist.
- */
- if ((lda = read_plist_file(AUDITD_PLIST_FILE, false, false)) != NULL &&
- ((ldb = launch_data_dict_lookup(lda, LAUNCH_JOBKEY_DISABLED)) == NULL ||
- job_disabled_logic(ldb) == false))
- {
- (void)assumes(audit_quick_start() == 0);
- launch_data_free(lda);
- }
-#else
- if (path_check("/etc/security/rc.audit")) {
- const char *audit_tool[] = { _PATH_BSHELL, "/etc/security/rc.audit", NULL };
- (void)assumes(fwexec(audit_tool, NULL) != -1);
- }
-#endif
-
- do_BootCache_magic(BOOTCACHE_START);
-
- preheat_page_cache_hack();
-
- _vproc_set_global_on_demand(true);
-
- char *load_launchd_items[] = { "load", "-D", "all", NULL };
- int load_launchd_items_cnt = 3;
-
- if (is_safeboot()) {
- load_launchd_items[2] = "system";
- }
-
- (void)assumes(load_and_unload_cmd(load_launchd_items_cnt, load_launchd_items) == 0);
-
- /*
- * 5066316
- *
- * We need to revisit this after Leopard ships.
- *
- * I want a plist defined knob for jobs to give advisory hints that
- * will "hopefully" serialize bootstrap. Reasons for doing so include
- * pragmatic performance optimizations and attempts to workaround bugs
- * in jobs. Something like what follows might work:
- *
- * The BootCache would switch to launchd and add this to the plist:
- *
- * <key>HopefullyStartsSerially<key>
- * <dict>
- * <key>ReadyTimeout</key>
- * <integer>2</integer>
- * </dict>
- *
- * And kextd would add the following:
- *
- * <key>HopefullyStartsSerially<key>
- * <dict>
- * <key>ReadyTimeout</key>
- * <integer>5</integer>
- * <key>HopefullyStartsAfter</key>
- * <string>com.apple.BootCache.daemon</string>
- * </dict>
- *
- *
- * Then both the BootCache and kextd could call something like:
- *
- * vproc_declare_ready_state();
- *
- * To tell launchd to short circuit the readiness timeout and let the
- * next wave of jobs start.
- *
- * Yes, this mechanism smells a lot like SystemStarter, rc.d and
- * friends. I think as long as we document that artificial
- * serialization is only advisory and not guaranteed, we should be
- * fine. Remember: IPC is the preferred way to serialize operations.
- *
- */
- if (!do_apple_internal_magic) {
- mach_timespec_t w = { 5, 0 };
- IOKitWaitQuiet(kIOMasterPortDefault, &w);
- }
-
- do_BootCache_magic(BOOTCACHE_TAG);
-
- do_bootroot_magic();
-
- _vproc_set_global_on_demand(false);
-
- (void)assumes(kevent(kq, NULL, 0, &kev, 1, NULL) == 1);
-
- /* warmd now handles cutting off the BootCache. We just kick it off. */
-
- (void)assumes(close(kq) != -1);
-}
-
-void
-do_BootCache_magic(BootCache_action_t what)
-{
- const char *bcc_tool[] = { "/usr/sbin/BootCacheControl", NULL, NULL };
-
- if (is_safeboot() || !path_check(bcc_tool[0])) {
- return;
- }
-
- switch (what) {
- case BOOTCACHE_START:
- bcc_tool[1] = "start";
- break;
- case BOOTCACHE_TAG:
- bcc_tool[1] = "tag";
- break;
- case BOOTCACHE_STOP:
- bcc_tool[1] = "stop";
- break;
- }
-
- fwexec(bcc_tool, NULL);
-}
-
-int
-bootstrap_cmd(int argc, char *const argv[])
-{
- char *session_type = NULL;
- bool sflag = false;
- int ch;
-
- while ((ch = getopt(argc, argv, "sS:")) != -1) {
- switch (ch) {
- case 's':
- sflag = true;
- break;
- case 'S':
- session_type = optarg;
- break;
- case '?':
- default:
- break;
- }
- }
-
- optind = 1;
- optreset = 1;
-
- if (!session_type) {
- fprintf(stderr, "usage: %s bootstrap [-s] -S <session-type>\n", getprogname());
- return 1;
- }
-
- if (strcasecmp(session_type, "System") == 0) {
- bootstrapping_system = true;
- system_specific_bootstrap(sflag);
- } else {
- char *load_launchd_items[] = { "load", "-S", session_type, "-D", "all", NULL, NULL, NULL, NULL, NULL, NULL };
- int the_argc = 5;
-
- char *load_launchd_items_user[] = { "load", "-S", VPROCMGR_SESSION_BACKGROUND, "-D", "user", NULL };
- int the_argc_user = 0;
-
- if (is_safeboot()) {
- load_launchd_items[4] = "system";
- }
-
- if (strcasecmp(session_type, VPROCMGR_SESSION_BACKGROUND) == 0 || strcasecmp(session_type, VPROCMGR_SESSION_LOGINWINDOW) == 0) {
- load_launchd_items[4] = "system";
- if (!is_safeboot()) {
- load_launchd_items[5] = "-D";
- load_launchd_items[6] = "local";
- the_argc += 2;
- }
- } else if (strcasecmp(session_type, VPROCMGR_SESSION_AQUA) == 0) {
- /* For now, we'll just load user Background agents when
- * bootstrapping the Aqua session. This way, we can
- * safely assume that the home directory is present. If
- * we try reading the user's Background agents when we're
- * actually bootstrapping the Background session, we run the
- * risk of deadlocking against mount_url. But this fix should
- * satisfy <rdar://problem/5279345>.
- */
- the_argc_user = 4;
-
- /* We want to read environment.plist, which is in the user's home directory.
- * Since the dance to mount a network home directory is fairly complex, all we
- * can do is try and read environment.plist when bootstrapping the Aqua session,
- * which is when we assume the home directory is present.
- *
- * The drawback here is that jobs bootstrapped in the Background session won't
- * get the new environment until they quit and relaunch. But then again, they
- * won't get the updated HOME directory or anything either. This is just a messy
- * problem.
- */
- read_environment_dot_plist();
- }
-
- if (strcasecmp(session_type, VPROCMGR_SESSION_BACKGROUND) == 0) {
- bootstrapping_peruser = true;
- read_launchd_conf();
-#if 0 /* XXX PR-6456403 */
- (void)assumes(SessionCreate(sessionKeepCurrentBootstrap, 0) == 0);
-#endif
- }
-
- int retval = load_and_unload_cmd(the_argc, load_launchd_items);
- if (retval == 0 && the_argc_user != 0) {
- optind = 1;
- int64_t junk = 0;
- vproc_err_t err = vproc_swap_integer(NULL, VPROC_GSK_WEIRD_BOOTSTRAP, &junk, NULL);
- if (!err) {
- retval = load_and_unload_cmd(the_argc_user, load_launchd_items_user);
-#if TARGET_OS_MAC
- _SMLoginItemBootstrapItems();
-#endif
- }
- }
-
- return retval;
- }
-
- return 0;
-}
-
-int
-load_and_unload_cmd(int argc, char *const argv[])
-{
- NSSearchPathEnumerationState es = 0;
- char nspath[PATH_MAX * 2]; /* safe side, we need to append */
- bool badopts = false;
- struct load_unload_state lus;
- size_t i;
- int ch;
-
- memset(&lus, 0, sizeof(lus));
-
- if (strcmp(argv[0], "load") == 0) {
- lus.load = true;
- }
-
- while ((ch = getopt(argc, argv, "wFS:D:")) != -1) {
- switch (ch) {
- case 'w':
- lus.editondisk = true;
- break;
- case 'F':
- lus.forceload = true;
- break;
- case 'S':
- lus.session_type = optarg;
- break;
- case 'D':
- if (strcasecmp(optarg, "all") == 0) {
- es |= NSAllDomainsMask;
- } else if (strcasecmp(optarg, "user") == 0) {
- es |= NSUserDomainMask;
- } else if (strcasecmp(optarg, "local") == 0) {
- es |= NSLocalDomainMask;
- } else if (strcasecmp(optarg, "network") == 0) {
- es |= NSNetworkDomainMask;
- } else if (strcasecmp(optarg, "system") == 0) {
- es |= NSSystemDomainMask;
- } else {
- badopts = true;
- }
- break;
- case '?':
- default:
- badopts = true;
- break;
- }
- }
- argc -= optind;
- argv += optind;
-
- if (lus.session_type == NULL) {
- es &= ~NSUserDomainMask;
- }
-
- if (argc == 0 && es == 0) {
- badopts = true;
- }
-
- if (badopts) {
- fprintf(stderr, "usage: %s load [-wF] [-D <user|local|network|system|all>] paths...\n", getprogname());
- return 1;
- }
-
- int dbfd = -1;
- char *db = NULL;
- vproc_err_t verr = vproc_swap_string(NULL, VPROC_GSK_JOB_OVERRIDES_DB, NULL, &db);
- if (verr) {
- fprintf(stderr, "Could not get location of job overrides database.\n");
- g_job_overrides_db_path[0] = 0;
- } else {
- strncpy(g_job_overrides_db_path, db, strlen(db));
-
- /* If we can't create or lock the overrides database, we'll fall back to writing to the
- * plist file directly.
- */
- (void)assumes((dbfd = open(g_job_overrides_db_path, O_RDONLY | O_EXLOCK | O_CREAT, S_IRUSR | S_IWUSR)) != -1);
- if (dbfd != -1) {
- g_job_overrides_db = (CFMutableDictionaryRef)CreateMyPropertyListFromFile(g_job_overrides_db_path);
- if (!g_job_overrides_db) {
- g_job_overrides_db = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
- }
- }
- free(db);
- }
-
- /* I wish I didn't need to do three passes, but I need to load mDNSResponder and use it too.
- * And loading legacy mach init jobs is extra fun.
- *
- * In later versions of launchd, I hope to load everything in the first pass,
- * then do the Bonjour magic on the jobs that need it, and reload them, but for now,
- * I haven't thought through the various complexities of reloading jobs, and therefore
- * launchd doesn't have reload support right now.
- */
-
- lus.pass1 = launch_data_alloc(LAUNCH_DATA_ARRAY);
- lus.pass2 = launch_data_alloc(LAUNCH_DATA_ARRAY);
-
- es = NSStartSearchPathEnumeration(NSLibraryDirectory, es);
-
- while ((es = NSGetNextSearchPathEnumeration(es, nspath))) {
- glob_t g;
-
- if (lus.session_type) {
- strcat(nspath, "/LaunchAgents");
- } else {
- strcat(nspath, "/LaunchDaemons");
- }
-
- if (glob(nspath, GLOB_TILDE|GLOB_NOSORT, NULL, &g) == 0) {
- for (i = 0; i < g.gl_pathc; i++) {
- readpath(g.gl_pathv[i], &lus);
- }
- globfree(&g);
- }
- }
-
- for (i = 0; i < (size_t)argc; i++) {
- readpath(argv[i], &lus);
- }
-
- if (launch_data_array_get_count(lus.pass1) == 0 &&
- launch_data_array_get_count(lus.pass2) == 0) {
- if (!is_managed) {
- fprintf(stderr, "nothing found to %s\n", lus.load ? "load" : "unload");
- }
- launch_data_free(lus.pass1);
- launch_data_free(lus.pass2);
- return is_managed ? 0 : 1;
- }
-
- if (lus.load) {
- distill_jobs(lus.pass1);
- submit_job_pass(lus.pass1);
- distill_jobs(lus.pass2);
- submit_job_pass(lus.pass2);
- } else {
- for (i = 0; i < launch_data_array_get_count(lus.pass1); i++) {
- unloadjob(launch_data_array_get_index(lus.pass1, i));
- }
- for (i = 0; i < launch_data_array_get_count(lus.pass2); i++) {
- unloadjob(launch_data_array_get_index(lus.pass2, i));
- }
- }
-
- if (g_job_overrides_db_has_changed) {
- WriteMyPropertyListToFile(g_job_overrides_db, g_job_overrides_db_path);
- }
-
- flock(dbfd, LOCK_UN);
- close(dbfd);
- return 0;
-}
-
-void
-submit_job_pass(launch_data_t jobs)
-{
- launch_data_t msg, resp;
- size_t i;
- int e;
-
- if (launch_data_array_get_count(jobs) == 0)
- return;
-
- msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
-
- launch_data_dict_insert(msg, jobs, LAUNCH_KEY_SUBMITJOB);
-
- resp = launch_msg(msg);
-
- if (resp) {
- switch (launch_data_get_type(resp)) {
- case LAUNCH_DATA_ERRNO:
- if ((e = launch_data_get_errno(resp)))
- fprintf(stderr, "%s\n", strerror(e));
- break;
- case LAUNCH_DATA_ARRAY:
- for (i = 0; i < launch_data_array_get_count(jobs); i++) {
- launch_data_t obatind = launch_data_array_get_index(resp, i);
- launch_data_t jatind = launch_data_array_get_index(jobs, i);
- const char *lab4job = launch_data_get_string(launch_data_dict_lookup(jatind, LAUNCH_JOBKEY_LABEL));
- if (LAUNCH_DATA_ERRNO == launch_data_get_type(obatind)) {
- e = launch_data_get_errno(obatind);
- switch (e) {
- case EEXIST:
- fprintf(stderr, "%s: %s\n", lab4job, "Already loaded");
- break;
- case ESRCH:
- fprintf(stderr, "%s: %s\n", lab4job, "Not loaded");
- break;
- case ENEEDAUTH:
- fprintf(stderr, "%s: %s\n", lab4job, "Could not set security session");
- default:
- fprintf(stderr, "%s: %s\n", lab4job, strerror(e));
- case 0:
- break;
- }
- }
- }
- break;
- default:
- fprintf(stderr, "unknown respose from launchd!\n");
- break;
- }
- launch_data_free(resp);
- } else {
- fprintf(stderr, "launch_msg(): %s\n", strerror(errno));
- }
-
- launch_data_free(msg);
-}
-
-int
-start_stop_remove_cmd(int argc, char *const argv[])
-{
- launch_data_t resp, msg;
- const char *lmsgcmd = LAUNCH_KEY_STOPJOB;
- int e, r = 0;
-
- if (0 == strcmp(argv[0], "start"))
- lmsgcmd = LAUNCH_KEY_STARTJOB;
-
- if (0 == strcmp(argv[0], "remove"))
- lmsgcmd = LAUNCH_KEY_REMOVEJOB;
-
- if (argc != 2) {
- fprintf(stderr, "usage: %s %s <job label>\n", getprogname(), argv[0]);
- return 1;
- }
-
- msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
- launch_data_dict_insert(msg, launch_data_new_string(argv[1]), lmsgcmd);
-
- resp = launch_msg(msg);
- launch_data_free(msg);
-
- if (resp == NULL) {
- fprintf(stderr, "launch_msg(): %s\n", strerror(errno));
- return 1;
- } else if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO) {
- if ((e = launch_data_get_errno(resp))) {
- fprintf(stderr, "%s %s error: %s\n", getprogname(), argv[0], strerror(e));
- r = 1;
- }
- } else {
- fprintf(stderr, "%s %s returned unknown response\n", getprogname(), argv[0]);
- r = 1;
- }
-
- launch_data_free(resp);
- return r;
-}
-
-void
-print_jobs(launch_data_t j, const char *key __attribute__((unused)), void *context __attribute__((unused)))
-{
- static size_t depth = 0;
- launch_data_t lo = launch_data_dict_lookup(j, LAUNCH_JOBKEY_LABEL);
- launch_data_t pido = launch_data_dict_lookup(j, LAUNCH_JOBKEY_PID);
- launch_data_t stato = launch_data_dict_lookup(j, LAUNCH_JOBKEY_LASTEXITSTATUS);
- const char *label = launch_data_get_string(lo);
- size_t i;
-
- if (pido) {
- fprintf(stdout, "%lld\t-\t", launch_data_get_integer(pido));
- } else if (stato) {
- int wstatus = (int)launch_data_get_integer(stato);
- if (WIFEXITED(wstatus)) {
- fprintf(stdout, "-\t%d\t", WEXITSTATUS(wstatus));
- } else if (WIFSIGNALED(wstatus)) {
- fprintf(stdout, "-\t-%d\t", WTERMSIG(wstatus));
- } else {
- fprintf(stdout, "-\t???\t");
- }
- } else {
- fprintf(stdout, "-\t-\t");
- }
- for (i = 0; i < depth; i++)
- fprintf(stdout, "\t");
-
- fprintf(stdout, "%s\n", label);
-}
-
-void
-print_obj(launch_data_t obj, const char *key, void *context __attribute__((unused)))
-{
- static size_t indent = 0;
- size_t i, c;
-
- for (i = 0; i < indent; i++)
- fprintf(stdout, "\t");
-
- if (key)
- fprintf(stdout, "\"%s\" = ", key);
-
- switch (launch_data_get_type(obj)) {
- case LAUNCH_DATA_STRING:
- fprintf(stdout, "\"%s\";\n", launch_data_get_string(obj));
- break;
- case LAUNCH_DATA_INTEGER:
- fprintf(stdout, "%lld;\n", launch_data_get_integer(obj));
- break;
- case LAUNCH_DATA_REAL:
- fprintf(stdout, "%f;\n", launch_data_get_real(obj));
- break;
- case LAUNCH_DATA_BOOL:
- fprintf(stdout, "%s;\n", launch_data_get_bool(obj) ? "true" : "false");
- break;
- case LAUNCH_DATA_ARRAY:
- c = launch_data_array_get_count(obj);
- fprintf(stdout, "(\n");
- indent++;
- for (i = 0; i < c; i++)
- print_obj(launch_data_array_get_index(obj, i), NULL, NULL);
- indent--;
- for (i = 0; i < indent; i++)
- fprintf(stdout, "\t");
- fprintf(stdout, ");\n");
- break;
- case LAUNCH_DATA_DICTIONARY:
- fprintf(stdout, "{\n");
- indent++;
- launch_data_dict_iterate(obj, print_obj, NULL);
- indent--;
- for (i = 0; i < indent; i++)
- fprintf(stdout, "\t");
- fprintf(stdout, "};\n");
- break;
- case LAUNCH_DATA_FD:
- fprintf(stdout, "file-descriptor-object;\n");
- break;
- case LAUNCH_DATA_MACHPORT:
- fprintf(stdout, "mach-port-object;\n");
- break;
- default:
- fprintf(stdout, "???;\n");
- break;
- }
-}
-
-int
-list_cmd(int argc, char *const argv[])
-{
- launch_data_t resp, msg = NULL;
- int r = 0;
-
- bool plist_output = false;
- char *label = NULL;
- if (argc > 3) {
- fprintf(stderr, "usage: %s list [-x] [label]\n", getprogname());
- return 1;
- } else if (argc >= 2) {
- plist_output = ( strncmp(argv[1], "-x", sizeof("-x")) == 0 );
- label = plist_output ? argv[2] : argv[1];
- }
-
- if (label) {
- msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
- launch_data_dict_insert(msg, launch_data_new_string(label), LAUNCH_KEY_GETJOB);
-
- resp = launch_msg(msg);
- launch_data_free(msg);
-
- if (resp == NULL) {
- fprintf(stderr, "launch_msg(): %s\n", strerror(errno));
- r = 1;
- } else if (launch_data_get_type(resp) == LAUNCH_DATA_DICTIONARY) {
- if (plist_output) {
- CFDictionaryRef respDict = CFDictionaryCreateFromLaunchDictionary(resp);
- CFStringRef plistStr = NULL;
- if (respDict) {
- CFDataRef plistData = CFPropertyListCreateXMLData(NULL, (CFPropertyListRef)respDict);
- CFRelease(respDict);
- if (plistData) {
- plistStr = CFStringCreateWithBytes(NULL, CFDataGetBytePtr(plistData), CFDataGetLength(plistData), kCFStringEncodingUTF8, false);
- CFRelease(plistData);
- } else {
- r = 1;
- }
- } else {
- r = 1;
- }
-
- if (plistStr) {
- CFShow(plistStr);
- CFRelease(plistStr);
- r = 0;
- }
- } else {
- print_obj(resp, NULL, NULL);
- r = 0;
- }
- launch_data_free(resp);
- } else {
- fprintf(stderr, "%s %s returned unknown response\n", getprogname(), argv[0]);
- r = 1;
- launch_data_free(resp);
- }
- } else if (vproc_swap_complex(NULL, VPROC_GSK_ALLJOBS, NULL, &resp) == NULL) {
- fprintf(stdout, "PID\tStatus\tLabel\n");
- launch_data_dict_iterate(resp, print_jobs, NULL);
- launch_data_free(resp);
-
- r = 0;
- }
-
- return r;
-}
-
-int
-stdio_cmd(int argc __attribute__((unused)), char *const argv[])
-{
- fprintf(stderr, "%s %s: This sub-command no longer does anything\n", getprogname(), argv[0]);
- return 1;
-}
-
-int
-fyi_cmd(int argc, char *const argv[])
-{
- launch_data_t resp, msg;
- const char *lmsgk = NULL;
- int e, r = 0;
-
- if (argc != 1) {
- fprintf(stderr, "usage: %s %s\n", getprogname(), argv[0]);
- return 1;
- }
-
- if (!strcmp(argv[0], "shutdown")) {
- lmsgk = LAUNCH_KEY_SHUTDOWN;
- } else if (!strcmp(argv[0], "singleuser")) {
- lmsgk = LAUNCH_KEY_SINGLEUSER;
- } else {
- return 1;
- }
-
- msg = launch_data_new_string(lmsgk);
- resp = launch_msg(msg);
- launch_data_free(msg);
-
- if (resp == NULL) {
- fprintf(stderr, "launch_msg(): %s\n", strerror(errno));
- return 1;
- } else if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO) {
- if ((e = launch_data_get_errno(resp))) {
- fprintf(stderr, "%s %s error: %s\n", getprogname(), argv[0], strerror(e));
- r = 1;
- }
- } else {
- fprintf(stderr, "%s %s returned unknown response\n", getprogname(), argv[0]);
- r = 1;
- }
-
- launch_data_free(resp);
-
- return r;
-}
-
-int
-logupdate_cmd(int argc, char *const argv[])
-{
- int64_t inval, outval;
- bool badargs = false, maskmode = false, onlymode = false, levelmode = false;
- static const struct {
- const char *name;
- int level;
- } logtbl[] = {
- { "debug", LOG_DEBUG },
- { "info", LOG_INFO },
- { "notice", LOG_NOTICE },
- { "warning", LOG_WARNING },
- { "error", LOG_ERR },
- { "critical", LOG_CRIT },
- { "alert", LOG_ALERT },
- { "emergency", LOG_EMERG },
- };
- size_t i, j, logtblsz = sizeof logtbl / sizeof logtbl[0];
- int m = 0;
-
- if (argc >= 2) {
- if (!strcmp(argv[1], "mask"))
- maskmode = true;
- else if (!strcmp(argv[1], "only"))
- onlymode = true;
- else if (!strcmp(argv[1], "level"))
- levelmode = true;
- else
- badargs = true;
- }
-
- if (maskmode)
- m = LOG_UPTO(LOG_DEBUG);
-
- if (argc > 2 && (maskmode || onlymode)) {
- for (i = 2; i < (size_t)argc; i++) {
- for (j = 0; j < logtblsz; j++) {
- if (!strcmp(argv[i], logtbl[j].name)) {
- if (maskmode)
- m &= ~(LOG_MASK(logtbl[j].level));
- else
- m |= LOG_MASK(logtbl[j].level);
- break;
- }
- }
- if (j == logtblsz) {
- badargs = true;
- break;
- }
- }
- } else if (argc > 2 && levelmode) {
- for (j = 0; j < logtblsz; j++) {
- if (!strcmp(argv[2], logtbl[j].name)) {
- m = LOG_UPTO(logtbl[j].level);
- break;
- }
- }
- if (j == logtblsz)
- badargs = true;
- } else if (argc != 1) {
- badargs = true;
- }
-
- if (badargs) {
- fprintf(stderr, "usage: %s [[mask loglevels...] | [only loglevels...] [level loglevel]]\n", getprogname());
- return 1;
- }
-
- inval = m;
-
- if (vproc_swap_integer(NULL, VPROC_GSK_GLOBAL_LOG_MASK, argc != 1 ? &inval : NULL, &outval) == NULL) {
- if (argc == 1) {
- for (j = 0; j < logtblsz; j++) {
- if (outval & LOG_MASK(logtbl[j].level)) {
- fprintf(stdout, "%s ", logtbl[j].name);
- }
- }
- fprintf(stdout, "\n");
- }
- return 0;
- } else {
- return 1;
- }
-}
-
-static const struct {
- const char *name;
- int lim;
-} limlookup[] = {
- { "cpu", RLIMIT_CPU },
- { "filesize", RLIMIT_FSIZE },
- { "data", RLIMIT_DATA },
- { "stack", RLIMIT_STACK },
- { "core", RLIMIT_CORE },
- { "rss", RLIMIT_RSS },
- { "memlock", RLIMIT_MEMLOCK },
- { "maxproc", RLIMIT_NPROC },
- { "maxfiles", RLIMIT_NOFILE }
-};
-
-static const size_t limlookupcnt = sizeof limlookup / sizeof limlookup[0];
-
-ssize_t
-name2num(const char *n)
-{
- size_t i;
-
- for (i = 0; i < limlookupcnt; i++) {
- if (!strcmp(limlookup[i].name, n)) {
- return limlookup[i].lim;
- }
- }
- return -1;
-}
-
-const char *
-num2name(int n)
-{
- size_t i;
-
- for (i = 0; i < limlookupcnt; i++) {
- if (limlookup[i].lim == n)
- return limlookup[i].name;
- }
- return NULL;
-}
-
-const char *
-lim2str(rlim_t val, char *buf)
-{
- if (val == RLIM_INFINITY)
- strcpy(buf, "unlimited");
- else
- sprintf(buf, "%lld", val);
- return buf;
-}
-
-bool
-str2lim(const char *buf, rlim_t *res)
-{
- char *endptr;
- *res = strtoll(buf, &endptr, 10);
- if (!strcmp(buf, "unlimited")) {
- *res = RLIM_INFINITY;
- return false;
- } else if (*endptr == '\0') {
- return false;
- }
- return true;
-}
-
-int
-limit_cmd(int argc, char *const argv[])
-{
- char slimstr[100];
- char hlimstr[100];
- struct rlimit *lmts = NULL;
- launch_data_t resp, resp1 = NULL, msg, tmp;
- int r = 0;
- size_t i, lsz = -1;
- ssize_t which = 0;
- rlim_t slim = -1, hlim = -1;
- bool badargs = false;
-
- if (argc > 4)
- badargs = true;
-
- if (argc >= 3 && str2lim(argv[2], &slim))
- badargs = true;
- else
- hlim = slim;
-
- if (argc == 4 && str2lim(argv[3], &hlim))
- badargs = true;
-
- if (argc >= 2 && -1 == (which = name2num(argv[1])))
- badargs = true;
-
- if (badargs) {
- fprintf(stderr, "usage: %s %s [", getprogname(), argv[0]);
- for (i = 0; i < limlookupcnt; i++)
- fprintf(stderr, "%s %s", limlookup[i].name, (i + 1) == limlookupcnt ? "" : "| ");
- fprintf(stderr, "[both | soft hard]]\n");
- return 1;
- }
-
- msg = launch_data_new_string(LAUNCH_KEY_GETRESOURCELIMITS);
- resp = launch_msg(msg);
- launch_data_free(msg);
-
- if (resp == NULL) {
- fprintf(stderr, "launch_msg(): %s\n", strerror(errno));
- return 1;
- } else if (launch_data_get_type(resp) == LAUNCH_DATA_OPAQUE) {
- lmts = launch_data_get_opaque(resp);
- lsz = launch_data_get_opaque_size(resp);
- if (argc <= 2) {
- for (i = 0; i < (lsz / sizeof(struct rlimit)); i++) {
- if (argc == 2 && (size_t)which != i)
- continue;
- fprintf(stdout, "\t%-12s%-15s%-15s\n", num2name((int)i),
- lim2str(lmts[i].rlim_cur, slimstr),
- lim2str(lmts[i].rlim_max, hlimstr));
- }
- }
- } else if (launch_data_get_type(resp) == LAUNCH_DATA_STRING) {
- fprintf(stderr, "%s %s error: %s\n", getprogname(), argv[0], launch_data_get_string(resp));
- r = 1;
- } else {
- fprintf(stderr, "%s %s returned unknown response\n", getprogname(), argv[0]);
- r = 1;
- }
-
- if (argc <= 2 || r != 0) {
- launch_data_free(resp);
- return r;
- } else {
- resp1 = resp;
- }
-
- lmts[which].rlim_cur = slim;
- lmts[which].rlim_max = hlim;
-
- bool maxfiles_exceeded = false;
- if (strncmp(argv[1], "maxfiles", sizeof("maxfiles")) == 0) {
- if (argc > 2) {
- maxfiles_exceeded = ( strncmp(argv[2], "unlimited", sizeof("unlimited")) == 0 );
- }
-
- if (argc > 3) {
- maxfiles_exceeded = ( maxfiles_exceeded || strncmp(argv[3], "unlimited", sizeof("unlimited")) == 0 );
- }
-
- if (maxfiles_exceeded) {
- fprintf(stderr, "Neither the hard nor soft limit for \"maxfiles\" can be unlimited. Please use a numeric parameter for both.\n");
- return 1;
- }
- }
-
- msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
- tmp = launch_data_new_opaque(lmts, lsz);
- launch_data_dict_insert(msg, tmp, LAUNCH_KEY_SETRESOURCELIMITS);
- resp = launch_msg(msg);
- launch_data_free(msg);
-
- if (resp == NULL) {
- fprintf(stderr, "launch_msg(): %s\n", strerror(errno));
- return 1;
- } else if (launch_data_get_type(resp) == LAUNCH_DATA_STRING) {
- fprintf(stderr, "%s %s error: %s\n", getprogname(), argv[0], launch_data_get_string(resp));
- r = 1;
- } else if (launch_data_get_type(resp) != LAUNCH_DATA_OPAQUE) {
- fprintf(stderr, "%s %s returned unknown response\n", getprogname(), argv[0]);
- r = 1;
- }
-
- launch_data_free(resp);
- launch_data_free(resp1);
-
- return r;
-}
-
-int
-umask_cmd(int argc, char *const argv[])
-{
- bool badargs = false;
- char *endptr;
- long m = 0;
- int64_t inval, outval;
-
- if (argc == 2) {
- m = strtol(argv[1], &endptr, 8);
- if (*endptr != '\0' || m > 0777)
- badargs = true;
- }
-
- if (argc > 2 || badargs) {
- fprintf(stderr, "usage: %s %s <mask>\n", getprogname(), argv[0]);
- return 1;
- }
-
- inval = m;
-
- if (vproc_swap_integer(NULL, VPROC_GSK_GLOBAL_UMASK, argc == 2 ? &inval : NULL, &outval) == NULL) {
- if (argc == 1) {
- fprintf(stdout, "%o\n", (unsigned int)outval);
- }
- return 0;
- } else {
- return 1;
- }
-}
-
-void
-setup_system_context(void)
-{
- if (getenv(LAUNCHD_SOCKET_ENV)) {
- return;
- }
-
- if (getenv(LAUNCH_ENV_KEEPCONTEXT)) {
- return;
- }
-
- if (geteuid() != 0) {
- fprintf(stderr, "You must be the root user to perform this operation.\n");
- return;
- }
-
- /* Use the system launchd's socket. */
- setenv("__USE_SYSTEM_LAUNCHD", "1", 0);
-
- /* Put ourselves in the system launchd's bootstrap. */
- mach_port_t rootbs = str2bsport("/");
- mach_port_deallocate(mach_task_self(), bootstrap_port);
- task_set_bootstrap_port(mach_task_self(), rootbs);
- bootstrap_port = rootbs;
-}
-
-int
-submit_cmd(int argc, char *const argv[])
-{
- launch_data_t msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
- launch_data_t job = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
- launch_data_t resp, largv = launch_data_alloc(LAUNCH_DATA_ARRAY);
- int ch, i, r = 0;
-
- launch_data_dict_insert(job, launch_data_new_bool(false), LAUNCH_JOBKEY_ONDEMAND);
-
- while ((ch = getopt(argc, argv, "l:p:o:e:")) != -1) {
- switch (ch) {
- case 'l':
- launch_data_dict_insert(job, launch_data_new_string(optarg), LAUNCH_JOBKEY_LABEL);
- break;
- case 'p':
- launch_data_dict_insert(job, launch_data_new_string(optarg), LAUNCH_JOBKEY_PROGRAM);
- break;
- case 'o':
- launch_data_dict_insert(job, launch_data_new_string(optarg), LAUNCH_JOBKEY_STANDARDOUTPATH);
- break;
- case 'e':
- launch_data_dict_insert(job, launch_data_new_string(optarg), LAUNCH_JOBKEY_STANDARDERRORPATH);
- break;
- default:
- fprintf(stderr, "usage: %s submit ...\n", getprogname());
- return 1;
- }
- }
- argc -= optind;
- argv += optind;
-
- for (i = 0; argv[i]; i++) {
- launch_data_array_append(largv, launch_data_new_string(argv[i]));
- }
-
- launch_data_dict_insert(job, largv, LAUNCH_JOBKEY_PROGRAMARGUMENTS);
-
- launch_data_dict_insert(msg, job, LAUNCH_KEY_SUBMITJOB);
-
- resp = launch_msg(msg);
- launch_data_free(msg);
-
- if (resp == NULL) {
- fprintf(stderr, "launch_msg(): %s\n", strerror(errno));
- return 1;
- } else if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO) {
- errno = launch_data_get_errno(resp);
- if (errno) {
- fprintf(stderr, "%s %s error: %s\n", getprogname(), argv[0], strerror(errno));
- r = 1;
- }
- } else {
- fprintf(stderr, "%s %s error: %s\n", getprogname(), argv[0], "unknown response");
- }
-
- launch_data_free(resp);
-
- return r;
-}
-
-int
-getrusage_cmd(int argc, char *const argv[])
-{
- launch_data_t resp, msg;
- bool badargs = false;
- int r = 0;
-
- if (argc != 2)
- badargs = true;
- else if (strcmp(argv[1], "self") && strcmp(argv[1], "children"))
- badargs = true;
-
- if (badargs) {
- fprintf(stderr, "usage: %s %s self | children\n", getprogname(), argv[0]);
- return 1;
- }
-
- if (!strcmp(argv[1], "self")) {
- msg = launch_data_new_string(LAUNCH_KEY_GETRUSAGESELF);
- } else {
- msg = launch_data_new_string(LAUNCH_KEY_GETRUSAGECHILDREN);
- }
-
- resp = launch_msg(msg);
- launch_data_free(msg);
-
- if (resp == NULL) {
- fprintf(stderr, "launch_msg(): %s\n", strerror(errno));
- return 1;
- } else if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO) {
- fprintf(stderr, "%s %s error: %s\n", getprogname(), argv[0], strerror(launch_data_get_errno(resp)));
- r = 1;
- } else if (launch_data_get_type(resp) == LAUNCH_DATA_OPAQUE) {
- struct rusage *rusage = launch_data_get_opaque(resp);
- fprintf(stdout, "\t%-10f\tuser time used\n",
- (double)rusage->ru_utime.tv_sec + (double)rusage->ru_utime.tv_usec / (double)1000000);
- fprintf(stdout, "\t%-10f\tsystem time used\n",
- (double)rusage->ru_stime.tv_sec + (double)rusage->ru_stime.tv_usec / (double)1000000);
- fprintf(stdout, "\t%-10ld\tmax resident set size\n", rusage->ru_maxrss);
- fprintf(stdout, "\t%-10ld\tshared text memory size\n", rusage->ru_ixrss);
- fprintf(stdout, "\t%-10ld\tunshared data size\n", rusage->ru_idrss);
- fprintf(stdout, "\t%-10ld\tunshared stack size\n", rusage->ru_isrss);
- fprintf(stdout, "\t%-10ld\tpage reclaims\n", rusage->ru_minflt);
- fprintf(stdout, "\t%-10ld\tpage faults\n", rusage->ru_majflt);
- fprintf(stdout, "\t%-10ld\tswaps\n", rusage->ru_nswap);
- fprintf(stdout, "\t%-10ld\tblock input operations\n", rusage->ru_inblock);
- fprintf(stdout, "\t%-10ld\tblock output operations\n", rusage->ru_oublock);
- fprintf(stdout, "\t%-10ld\tmessages sent\n", rusage->ru_msgsnd);
- fprintf(stdout, "\t%-10ld\tmessages received\n", rusage->ru_msgrcv);
- fprintf(stdout, "\t%-10ld\tsignals received\n", rusage->ru_nsignals);
- fprintf(stdout, "\t%-10ld\tvoluntary context switches\n", rusage->ru_nvcsw);
- fprintf(stdout, "\t%-10ld\tinvoluntary context switches\n", rusage->ru_nivcsw);
- } else {
- fprintf(stderr, "%s %s returned unknown response\n", getprogname(), argv[0]);
- r = 1;
- }
-
- launch_data_free(resp);
-
- return r;
-}
-
-bool
-launch_data_array_append(launch_data_t a, launch_data_t o)
-{
- size_t offt = launch_data_array_get_count(a);
-
- return launch_data_array_set_index(a, o, offt);
-}
-
-mach_port_t
-str2bsport(const char *s)
-{
- bool getrootbs = strcmp(s, "/") == 0;
- mach_port_t last_bport, bport = bootstrap_port;
- task_t task = mach_task_self();
- kern_return_t result;
-
- if (strcmp(s, "..") == 0 || getrootbs) {
- do {
- last_bport = bport;
- result = bootstrap_parent(last_bport, &bport);
-
- if (result == BOOTSTRAP_NOT_PRIVILEGED) {
- fprintf(stderr, "Permission denied\n");
- return 1;
- } else if (result != BOOTSTRAP_SUCCESS) {
- fprintf(stderr, "bootstrap_parent() %d\n", result);
- return 1;
- }
- } while (getrootbs && last_bport != bport);
- } else if (strcmp(s, "0") == 0 || strcmp(s, "NULL") == 0) {
- bport = MACH_PORT_NULL;
- } else {
- int pid = atoi(s);
-
- result = task_for_pid(mach_task_self(), pid, &task);
-
- if (result != KERN_SUCCESS) {
- fprintf(stderr, "task_for_pid() %s\n", mach_error_string(result));
- return 1;
- }
-
- result = task_get_bootstrap_port(task, &bport);
-
- if (result != KERN_SUCCESS) {
- fprintf(stderr, "Couldn't get bootstrap port: %s\n", mach_error_string(result));
- return 1;
- }
- }
-
- return bport;
-}
-
-int
-bsexec_cmd(int argc, char *const argv[])
-{
- kern_return_t result;
- mach_port_t bport;
-
- if (argc < 3) {
- fprintf(stderr, "usage: %s bsexec <PID> prog...\n", getprogname());
- return 1;
- }
-
- bport = str2bsport(argv[1]);
-
- result = task_set_bootstrap_port(mach_task_self(), bport);
-
- if (result != KERN_SUCCESS) {
- fprintf(stderr, "Couldn't switch to new bootstrap port: %s\n", mach_error_string(result));
- return 1;
- }
-
- setgid(getgid());
- setuid(getuid());
-
- setenv(LAUNCH_ENV_KEEPCONTEXT, "1", 1);
- if (fwexec((const char *const *)argv + 2, NULL) == -1) {
- fprintf(stderr, "%s bsexec failed: %s\n", getprogname(), strerror(errno));
- return 1;
- }
-
- return 0;
-}
-
-int
-_bslist_cmd(mach_port_t bport, unsigned int depth, bool show_job, bool local_only)
-{
- kern_return_t result;
- name_array_t service_names;
- name_array_t service_jobs;
- mach_msg_type_number_t service_cnt, service_jobs_cnt, service_active_cnt;
- bootstrap_status_array_t service_actives;
- unsigned int i;
-
- if (bport == MACH_PORT_NULL) {
- fprintf(stderr, "Invalid bootstrap port\n");
- return 1;
- }
-
- uint64_t flags = 0;
- flags |= local_only ? BOOTSTRAP_FORCE_LOCAL : 0;
- result = bootstrap_info(bport, &service_names, &service_cnt, &service_jobs, &service_jobs_cnt, &service_actives, &service_active_cnt, flags);
- if (result != BOOTSTRAP_SUCCESS) {
- fprintf(stderr, "bootstrap_info(): %d\n", result);
- return 1;
- }
-
-#define bport_state(x) (((x) == BOOTSTRAP_STATUS_ACTIVE) ? "A" : ((x) == BOOTSTRAP_STATUS_ON_DEMAND) ? "D" : "I")
-
- for (i = 0; i < service_cnt ; i++) {
- fprintf(stdout, "%*s%-3s%s", depth, "", bport_state((service_actives[i])), service_names[i]);
- if (show_job) {
- fprintf(stdout, " (%s)", service_jobs[i]);
- }
- fprintf(stdout, "\n");
- }
-
- return 0;
-}
-
-int
-bslist_cmd(int argc, char *const argv[])
-{
- mach_port_t bport = bootstrap_port;
- bool show_jobs = false;
- if (argc > 2 && strcmp(argv[2], "-j") == 0) {
- show_jobs = true;
- }
-
- if (argc > 1) {
- if (show_jobs) {
- bport = str2bsport(argv[1]);
- } else if (strcmp(argv[1], "-j") == 0) {
- show_jobs = true;
- }
- }
-
- if (bport == MACH_PORT_NULL) {
- fprintf(stderr, "Invalid bootstrap port\n");
- return 1;
- }
-
- return _bslist_cmd(bport, 0, show_jobs, false);
-}
-
-int
-_bstree_cmd(mach_port_t bsport, unsigned int depth, bool show_jobs)
-{
- if (bsport == MACH_PORT_NULL) {
- fprintf(stderr, "No root port!\n");
- return 1;
- }
-
- mach_port_array_t child_ports = NULL;
- name_array_t child_names = NULL;
- bootstrap_property_array_t child_props = NULL;
- unsigned int cnt = 0;
-
- kern_return_t kr = bootstrap_lookup_children(bsport, &child_ports, &child_names, &child_props, (mach_msg_type_number_t *)&cnt);
- if (kr != BOOTSTRAP_SUCCESS && kr != BOOTSTRAP_NO_CHILDREN) {
- if (kr == BOOTSTRAP_NOT_PRIVILEGED) {
- fprintf(stderr, "You must be root to perform this operation.\n");
- } else {
- fprintf(stderr, "bootstrap_lookup_children(): %d\n", kr);
- }
-
- return 1;
- }
-
- unsigned int i = 0;
- _bslist_cmd(bsport, depth, show_jobs, true);
-
- for (i = 0; i < cnt; i++) {
- char *type = NULL;
- if (child_props[i] & BOOTSTRAP_PROPERTY_PERUSER) {
- type = "Per-user";
- } else if (child_props[i] & BOOTSTRAP_PROPERTY_EXPLICITSUBSET) {
- type = "Explicit Subset";
- } else if (child_props[i] & BOOTSTRAP_PROPERTY_IMPLICITSUBSET) {
- type = "Implicit Subset";
- } else if (child_props[i] & BOOTSTRAP_PROPERTY_MOVEDSUBSET) {
- type = "Moved Subset";
- } else if (child_props[i] & BOOTSTRAP_PROPERTY_XPC_SINGLETON) {
- type = "XPC Singleton Domain";
- } else if (child_props[i] & BOOTSTRAP_PROPERTY_XPC_DOMAIN) {
- type = "XPC Private Domain";
- } else {
- type = "Unknown";
- }
-
- fprintf(stdout, "%*s%s (%s)/\n", depth, "", child_names[i], type);
- if (child_ports[i] != MACH_PORT_NULL) {
- _bstree_cmd(child_ports[i], depth + 4, show_jobs);
- }
- }
-
- return 0;
-}
-
-int
-bstree_cmd(int argc, char * const argv[])
-{
- bool show_jobs = false;
- if (geteuid() != 0) {
- fprintf(stderr, "You must be root to perform this operation.\n");
- return 1;
- } else {
- if (argc == 2 && strcmp(argv[1], "-j") == 0) {
- show_jobs = true;
- }
- fprintf(stdout, "System/\n");
- }
-
- return _bstree_cmd(str2bsport("/"), 4, show_jobs);
-}
-
-int
-managerpid_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused)))
-{
- int64_t manager_pid = 0;
- vproc_err_t verr = vproc_swap_integer(NULL, VPROC_GSK_MGR_PID, NULL, (int64_t *)&manager_pid);
- if (verr) {
- fprintf(stdout, "Unknown job manager!\n");
- return 1;
- }
-
- fprintf(stdout, "%d\n", (pid_t)manager_pid);
- return 0;
-}
-
-int
-manageruid_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused)))
-{
- int64_t manager_uid = 0;
- vproc_err_t verr = vproc_swap_integer(NULL, VPROC_GSK_MGR_UID, NULL, (int64_t *)&manager_uid);
- if (verr) {
- fprintf(stdout, "Unknown job manager!\n");
- return 1;
- }
-
- fprintf(stdout, "%lli\n", manager_uid);
- return 0;
-}
-
-int
-managername_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused)))
-{
- char *manager_name = NULL;
- vproc_err_t verr = vproc_swap_string(NULL, VPROC_GSK_MGR_NAME, NULL, &manager_name);
- if (verr) {
- fprintf(stdout, "Unknown job manager!\n");
- return 1;
- }
-
- fprintf(stdout, "%s\n", manager_name);
- free(manager_name);
-
- return 0;
-}
-
-int
-asuser_cmd(int argc, char * const argv[])
-{
- /* This code plays fast and loose with Mach ports. Do NOT use it as any sort
- * of reference for port handling. Or really anything else in this file.
- */
- uid_t req_uid = (uid_t)-2;
- if (argc > 2) {
- req_uid = atoi(argv[1]);
- if (req_uid == (uid_t)-2) {
- fprintf(stderr, "You cannot run a command nobody.\n");
- return 1;
- }
- } else {
- fprintf(stderr, "Usage: launchctl asuser <UID> <command> [arguments...].\n");
- return 1;
- }
-
- if (geteuid() != 0) {
- fprintf(stderr, "You must be root to run a command as another user.\n");
- return 1;
- }
-
- mach_port_t rbs = MACH_PORT_NULL;
- kern_return_t kr = bootstrap_get_root(bootstrap_port, &rbs);
- if (kr != BOOTSTRAP_SUCCESS) {
- fprintf(stderr, "bootstrap_get_root(): %u\n", kr);
- return 1;
- }
-
- mach_port_t bp = MACH_PORT_NULL;
- kr = bootstrap_look_up_per_user(rbs, NULL, req_uid, &bp);
- if (kr != BOOTSTRAP_SUCCESS) {
- fprintf(stderr, "bootstrap_look_up_per_user(): %u\n", kr);
- return 1;
- }
-
- bootstrap_port = bp;
- kr = task_set_bootstrap_port(mach_task_self(), bp);
- if (kr != KERN_SUCCESS) {
- fprintf(stderr, "task_set_bootstrap_port(): 0x%x: %s\n", kr, mach_error_string(kr));
- return 1;
- }
-
- name_t sockpath;
- sockpath[0] = 0;
- kr = _vprocmgr_getsocket(sockpath);
- if (kr != BOOTSTRAP_SUCCESS) {
- fprintf(stderr, "_vprocmgr_getsocket(): %u\n", kr);
- return 1;
- }
-
- setenv(LAUNCHD_SOCKET_ENV, sockpath, 1);
- setenv(LAUNCH_ENV_KEEPCONTEXT, "1", 1);
- if (fwexec((const char *const *)argv + 2, NULL) == -1) {
- fprintf(stderr, "Couldn't spawn command: %s\n", argv[2]);
- return 1;
- }
-
- return 0;
-}
-
-void
-_log_launchctl_bug(const char *rcs_rev, const char *path, unsigned int line, const char *test)
-{
- int saved_errno = errno;
- char buf[100];
- const char *file = strrchr(path, '/');
- char *rcs_rev_tmp = strchr(rcs_rev, ' ');
-
- if (!file) {
- file = path;
- } else {
- file += 1;
- }
-
- if (!rcs_rev_tmp) {
- strlcpy(buf, rcs_rev, sizeof(buf));
- } else {
- strlcpy(buf, rcs_rev_tmp + 1, sizeof(buf));
- rcs_rev_tmp = strchr(buf, ' ');
- if (rcs_rev_tmp)
- *rcs_rev_tmp = '\0';
- }
-
- fprintf(stderr, "Bug: %s:%u (%s):%u: %s\n", file, line, buf, saved_errno, test);
-}
-
-void
-loopback_setup_ipv4(void)
-{
- struct ifaliasreq ifra;
- struct ifreq ifr;
- int s;
-
- memset(&ifr, 0, sizeof(ifr));
- strcpy(ifr.ifr_name, "lo0");
-
- if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
- return;
-
- if (assumes(ioctl(s, SIOCGIFFLAGS, &ifr) != -1)) {
- ifr.ifr_flags |= IFF_UP;
- (void)assumes(ioctl(s, SIOCSIFFLAGS, &ifr) != -1);
- }
-
- memset(&ifra, 0, sizeof(ifra));
- strcpy(ifra.ifra_name, "lo0");
- ((struct sockaddr_in *)&ifra.ifra_addr)->sin_family = AF_INET;
- ((struct sockaddr_in *)&ifra.ifra_addr)->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- ((struct sockaddr_in *)&ifra.ifra_addr)->sin_len = sizeof(struct sockaddr_in);
- ((struct sockaddr_in *)&ifra.ifra_mask)->sin_family = AF_INET;
- ((struct sockaddr_in *)&ifra.ifra_mask)->sin_addr.s_addr = htonl(IN_CLASSA_NET);
- ((struct sockaddr_in *)&ifra.ifra_mask)->sin_len = sizeof(struct sockaddr_in);
-
- (void)assumes(ioctl(s, SIOCAIFADDR, &ifra) != -1);
-
- (void)assumes(close(s) == 0);
-}
-
-void
-loopback_setup_ipv6(void)
-{
- struct in6_aliasreq ifra6;
- struct ifreq ifr;
- int s6;
-
- memset(&ifr, 0, sizeof(ifr));
- strcpy(ifr.ifr_name, "lo0");
-
- if ((s6 = socket(AF_INET6, SOCK_DGRAM, 0)) == -1)
- return;
-
- memset(&ifr, 0, sizeof(ifr));
- strcpy(ifr.ifr_name, "lo0");
-
- if (assumes(ioctl(s6, SIOCGIFFLAGS, &ifr) != -1)) {
- ifr.ifr_flags |= IFF_UP;
- (void)assumes(ioctl(s6, SIOCSIFFLAGS, &ifr) != -1);
- }
-
- memset(&ifra6, 0, sizeof(ifra6));
- strcpy(ifra6.ifra_name, "lo0");
-
- ifra6.ifra_addr.sin6_family = AF_INET6;
- ifra6.ifra_addr.sin6_addr = in6addr_loopback;
- ifra6.ifra_addr.sin6_len = sizeof(struct sockaddr_in6);
- ifra6.ifra_prefixmask.sin6_family = AF_INET6;
- memset(&ifra6.ifra_prefixmask.sin6_addr, 0xff, sizeof(struct in6_addr));
- ifra6.ifra_prefixmask.sin6_len = sizeof(struct sockaddr_in6);
- ifra6.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
- ifra6.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;
-
- if (ioctl(s6, SIOCAIFADDR_IN6, &ifra6) == -1) {
- (void)assumes(errno == EEXIST);
- }
-
- (void)assumes(close(s6) == 0);
-}
-
-pid_t
-fwexec(const char *const *argv, int *wstatus)
-{
- int wstatus2;
- pid_t p;
-
- /* We'd use posix_spawnp(), but we want to workaround: 6288899 */
- if ((p = vfork()) == -1) {
- return -1;
- } else if (p == 0) {
- execvp(argv[0], (char *const *)argv);
- _exit(EXIT_FAILURE);
- }
-
- if (waitpid(p, wstatus ? wstatus : &wstatus2, 0) == -1) {
- return -1;
- }
-
- if (wstatus) {
- return p;
- } else if (WIFEXITED(wstatus2) && WEXITSTATUS(wstatus2) == EXIT_SUCCESS) {
- return p;
- }
-
- return -1;
-}
-
-void
-do_potential_fsck(void)
-{
- const char *safe_fsck_tool[] = { "fsck", "-fy", NULL };
- const char *fsck_tool[] = { "fsck", "-q", NULL };
- const char *remount_tool[] = { "mount", "-uw", "/", NULL };
-#if TARGET_OS_EMBEDDED
- const char *nvram_tool[] = { "/usr/sbin/nvram", "auto-boot=false", NULL };
-#endif /* TARGET_OS_EMBEDDED */
- struct statfs sfs;
-
- if (!assumes(statfs("/", &sfs) != -1)) {
- return;
- }
-
- if (!(sfs.f_flags & MNT_RDONLY)) {
- return;
- }
-
- if (!is_safeboot()) {
-#if 0
- /* We have disabled this block for now. We need to revisit this optimization after Leopard. */
- if (sfs.f_flags & MNT_JOURNALED) {
- goto out;
- }
-#endif
- fprintf(stdout, "Running fsck on the boot volume...\n");
- if (fwexec(fsck_tool, NULL) != -1) {
- goto out;
- }
- }
-
- fprintf(stdout, "Running safe fsck on the boot volume...\n");
- if (fwexec(safe_fsck_tool, NULL) != -1) {
- goto out;
- }
-
- fprintf(stdout, "fsck failed!\n");
-
- /* someday, we should keep booting read-only, but as of today, other sub-systems cannot handle that scenario */
-#if TARGET_OS_EMBEDDED
- (void)assumes(fwexec(nvram_tool, NULL) != -1);
- (void)assumes(reboot(RB_AUTOBOOT) != -1);
-#else
- (void)assumes(reboot(RB_HALT) != -1);
-#endif
-
- return;
-out:
- /*
- * Once this is fixed:
- *
- * <rdar://problem/3948774> Mount flag updates should be possible with NULL as the forth argument to mount()
- *
- * We can then do this one system call instead of calling out a full blown process.
- *
- * assumes(mount(sfs.f_fstypename, "/", MNT_UPDATE, NULL) != -1);
- */
-#if TARGET_OS_EMBEDDED
- if (path_check("/etc/fstab")) {
- const char *mount_tool[] = { "mount", "-vat", "nonfs", NULL };
- if (!assumes(fwexec(mount_tool, NULL) != -1)) {
- (void)assumes(fwexec(nvram_tool, NULL) != -1);
- (void)assumes(reboot(RB_AUTOBOOT) != -1);
- }
- } else
-#endif
- {
- (void)assumes(fwexec(remount_tool, NULL) != -1);
- }
-
- fix_bogus_file_metadata();
-}
-
-void
-fix_bogus_file_metadata(void)
-{
- static const struct {
- const char *path;
- const uid_t owner;
- const gid_t group;
- const mode_t needed_bits;
- const mode_t bad_bits;
- const bool create;
- } f[] = {
- { "/sbin/launchd", 0, 0, S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH, S_ISUID|S_ISGID|S_ISVTX|S_IWOTH, false },
- { _PATH_TMP, 0, 0, S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO, S_ISUID|S_ISGID, true },
- { _PATH_VARTMP, 0, 0, S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO, S_ISUID|S_ISGID, true },
- { "/var/folders", 0, 0, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, S_ISUID | S_ISGID, true },
- { LAUNCHD_DB_PREFIX, 0, 0, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, S_IWGRP | S_IWOTH, true },
- { LAUNCHD_DB_PREFIX "/com.apple.launchd", 0, 0, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, S_IWGRP | S_IWOTH, true },
- // Fixing <rdar://problem/7571633>.
- { _PATH_VARDB, 0, 0, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, S_IWGRP | S_IWOTH | S_ISUID | S_ISGID, true },
- { _PATH_VARDB "mds/", 0, 0, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, S_IWGRP | S_IWOTH | S_ISUID | S_ISGID, true },
-#if !TARGET_OS_EMBEDDED
- // Similar fix for <rdar://problem/6550172>.
- { "/Library/StartupItems", 0, 0, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, S_IWGRP | S_IWOTH | S_ISUID | S_ISGID, true },
-#endif
- };
- struct stat sb;
- size_t i;
-
- for (i = 0; i < (sizeof(f) / sizeof(f[0])); i++) {
- mode_t i_needed_bits;
- mode_t i_bad_bits;
- bool fix_mode = false;
- bool fix_id = false;
-
- if (!assumes(stat(f[i].path, &sb) != -1)) {
- fprintf(stdout, "Crucial filesystem check: Path not present: %s. %s\n", f[i].path, f[i].create ? "Will create." : "");
- if (f[i].create) {
- if (!assumes(mkdir(f[i].path, f[i].needed_bits) != -1)) {
- continue;
- } else if (!assumes(stat(f[i].path, &sb) != -1)) {
- continue;
- }
- } else {
- continue;
- }
- }
-
- i_needed_bits = ~sb.st_mode & f[i].needed_bits;
- i_bad_bits = sb.st_mode & f[i].bad_bits;
-
- if (i_bad_bits) {
- fprintf(stderr, "Crucial filesystem check: Removing bogus mode bits 0%o on path: %s\n", i_bad_bits, f[i].path);
- fix_mode = true;
- }
- if (i_needed_bits) {
- fprintf(stderr, "Crucial filesystem check: Adding missing mode bits 0%o on path: %s\n", i_needed_bits, f[i].path);
- fix_mode = true;
- }
- if (sb.st_uid != f[i].owner) {
- fprintf(stderr, "Crucial filesystem check: Fixing bogus UID %u on path: %s\n", sb.st_uid, f[i].path);
- fix_id = true;
- }
- if (sb.st_gid != f[i].group) {
- fprintf(stderr, "Crucial filesystem check: Fixing bogus GID %u on path: %s\n", sb.st_gid, f[i].path);
- fix_id = true;
- }
-
- if (fix_mode) {
- (void)assumes(chmod(f[i].path, (sb.st_mode & ~i_bad_bits) | i_needed_bits) != -1);
- }
- if (fix_id) {
- (void)assumes(chown(f[i].path, f[i].owner, f[i].group) != -1);
- }
- }
-}
-
-
-bool
-path_check(const char *path)
-{
- struct stat sb;
-
- if (stat(path, &sb) == 0)
- return true;
- return false;
-}
-
-bool
-is_safeboot(void)
-{
- int sbmib[] = { CTL_KERN, KERN_SAFEBOOT };
- uint32_t sb = 0;
- size_t sbsz = sizeof(sb);
-
- if (!assumes(sysctl(sbmib, 2, &sb, &sbsz, NULL, 0) == 0))
- return false;
-
- return (bool)sb;
-}
-
-bool
-is_netboot(void)
-{
- int nbmib[] = { CTL_KERN, KERN_NETBOOT };
- uint32_t nb = 0;
- size_t nbsz = sizeof(nb);
-
- if (!assumes(sysctl(nbmib, 2, &nb, &nbsz, NULL, 0) == 0))
- return false;
-
- return (bool)nb;
-}
-
-void
-empty_dir(const char *thedir, struct stat *psb)
-{
- struct dirent *de;
- struct stat psb2;
- DIR *od;
- int currend_dir_fd;
-
- if (!psb) {
- psb = &psb2;
- if (!assumes(lstat(thedir, psb) != -1)) {
- return;
- }
- }
-
- if (!assumes((currend_dir_fd = open(".", 0)) != -1)) {
- return;
- }
-
- if (!assumes(chdir(thedir) != -1)) {
- goto out;
- }
-
- if (!assumes(od = opendir("."))) {
- goto out;
- }
-
- while ((de = readdir(od))) {
- struct stat sb;
-
- if (strcmp(de->d_name, ".") == 0) {
- continue;
- }
-
- if (strcmp(de->d_name, "..") == 0) {
- continue;
- }
-
- if (!assumes(lstat(de->d_name, &sb) != -1)) {
- continue;
- }
-
- if (psb->st_dev != sb.st_dev) {
- (void)assumes(unmount(de->d_name, MNT_FORCE) != -1);
-
- /* Let's lstat() again to see if the unmount() worked and what was under it */
- if (!assumes(lstat(de->d_name, &sb) != -1)) {
- continue;
- }
-
- if (!assumes(psb->st_dev == sb.st_dev)) {
- continue;
- }
- }
-
- if (S_ISDIR(sb.st_mode)) {
- empty_dir(de->d_name, &sb);
- }
-
- (void)assumes(lchflags(de->d_name, 0) != -1);
- (void)assumes(remove(de->d_name) != -1);
- }
-
- (void)assumes(closedir(od) != -1);
-
-out:
- (void)assumes(fchdir(currend_dir_fd) != -1);
- (void)assumes(close(currend_dir_fd) != -1);
-}
-
-int
-touch_file(const char *path, mode_t m)
-{
- int fd = open(path, O_CREAT, m);
-
- if (fd == -1)
- return -1;
-
- return close(fd);
-}
-
-void
-apply_sysctls_from_file(const char *thefile)
-{
- const char *sysctl_tool[] = { "sysctl", "-w", NULL, NULL };
- size_t ln_len = 0;
- char *val, *tmpstr;
- FILE *sf;
-
- if (!(sf = fopen(thefile, "r")))
- return;
-
- while ((val = fgetln(sf, &ln_len))) {
- if (ln_len == 0) {
- continue;
- }
- if (!assumes((tmpstr = malloc(ln_len + 1)) != NULL)) {
- continue;
- }
- memcpy(tmpstr, val, ln_len);
- tmpstr[ln_len] = 0;
- val = tmpstr;
-
- if (val[ln_len - 1] == '\n' || val[ln_len - 1] == '\r') {
- val[ln_len - 1] = '\0';
- }
-
- while (*val && isspace(*val))
- val++;
- if (*val == '\0' || *val == '#') {
- goto skip_sysctl_tool;
- }
- sysctl_tool[2] = val;
- (void)assumes(fwexec(sysctl_tool, NULL) != -1);
-skip_sysctl_tool:
- free(tmpstr);
- }
-
- (void)assumes(fclose(sf) == 0);
-}
-
-static CFStringRef
-copySystemBuildVersion(void)
-{
- CFStringRef build = NULL;
- const char path[] = "/System/Library/CoreServices/SystemVersion.plist";
- CFURLRef plistURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorSystemDefault, (const uint8_t *)path, sizeof(path) - 1, false);
-
- CFPropertyListRef plist = NULL;
- if (plistURL && (plist = CFPropertyListCreateFromFile(plistURL))) {
- if (CFTypeCheck(plist, CFDictionary)) {
- build = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)plist, _kCFSystemVersionBuildVersionKey);
- if (build && CFTypeCheck(build, CFString)) {
- CFRetain(build);
- } else {
- build = CFSTR("99Z999");
- }
- }
-
- CFRelease(plist);
- } else {
- build = CFSTR("99Z999");
- }
-
- if (plistURL) {
- CFRelease(plistURL);
- }
-
- return build;
-}
-
-void
-do_sysversion_sysctl(void)
-{
- int mib[] = { CTL_KERN, KERN_OSVERSION };
- CFStringRef buildvers;
- char buf[1024];
- size_t bufsz = sizeof(buf);
-
- /* <rdar://problem/4477682> ER: launchd should set kern.osversion very early in boot */
-
- if (sysctl(mib, 2, buf, &bufsz, NULL, 0) == -1) {
- fprintf(stderr, "sysctl(): %s\n", strerror(errno));
- return;
- }
-
- if (buf[0] != '\0') {
- return;
- }
-
- buildvers = copySystemBuildVersion();
- if (assumes(buildvers)) {
- CFStringGetCString(buildvers, buf, sizeof(buf), kCFStringEncodingUTF8);
- (void)assumes(sysctl(mib, 2, NULL, 0, buf, strlen(buf) + 1) != -1);
- }
-
- CFRelease(buildvers);
-}
-
-void
-do_application_firewall_magic(int sfd, launch_data_t thejob)
-{
- const char *prog = NULL, *partialprog = NULL;
- char *path, *pathtmp, **pathstmp;
- char *paths[100];
- launch_data_t tmp;
-
- /*
- * Sigh...
- * <rdar://problem/4684434> setsockopt() with the executable path as the argument
- */
-
- if ((tmp = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_PROGRAM))) {
- prog = launch_data_get_string(tmp);
- }
-
- if (!prog) {
- if ((tmp = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_PROGRAMARGUMENTS))) {
- if ((tmp = launch_data_array_get_index(tmp, 0))) {
- if (assumes((partialprog = launch_data_get_string(tmp)) != NULL)) {
- if (partialprog[0] == '/') {
- prog = partialprog;
- }
- }
- }
- }
- }
-
- if (!prog) {
- pathtmp = path = strdup(getenv("PATH"));
-
- pathstmp = paths;
-
- while ((*pathstmp = strsep(&pathtmp, ":"))) {
- if (**pathstmp != '\0') {
- pathstmp++;
- }
- }
-
- free(path);
- pathtmp = alloca(MAXPATHLEN);
-
- pathstmp = paths;
-
- for (; *pathstmp; pathstmp++) {
- snprintf(pathtmp, MAXPATHLEN, "%s/%s", *pathstmp, partialprog);
- if (path_check(pathtmp)) {
- prog = pathtmp;
- break;
- }
- }
- }
-
- if (assumes(prog != NULL)) {
- /* The networking team has asked us to ignore the failure of this API if errno == ENOPROTOOPT */
- (void)assumes(setsockopt(sfd, SOL_SOCKET, SO_EXECPATH, prog, (socklen_t)(strlen(prog) + 1)) != -1 || errno == ENOPROTOOPT);
- }
-}
-
-
-void
-preheat_page_cache_hack(void)
-{
- struct dirent *de;
- DIR *thedir;
-
- /* Disable this hack for now */
- return;
-
- if ((thedir = opendir("/etc/preheat_at_boot")) == NULL) {
- return;
- }
-
- while ((de = readdir(thedir))) {
- struct stat sb;
- void *junkbuf;
- int fd;
-
- if (de->d_name[0] == '.') {
- continue;
- }
-
- if ((fd = open(de->d_name, O_RDONLY)) == -1) {
- continue;
- }
-
- if (fstat(fd, &sb) != -1) {
- if ((sb.st_size < 10*1024*1024) && (junkbuf = malloc((size_t)sb.st_size)) != NULL) {
- (void)assumes(read(fd, junkbuf, (size_t)sb.st_size) == (ssize_t)sb.st_size);
- free(junkbuf);
- }
- }
-
- close(fd);
- }
-
- closedir(thedir);
-}
-
-
-void
-do_bootroot_magic(void)
-{
- const char *kextcache_tool[] = { "kextcache", "-U", "/", NULL };
- CFTypeRef bootrootProp;
- io_service_t chosen;
- int wstatus;
- pid_t p;
-
- chosen = IORegistryEntryFromPath(kIOMasterPortDefault, "IODeviceTree:/chosen");
-
- if (!assumes(chosen)) {
- return;
- }
-
- bootrootProp = IORegistryEntryCreateCFProperty(chosen, CFSTR(kBootRootActiveKey), kCFAllocatorDefault, 0);
-
- IOObjectRelease(chosen);
-
- if (!bootrootProp) {
- return;
- }
-
- CFRelease(bootrootProp);
-
- if (!assumes((p = fwexec(kextcache_tool, &wstatus)) != -1)) {
- return;
- }
-
- if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == EX_OSFILE) {
- (void)assumes(reboot(RB_AUTOBOOT) != -1);
- }
-}
-
-void
-do_file_init(void)
-{
- struct stat sb;
-
- if (stat("/AppleInternal", &sb) == 0 && stat("/var/db/disableAppleInternal", &sb) == -1) {
- do_apple_internal_magic = true;
- }
-
- char bootargs[128];
- size_t len = sizeof(bootargs);
- int r = sysctlbyname("kern.bootargs", bootargs, &len, NULL, 0);
- if (r == 0 && (strnstr(bootargs, "-v", len) != NULL || strnstr(bootargs, "-s", len))) {
- g_verbose_boot = true;
- }
-
- if (stat("/var/db/.launchd_shutdown_debugging", &sb) == 0 && g_verbose_boot) {
- g_startup_debugging = true;
- }
-}
+++ /dev/null
-.Dd 1 May, 2009
-.Dt launchd 8
-.Os Darwin
-.Sh NAME
-.Nm launchd
-.Nd System wide and per-user daemon/agent manager
-.Sh SYNOPSIS
-.Nm
-.Op Fl d
-.Op Fl D
-.Op Fl s
-.Op Fl S Ar SessionType
-.Op Ar -- command Op Ar args ...
-.Sh DESCRIPTION
-.Nm
-manages processes, both for the system as a whole and for individual users.
-The primary and preferred interface to
-.Nm
-is via the
-.Xr launchctl 1
-tool which (among other options) allows the user or administrator to load and unload jobs.
-Where possible, it is preferable for jobs to launch on demand based on criteria specified
-in their respective configuration files.
-.Pp
-During boot
-.Nm
-is invoked by the kernel to run as the first process on the system and to further bootstrap the rest of the system.
-.Pp
-You cannot invoke
-.Nm
-directly.
-.Sh ENVIRONMENTAL VARIABLES
-.Bl -tag -width -indent
-.It Pa LAUNCHD_SOCKET
-This variable is exported when invoking a command via the launchd command line. It informs launchctl how to find the correct launchd to talk to.
-.El
-.Sh NOTES
-In Darwin, the canonical way to launch a daemon is through
-.Nm launchd
-as opposed to more traditional mechanisms or mechanisms provided in earlier versions of Mac OS X. These alternate methods should
-be considered deprecated and not suitable for new projects.
-.Pp
-In the
-.Nm launchd
-lexicon, a "daemon" is, by definition, a system-wide service of which there is one instance for all clients. An "agent" is a service that runs on
-a per-user basis. Daemons should not attempt to display UI or interact directly with a user's login session. Any and all work that involves interacting
-with a user should be done through agents.
-.Pp
-If you wish your service to run as a certain user, in that user's environment, making it a
-.Nm launchd
-agent is the ONLY supported means of accomplishing this on Mac OS X. In other words, it is not sufficient to perform a
-.Xr setuid 2
-to become a user in the truest sense on Mac OS X.
-.Sh FILES
-.Bl -tag -width "/System/Library/LaunchDaemons" -compact
-.It Pa ~/Library/LaunchAgents
-Per-user agents provided by the user.
-.It Pa /Library/LaunchAgents
-Per-user agents provided by the administrator.
-.It Pa /Library/LaunchDaemons
-System-wide daemons provided by the administrator.
-.It Pa /System/Library/LaunchAgents
-Per-user agents provided by Mac OS X.
-.It Pa /System/Library/LaunchDaemons
-System-wide daemons provided by Mac OS X.
-.El
-.Sh SEE ALSO
-.Xr launchctl 1 ,
-.Xr launchd.plist 5 ,
+++ /dev/null
-/*
- * Copyright (c) 2005 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@
- */
-
-static const char *const __rcs_file_version__ = "$Revision: 24863 $";
-
-#include "config.h"
-#include "launchd.h"
-
-#include <sys/types.h>
-#include <sys/queue.h>
-#include <sys/event.h>
-#include <sys/stat.h>
-#include <sys/ucred.h>
-#include <sys/fcntl.h>
-#include <sys/un.h>
-#include <sys/wait.h>
-#include <sys/sysctl.h>
-#include <sys/sockio.h>
-#include <sys/time.h>
-#include <sys/resource.h>
-#include <sys/ioctl.h>
-#include <sys/mount.h>
-#include <sys/kern_event.h>
-#include <sys/reboot.h>
-#include <sys/socket.h>
-#include <sys/syscall.h>
-#include <net/if.h>
-#include <netinet/in.h>
-#include <netinet/in_var.h>
-#include <netinet6/nd6.h>
-#include <ifaddrs.h>
-#include <unistd.h>
-#include <signal.h>
-#include <errno.h>
-#include <libgen.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <paths.h>
-#include <pwd.h>
-#include <grp.h>
-#include <ttyent.h>
-#include <dlfcn.h>
-#include <dirent.h>
-#include <string.h>
-#include <setjmp.h>
-#include <spawn.h>
-#include <sched.h>
-#include <pthread.h>
-#include <util.h>
-
-#if HAVE_LIBAUDITD
-#include <bsm/auditd_lib.h>
-#include <bsm/audit_session.h>
-#endif
-
-#include "bootstrap.h"
-#include "vproc.h"
-#include "vproc_priv.h"
-#include "vproc_internal.h"
-#include "launch.h"
-#include "launch_internal.h"
-
-#include "launchd_runtime.h"
-#include "launchd_core_logic.h"
-#include "launchd_unix_ipc.h"
-
-#define LAUNCHD_CONF ".launchd.conf"
-
-extern char **environ;
-
-static void pfsystem_callback(void *, struct kevent *);
-
-static kq_callback kqpfsystem_callback = pfsystem_callback;
-
-static void pid1_magic_init(void);
-
-static void testfd_or_openfd(int fd, const char *path, int flags);
-static bool get_network_state(void);
-static void monitor_networking_state(void);
-static void fatal_signal_handler(int sig, siginfo_t *si, void *uap);
-static void handle_pid1_crashes_separately(void);
-static void do_pid1_crash_diagnosis_mode(const char *msg);
-static int basic_fork(void);
-static bool do_pid1_crash_diagnosis_mode2(const char *msg);
-
-static void *update_thread(void *nothing);
-
-static bool re_exec_in_single_user_mode;
-static void *crash_addr;
-static pid_t crash_pid;
-
-bool shutdown_in_progress;
-bool fake_shutdown_in_progress;
-bool network_up;
-char g_username[128] = "__Uninitialized__";
-char g_my_label[128] = "__Uninitialized__";
-char g_launchd_database_dir[PATH_MAX];
-FILE *g_console = NULL;
-int32_t g_sync_frequency = 30;
-
-int
-main(int argc, char *const *argv)
-{
- bool sflag = false;
- int ch;
-
- testfd_or_openfd(STDIN_FILENO, _PATH_DEVNULL, O_RDONLY);
- testfd_or_openfd(STDOUT_FILENO, _PATH_DEVNULL, O_WRONLY);
- testfd_or_openfd(STDERR_FILENO, _PATH_DEVNULL, O_WRONLY);
-
- if (g_use_gmalloc) {
- if (!getenv("DYLD_INSERT_LIBRARIES")) {
- setenv("DYLD_INSERT_LIBRARIES", "/usr/lib/libgmalloc.dylib", 1);
- setenv("MALLOC_STRICT_SIZE", "1", 1);
- execv(argv[0], argv);
- } else {
- unsetenv("DYLD_INSERT_LIBRARIES");
- unsetenv("MALLOC_STRICT_SIZE");
- }
- } else if (g_malloc_log_stacks) {
- if (!getenv("MallocStackLogging")) {
- setenv("MallocStackLogging", "1", 1);
- execv(argv[0], argv);
- } else {
- unsetenv("MallocStackLogging");
- }
- }
-
- while ((ch = getopt(argc, argv, "s")) != -1) {
- switch (ch) {
- case 's': sflag = true; break; /* single user */
- case '?': /* we should do something with the global optopt variable here */
- default:
- fprintf(stderr, "%s: ignoring unknown arguments\n", getprogname());
- break;
- }
- }
-
- if (getpid() != 1 && getppid() != 1) {
- fprintf(stderr, "%s: This program is not meant to be run directly.\n", getprogname());
- exit(EXIT_FAILURE);
- }
-
- launchd_runtime_init();
-
- if (pid1_magic) {
- int cfd = -1;
- if (launchd_assumes((cfd = open(_PATH_CONSOLE, O_WRONLY | O_NOCTTY)) != -1)) {
- _fd(cfd);
- if (!launchd_assumes((g_console = fdopen(cfd, "w")) != NULL)) {
- close(cfd);
- }
- }
- }
-
- if (NULL == getenv("PATH")) {
- setenv("PATH", _PATH_STDPATH, 1);
- }
-
- if (pid1_magic) {
- pid1_magic_init();
- } else {
- ipc_server_init();
-
- runtime_log_push();
-
- struct passwd *pwent = getpwuid(getuid());
- if (pwent) {
- strlcpy(g_username, pwent->pw_name, sizeof(g_username) - 1);
- }
-
- snprintf(g_my_label, sizeof(g_my_label), "com.apple.launchd.peruser.%u", getuid());
-
- auditinfo_addr_t auinfo;
- if (launchd_assumes(getaudit_addr(&auinfo, sizeof(auinfo)) != -1)) {
- g_audit_session = auinfo.ai_asid;
- runtime_syslog(LOG_DEBUG, "Our audit session ID is %i", g_audit_session);
- }
-
- g_audit_session_port = _audit_session_self();
- snprintf(g_launchd_database_dir, sizeof(g_launchd_database_dir), LAUNCHD_DB_PREFIX "/com.apple.launchd.peruser.%u", getuid());
- runtime_syslog(LOG_DEBUG, "Per-user launchd for UID %u (%s) has begun.", getuid(), g_username);
- }
-
- if (pid1_magic) {
- runtime_syslog(LOG_NOTICE | LOG_CONSOLE, "*** launchd[1] has started up. ***");
- if (g_use_gmalloc) {
- runtime_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Using libgmalloc. ***");
- }
- if (g_malloc_log_stacks) {
- runtime_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Logging stacks of malloc(3) allocations. ***");
- }
-
- if (g_verbose_boot) {
- runtime_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Verbose boot, will log to /dev/console. ***");
- }
-
- if (g_shutdown_debugging) {
- runtime_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Shutdown debugging is enabled. ***");
- }
-
- /* PID 1 doesn't have a flat namespace. */
- g_flat_mach_namespace = false;
- } else {
- if (g_use_gmalloc) {
- runtime_syslog(LOG_NOTICE, "*** Per-user launchd using libgmalloc. ***");
- }
- }
-
- monitor_networking_state();
-
- if (pid1_magic) {
- handle_pid1_crashes_separately();
- } else {
- #if !TARGET_OS_EMBEDDED
- /* prime shared memory before the 'bootstrap_port' global is set to zero */
- _vproc_transaction_begin();
- _vproc_transaction_end();
- #endif
- }
-
- if (pid1_magic) {
- /* Start the update thread -- rdar://problem/5039559&6153301 */
- pthread_t t = NULL;
- int err = pthread_create(&t, NULL, update_thread, NULL);
- (void)launchd_assumes(err == 0);
- (void)launchd_assumes(pthread_detach(t) == 0);
- }
-
- jobmgr_init(sflag);
-
- launchd_runtime_init2();
-
- launchd_runtime();
-}
-
-void
-handle_pid1_crashes_separately(void)
-{
- struct sigaction fsa;
-
- fsa.sa_sigaction = fatal_signal_handler;
- fsa.sa_flags = SA_SIGINFO;
- sigemptyset(&fsa.sa_mask);
-
- (void)launchd_assumes(sigaction(SIGILL, &fsa, NULL) != -1);
- (void)launchd_assumes(sigaction(SIGFPE, &fsa, NULL) != -1);
- (void)launchd_assumes(sigaction(SIGBUS, &fsa, NULL) != -1);
- (void)launchd_assumes(sigaction(SIGSEGV, &fsa, NULL) != -1);
- (void)launchd_assumes(sigaction(SIGABRT, &fsa, NULL) != -1);
- (void)launchd_assumes(sigaction(SIGTRAP, &fsa, NULL) != -1);
-}
-
-void *update_thread(void *nothing __attribute__((unused)))
-{
- /* <rdar://problem/7385963> use IOPOL_PASSIVE for sync thread */
- (void)launchd_assumes(setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, IOPOL_PASSIVE) != -1);
-
- while( g_sync_frequency ) {
- sync();
- sleep(g_sync_frequency);
- }
-
- runtime_syslog(LOG_DEBUG, "Update thread exiting.");
- return NULL;
-}
-
-#define PID1_CRASH_LOGFILE "/var/log/launchd-pid1.crash"
-
-/* This hack forces the dynamic linker to resolve these symbols ASAP */
-static __attribute__((unused)) typeof(sync) *__junk_dyld_trick1 = sync;
-static __attribute__((unused)) typeof(sleep) *__junk_dyld_trick2 = sleep;
-static __attribute__((unused)) typeof(reboot) *__junk_dyld_trick3 = reboot;
-
-void
-do_pid1_crash_diagnosis_mode(const char *msg)
-{
- if (g_wsp) {
- kill(g_wsp, SIGKILL);
- sleep(3);
- g_wsp = 0;
- }
-
- while (g_shutdown_debugging && !do_pid1_crash_diagnosis_mode2(msg)) {
- sleep(1);
- }
-}
-
-int
-basic_fork(void)
-{
- int wstatus = 0;
- pid_t p;
-
- switch ((p = fork())) {
- case -1:
- runtime_syslog(LOG_ERR | LOG_CONSOLE, "Can't fork PID 1 copy for crash debugging: %m");
- return p;
- case 0:
- return p;
- default:
- do {
- (void)waitpid(p, &wstatus, 0);
- } while(!WIFEXITED(wstatus));
-
- fprintf(stdout, "PID 1 copy: exit status: %d\n", WEXITSTATUS(wstatus));
-
- return 1;
- }
-
- return -1;
-}
-
-bool
-do_pid1_crash_diagnosis_mode2(const char *msg)
-{
- if (basic_fork() == 0) {
- /* Neuter our bootstrap port so that the shell doesn't try talking to us while
- * we're blocked waiting on it.
- */
- if (g_console) {
- fflush(g_console);
- }
- task_set_bootstrap_port(mach_task_self(), MACH_PORT_NULL);
- if (basic_fork() != 0) {
- if (g_console) {
- fflush(g_console);
- }
- return true;
- }
- } else {
- return true;
- }
-
- int fd;
- revoke(_PATH_CONSOLE);
- if ((fd = open(_PATH_CONSOLE, O_RDWR)) == -1) {
- _exit(2);
- }
- if (login_tty(fd) == -1) {
- _exit(3);
- }
- setenv("TERM", "vt100", 1);
- fprintf(stdout, "\n");
- fprintf(stdout, "Entering launchd PID 1 debugging mode...\n");
- fprintf(stdout, "The PID 1 launchd has crashed %s.\n", msg);
- fprintf(stdout, "It has fork(2)ed itself for debugging.\n");
- fprintf(stdout, "To debug the crashing thread of PID 1:\n");
- fprintf(stdout, " gdb attach %d\n", getppid());
- fprintf(stdout, "To exit this shell and shut down:\n");
- fprintf(stdout, " kill -9 1\n");
- fprintf(stdout, "A sample of PID 1 has been written to %s\n", PID1_CRASH_LOGFILE);
- fprintf(stdout, "\n");
- fflush(stdout);
-
- execl(_PATH_BSHELL, "-sh", NULL);
- syslog(LOG_ERR, "can't exec %s for PID 1 crash debugging: %m", _PATH_BSHELL);
- _exit(EXIT_FAILURE);
-}
-
-void
-fatal_signal_handler(int sig, siginfo_t *si, void *uap __attribute__((unused)))
-{
- const char *doom_why = "at instruction";
- char msg[128];
- char *sample_args[] = { "/usr/bin/sample", "1", "1", "-file", PID1_CRASH_LOGFILE, NULL };
- pid_t sample_p;
- int wstatus;
-
- crash_addr = si->si_addr;
- crash_pid = si->si_pid;
-
- unlink(PID1_CRASH_LOGFILE);
-
- switch ((sample_p = vfork())) {
- case 0:
- execve(sample_args[0], sample_args, environ);
- _exit(EXIT_FAILURE);
- break;
- default:
- waitpid(sample_p, &wstatus, 0);
- break;
- case -1:
- break;
- }
-
- switch (sig) {
- default:
- case 0:
- break;
- case SIGBUS:
- case SIGSEGV:
- doom_why = "trying to read/write";
- case SIGILL:
- case SIGFPE:
- snprintf(msg, sizeof(msg), "%s: %p (%s sent by PID %u)", doom_why, crash_addr, strsignal(sig), crash_pid);
- sync();
- do_pid1_crash_diagnosis_mode(msg);
- sleep(3);
- reboot(0);
- break;
- }
-}
-
-void
-pid1_magic_init(void)
-{
- (void)launchd_assumes(setsid() != -1);
- (void)launchd_assumes(chdir("/") != -1);
- (void)launchd_assumes(setlogin("root") != -1);
-
- strcpy(g_my_label, "com.apple.launchd");
-
-#if !TARGET_OS_EMBEDDED
- auditinfo_addr_t auinfo = {
- .ai_termid = { .at_type = AU_IPv4 },
- .ai_asid = AU_ASSIGN_ASID,
- .ai_auid = AU_DEFAUDITID,
- .ai_flags = AU_SESSION_FLAG_IS_INITIAL,
- };
-
- if (!launchd_assumes(setaudit_addr(&auinfo, sizeof(auinfo)) != -1)) {
- runtime_syslog(LOG_WARNING | LOG_CONSOLE, "Could not set audit session: %s.", strerror(errno));
- _exit(EXIT_FAILURE);
- }
-
- g_audit_session = auinfo.ai_asid;
- runtime_syslog(LOG_DEBUG, "Audit Session ID: %i", g_audit_session);
-
- g_audit_session_port = _audit_session_self();
-#endif // !TARGET_OS_EMBEDDED
-
- strcpy(g_launchd_database_dir, LAUNCHD_DB_PREFIX "/com.apple.launchd");
-}
-
-char *
-launchd_data_base_path(int db_type)
-{
- static char result[PATH_MAX];
- static int last_db_type = -1;
-
- if (db_type == last_db_type) {
- return result;
- }
-
- switch (db_type) {
- case LAUNCHD_DB_TYPE_OVERRIDES :
- snprintf(result, sizeof(result), "%s/%s", g_launchd_database_dir, "overrides.plist");
- last_db_type = db_type;
- break;
- case LAUNCHD_DB_TYPE_JOBCACHE :
- snprintf(result, sizeof(result), "%s/%s", g_launchd_database_dir, "jobcache.launchdata");
- last_db_type = db_type;
- break;
- default :
- break;
- }
-
- return result;
-}
-
-int
-_fd(int fd)
-{
- if (fd >= 0) {
- (void)launchd_assumes(fcntl(fd, F_SETFD, 1) != -1);
- }
- return fd;
-}
-
-void
-launchd_shutdown(void)
-{
- int64_t now;
-
- if (shutdown_in_progress) {
- return;
- }
-
- runtime_ktrace0(RTKT_LAUNCHD_EXITING);
-
- shutdown_in_progress = true;
-
- if (pid1_magic || g_log_per_user_shutdown) {
- /*
- * When this changes to a more sustainable API, update this:
- * http://howto.apple.com/db.cgi?Debugging_Apps_Non-Responsive_At_Shutdown
- */
- runtime_setlogmask(LOG_UPTO(LOG_DEBUG));
- }
-
- runtime_log_push();
-
- now = runtime_get_wall_time();
-
- char *term_who = pid1_magic ? "System shutdown" : "Per-user launchd termination for ";
- runtime_syslog(LOG_INFO, "%s%s began", term_who, pid1_magic ? "" : g_username);
-
- launchd_assert(jobmgr_shutdown(root_jobmgr) != NULL);
-
-#if HAVE_LIBAUDITD
- if (pid1_magic) {
- (void)launchd_assumes(audit_quick_stop() == 0);
- }
-#endif
-}
-
-void
-launchd_single_user(void)
-{
- runtime_syslog(LOG_NOTICE, "Going to single-user mode");
-
- re_exec_in_single_user_mode = true;
-
- launchd_shutdown();
-
- sleep(3);
-
- runtime_kill(-1, SIGKILL);
-}
-
-void
-launchd_SessionCreate(void)
-{
-#if !TARGET_OS_EMBEDDED
- auditinfo_addr_t auinfo = {
- .ai_termid = { .at_type = AU_IPv4 },
- .ai_asid = AU_ASSIGN_ASID,
- .ai_auid = getuid(),
- .ai_flags = 0,
- };
- if (launchd_assumes(setaudit_addr(&auinfo, sizeof(auinfo)) == 0)) {
- char session[16];
- snprintf(session, sizeof(session), "%x", auinfo.ai_asid);
- setenv("SECURITYSESSIONID", session, 1);
- } else {
- runtime_syslog(LOG_WARNING, "Could not set audit session: %s.", strerror(errno));
- }
-#endif // !TARGET_OS_EMBEDDED
-}
-
-void
-testfd_or_openfd(int fd, const char *path, int flags)
-{
- int tmpfd;
-
- if (-1 != (tmpfd = dup(fd))) {
- (void)launchd_assumes(runtime_close(tmpfd) == 0);
- } else {
- if (-1 == (tmpfd = open(path, flags | O_NOCTTY, DEFFILEMODE))) {
- runtime_syslog(LOG_ERR, "open(\"%s\", ...): %m", path);
- } else if (tmpfd != fd) {
- (void)launchd_assumes(dup2(tmpfd, fd) != -1);
- (void)launchd_assumes(runtime_close(tmpfd) == 0);
- }
- }
-}
-
-bool
-get_network_state(void)
-{
- struct ifaddrs *ifa, *ifai;
- bool up = false;
- int r;
-
- /* Workaround 4978696: getifaddrs() reports false ENOMEM */
- while ((r = getifaddrs(&ifa)) == -1 && errno == ENOMEM) {
- runtime_syslog(LOG_DEBUG, "Worked around bug: 4978696");
- (void)launchd_assumes(sched_yield() != -1);
- }
-
- if (!launchd_assumes(r != -1)) {
- return network_up;
- }
-
- for (ifai = ifa; ifai; ifai = ifai->ifa_next) {
- if (!(ifai->ifa_flags & IFF_UP)) {
- continue;
- }
- if (ifai->ifa_flags & IFF_LOOPBACK) {
- continue;
- }
- if (ifai->ifa_addr->sa_family != AF_INET && ifai->ifa_addr->sa_family != AF_INET6) {
- continue;
- }
- up = true;
- break;
- }
-
- freeifaddrs(ifa);
-
- return up;
-}
-
-void
-monitor_networking_state(void)
-{
- int pfs = _fd(socket(PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT));
- struct kev_request kev_req;
-
- network_up = get_network_state();
-
- if (!launchd_assumes(pfs != -1)) {
- return;
- }
-
- memset(&kev_req, 0, sizeof(kev_req));
- kev_req.vendor_code = KEV_VENDOR_APPLE;
- kev_req.kev_class = KEV_NETWORK_CLASS;
-
- if (!launchd_assumes(ioctl(pfs, SIOCSKEVFILT, &kev_req) != -1)) {
- runtime_close(pfs);
- return;
- }
-
- (void)launchd_assumes(kevent_mod(pfs, EVFILT_READ, EV_ADD, 0, 0, &kqpfsystem_callback) != -1);
-}
-
-void
-pfsystem_callback(void *obj __attribute__((unused)), struct kevent *kev)
-{
- bool new_networking_state;
- char buf[1024];
-
- (void)launchd_assumes(read((int)kev->ident, &buf, sizeof(buf)) != -1);
-
- new_networking_state = get_network_state();
-
- if (new_networking_state != network_up) {
- network_up = new_networking_state;
- jobmgr_dispatch_all_semaphores(root_jobmgr);
- }
-}
-
-void
-_log_launchd_bug(const char *rcs_rev, const char *path, unsigned int line, const char *test)
-{
- int saved_errno = errno;
- char buf[100];
- const char *file = strrchr(path, '/');
- char *rcs_rev_tmp = strchr(rcs_rev, ' ');
-
- runtime_ktrace1(RTKT_LAUNCHD_BUG);
-
- if (!file) {
- file = path;
- } else {
- file += 1;
- }
-
- if (!rcs_rev_tmp) {
- strlcpy(buf, rcs_rev, sizeof(buf));
- } else {
- strlcpy(buf, rcs_rev_tmp + 1, sizeof(buf));
- rcs_rev_tmp = strchr(buf, ' ');
- if (rcs_rev_tmp) {
- *rcs_rev_tmp = '\0';
- }
- }
-
- runtime_syslog(LOG_NOTICE, "Bug: %s:%u (%s):%u: %s", file, line, buf, saved_errno, test);
-}
+++ /dev/null
-.Dd 1 May, 2009
-.Dt launchd.conf 5
-.Os Darwin
-.Sh NAME
-.Nm launchd.conf
-.Nd launchd configuration file
-.Sh SYNOPSIS
-.Nm $HOME/.launchd.conf
-.Nm /etc/launchd.conf
-.Sh DESCRIPTION
-.Nm
-contains a list of subcommands
-.Ar ( load ,
-.Ar unload ,
-etc.) to run via
-.Xr launchctl 1
-when
-.Xr launchd 8
-starts.
-.Sh FILES
-.Bl -tag -width "$HOME/.launchd.conf" -compact
-.It Pa $HOME/.launchd.conf
-Your launchd configuration file (currently unsupported).
-.It Pa /etc/launchd.conf
-The system's launchd configuration file.
-.El
-.Sh SEE ALSO
-.Xr launchctl 1 ,
-.Xr launchd 8 ,
-.Xr launchd.plist 5
+++ /dev/null
-/*
- * Copyright (c) 2005 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@
- */
-#ifndef __LAUNCHD_H__
-#define __LAUNCHD_H__
-
-#include <mach/mach.h>
-#include <mach/port.h>
-#include "launch.h"
-#include "bootstrap.h"
-#include "launchd_runtime.h"
-
-struct kevent;
-struct conncb;
-
-extern bool shutdown_in_progress;
-extern bool fake_shutdown_in_progress;
-extern bool network_up;
-extern bool g_simulate_pid1_crash;
-extern FILE *g_console;
-extern char g_launchd_database_dir[PATH_MAX];
-
-bool init_check_pid(pid_t);
-
-launch_data_t launchd_setstdio(int d, launch_data_t o);
-void launchd_SessionCreate(void);
-void launchd_shutdown(void);
-void launchd_single_user(void);
-boolean_t launchd_mach_ipc_demux(mach_msg_header_t *Request, mach_msg_header_t *Reply);
-
-enum {
- LAUNCHD_DB_TYPE_OVERRIDES,
- LAUNCHD_DB_TYPE_JOBCACHE,
- LAUNCHD_DB_TYPE_LAST,
-};
-char *launchd_data_base_path(int db_type);
-
-void mach_start_shutdown(void);
-
-int _fd(int fd);
-
-#endif
+++ /dev/null
-< mach* >
- mach-per-user-lookup
+++ /dev/null
-.Dd 1 May, 2009
-.Dt launchd.plist 5
-.Os Darwin
-.Sh NAME
-.Nm launchd.plist
-.Nd System wide and per-user daemon/agent configuration files
-.Sh DESCRIPTION
-This document details the parameters that can be given to an XML property list that can be loaded into
-.Nm launchd
-with
-.Nm launchctl .
-.Sh EXPECTATIONS
-Daemons or agents managed by
-.Nm launchd
-are expected to behave certain ways.
-.Pp
-A daemon or agent launched by
-.Nm launchd
-MUST NOT do the following in the process directly launched by
-.Nm launchd :
-.Pp
-.Bl -bullet -offset indent -compact
-.It
-Call
-.Xr daemon 3 .
-.It
-Do the moral equivalent of
-.Xr daemon 3
-by calling
-.Xr fork 2
-and have the parent process
-.Xr exit 3
-or
-.Xr _exit 2 .
-.El
-.Pp
-A daemon or agent launched by
-.Nm launchd
-SHOULD NOT do the following as a part of their startup initialization:
-.Pp
-.Bl -bullet -offset indent -compact
-.It
-Setup the user ID or group ID.
-.It
-Setup the working directory.
-.It
-.Xr chroot 2
-.It
-.Xr setsid 2
-.It
-Close "stray" file descriptors.
-.It
-Change
-.Xr stdio 3
-to /dev/null.
-.It
-Setup resource limits with
-.Xr setrusage 2 .
-.It
-Setup priority with
-.Xr setpriority 2 .
-.It
-Ignore the SIGTERM signal.
-.El
-.Pp
-A daemon or agent launched by
-.Nm launchd
-SHOULD:
-.Pp
-.Bl -bullet -offset indent -compact
-.It
-Launch on demand given criteria specified in the XML property list.
-More information can be found later in this man page.
-.It
-Catch the SIGTERM signal.
-.El
-.Sh XML PROPERTY LIST KEYS
-The following keys can be used to describe the configuration details of your daemon or agent.
-Property lists are Apple's standard configuration file format. Please see
-.Xr plist 5
-for more information. Please note: property list files are expected to have their name end in ".plist".
-Also please note that it is the expected convention for launchd property list files to be named <Label>.plist.
-Thus, if your job label is "com.apple.sshd", your plist file should be named "com.apple.sshd.plist".
-.Pp
-.Bl -ohang
-.It Sy Label <string>
-This required key uniquely identifies the job to
-.Nm launchd .
-.It Sy Disabled <boolean>
-This optional key is used as a hint to
-.Xr launchctl 1
-that it should not submit this job to
-.Nm launchd
-when loading a job or jobs. The value of this key does NOT reflect the current state of the job on the running system. If you wish to know whether a job is loaded in launchd, reading this key from a configuration file yourself is not a
-sufficient test. You should query
-.Nm launchd
-for the presence of the job using the
-.Xr launchctl 1
-.Ar list
-subcommand or use the ServiceManagement framework's SMJobCopyDictionary() method.
-.Pp
-Note that as of Mac OS X v10.6, this key's value in a configuration
-file conveys a default value, which is changed with the
-.Op Ar -w
-option of the
-.Xr launchctl 1
-.Ar load
-and
-.Ar unload
-subcommands. These subcommands no longer modify the configuration file, so the value displayed in the configuration file is not necessarily the value that
-.Xr launchctl 1
-will apply. See
-.Xr launchctl 1
-for more information.
-.Pp
-Please also be mindful that you should only use this key if the provided
-on-demand and KeepAlive criteria are insufficient to describe the conditions under which your job needs to run. The cost
-to have a job loaded in
-.Nm launchd
-is negligible, so there is no harm in loading a job which only runs once or very rarely.
-.It Sy UserName <string>
-This optional key specifies the user to run the job as. This key is only applicable when launchd is running as root.
-.It Sy GroupName <string>
-This optional key specifies the group to run the job as. This key is only applicable when launchd is running as root. If UserName is set and GroupName is not, the the group will be set to the default group of the user.
-.It Sy inetdCompatibility <dictionary>
-The presence of this key specifies that the daemon expects to be run as if it were launched from inetd.
-.Bl -ohang -offset indent
-.It Sy Wait <boolean>
-This flag corresponds to the "wait" or "nowait" option of inetd. If true, then the listening socket is passed via the standard in/out/error file descriptors. If false, then
-.Xr accept 2
-is called on behalf of the job, and the result is passed via the standard in/out/error descriptors.
-.El
-.It Sy LimitLoadToHosts <array of strings>
-This configuration file only applies to the hosts listed with this key. Note: One should set kern.hostname in
-.Xr sysctl.conf 5
-for this feature to work reliably.
-.It Sy LimitLoadFromHosts <array of strings>
-This configuration file only applies to hosts NOT listed with this key. Note: One should set kern.hostname in
-.Xr sysctl.conf 5
-for this feature to work reliably.
-.It Sy LimitLoadToSessionType <string>
-This configuration file only applies to sessions of the type specified. This key is used
-in concert with the -S flag to
-.Nm launchctl .
-.It Sy Program <string>
-This key maps to the first argument of
-.Xr execvp 3 .
-If this key is missing, then the first element of the array of strings provided to the ProgramArguments will be used instead.
-This key is required in the absence of the ProgramArguments key.
-.It Sy ProgramArguments <array of strings>
-This key maps to the second argument of
-.Xr execvp 3 .
-This key is required in the absence of the Program key. Please note: many people are confused by this key. Please read
-.Xr execvp 3
-very carefully!
-.It Sy EnableGlobbing <boolean>
-This flag causes
-.Nm launchd
-to use the
-.Xr glob 3
-mechanism to update the program arguments before invocation.
-.It Sy EnableTransactions <boolean>
-This flag instructs
-.Nm launchd
-that the job promises to use
-.Xr vproc_transaction_begin 3
-and
-.Xr vproc_transaction_end 3
-to track outstanding transactions that need to be reconciled before the process can safely terminate. If no outstanding transactions are in progress, then
-.Nm launchd
-is free to send the SIGKILL signal.
-.It Sy OnDemand <boolean>
-This key was used in Mac OS X 10.4 to control whether a job was kept alive or not. The default was true.
-This key has been deprecated and replaced in Mac OS X 10.5 and later with the more powerful KeepAlive option.
-.It Sy KeepAlive <boolean or dictionary of stuff>
-This optional key is used to control whether your job is to be kept
-continuously running or to let demand and conditions control the invocation. The
-default is false and therefore only demand will start the job. The value may be
-set to true to unconditionally keep the job alive. Alternatively, a dictionary
-of conditions may be specified to selectively control whether
-.Nm launchd
-keeps a job alive or not. If multiple keys are provided, launchd ORs them, thus
-providing maximum flexibility to the job to refine the logic and stall if necessary. If
-.Nm launchd
-finds no reason to restart the job, it falls back on demand based invocation.
-Jobs that exit quickly and frequently when configured to be kept alive will be
-throttled to converve system resources.
-.Bl -ohang -offset indent
-.It Sy SuccessfulExit <boolean>
-If true, the job will be restarted as long as the program exits and with an exit
-status of zero. If false, the job will be restarted in the inverse condition.
-This key implies that "RunAtLoad" is set to true, since the job needs to run at
-least once before we can get an exit status.
-.It Sy NetworkState <boolean>
-If true, the job will be kept alive as long as the network is up, where up is
-defined as at least one non-loopback interface being up and having IPv4 or IPv6
-addresses assigned to them.
-If false, the job will be kept alive in the inverse condition.
-.It Sy PathState <dictionary of booleans>
-Each key in this dictionary is a file-system path. If the value of the key is
-true, then the job will be kept alive as long as the path exists.
-If false, the job will be kept alive in the inverse condition. The intent of this
-feature is that two or more jobs may create semaphores in the file-system namespace.
-.It Sy OtherJobEnabled <dictionary of booleans>
-Each key in this dictionary is the label of another job. If the value of the key is
-true, then this job is kept alive as long as that other job is enabled. Otherwise,
-if the value is false, then this job is kept alive as long as the other job is disabled.
-This feature should not be considered a substitute for the use of IPC.
-.El
-.It Sy RunAtLoad <boolean>
-This optional key is used to control whether your job is launched once at the time the job is loaded. The default is false.
-.It Sy RootDirectory <string>
-This optional key is used to specify a directory to
-.Xr chroot 2
-to before running the job.
-.It Sy WorkingDirectory <string>
-This optional key is used to specify a directory to
-.Xr chdir 2
-to before running the job.
-.It Sy EnvironmentVariables <dictionary of strings>
-This optional key is used to specify additional environmental variables to be set before running the job.
-.It Sy Umask <integer>
-This optional key specifies what value should be passed to
-.Xr umask 2
-before running the job. Known bug: Property lists don't support octal, so please convert the value to decimal.
-.It Sy TimeOut <integer>
-The recommended idle time out (in seconds) to pass to the job. If no value is specified, a default time out will be supplied by
-.Nm launchd
-for use by the job at check in time.
-.It Sy ExitTimeOut <integer>
-The amount of time
-.Nm launchd
-waits before sending a SIGKILL signal. The default value is 20 seconds. The value zero is interpreted as infinity.
-.It Sy ThrottleInterval <integer>
-This key lets one override the default throttling policy imposed on jobs by
-.Nm launchd .
-The value is in seconds, and by default, jobs will not be spawned more than once every 10 seconds.
-The principle behind this is that jobs should linger around just in case they are needed again in the near future. This not only
-reduces the latency of responses, but it encourages developers to amortize the cost of program invocation.
-.It Sy InitGroups <boolean>
-This optional key specifies whether
-.Xr initgroups 3
-should be called before running the job.
-The default is true in 10.5 and false in 10.4. This key will be ignored if the UserName key is not set.
-.It Sy WatchPaths <array of strings>
-This optional key causes the job to be started if any one of the listed paths are modified.
-.It Sy QueueDirectories <array of strings>
-Much like the WatchPaths option, this key will watch the paths for modifications. The difference being that the job will only be started if the path is a directory and the directory is not empty.
-.It Sy StartOnMount <boolean>
-This optional key causes the job to be started every time a filesystem is mounted.
-.It Sy StartInterval <integer>
-This optional key causes the job to be started every N seconds.
-If the system is asleep, the job will be started the next time the computer
-wakes up. If multiple intervals transpire before the computer is woken, those
-events will be coalesced into one event upon wake from sleep.
-.It Sy StartCalendarInterval <dictionary of integers or array of dictionary of integers>
-This optional key causes the job to be started every calendar interval as specified. Missing arguments are considered to be wildcard. The semantics are much like
-.Xr crontab 5 .
-Unlike cron which skips job invocations when the computer is asleep, launchd
-will start the job the next time the computer wakes up. If multiple intervals
-transpire before the computer is woken, those events will be coalesced into one
-event upon wake from sleep.
-.Bl -ohang -offset indent
-.It Sy Minute <integer>
-The minute on which this job will be run.
-.It Sy Hour <integer>
-The hour on which this job will be run.
-.It Sy Day <integer>
-The day on which this job will be run.
-.It Sy Weekday <integer>
-The weekday on which this job will be run (0 and 7 are Sunday).
-.It Sy Month <integer>
-The month on which this job will be run.
-.El
-.It Sy StandardInPath <string>
-This optional key specifies what file should be used for data being supplied to stdin when using
-.Xr stdio 3 .
-.It Sy StandardOutPath <string>
-This optional key specifies what file should be used for data being sent to stdout when using
-.Xr stdio 3 .
-.It Sy StandardErrorPath <string>
-This optional key specifies what file should be used for data being sent to stderr when using
-.Xr stdio 3 .
-.It Sy Debug <boolean>
-This optional key specifies that
-.Nm launchd
-should adjust its log mask temporarily to LOG_DEBUG while dealing with this job.
-.It Sy WaitForDebugger <boolean>
-This optional key specifies that
-.Nm launchd
-should instruct the kernel to have the job wait for a debugger to attach before any code in the job is executed.
-.It Sy SoftResourceLimits <dictionary of integers>
-.It Sy HardResourceLimits <dictionary of integers>
-Resource limits to be imposed on the job. These adjust variables set with
-.Xr setrlimit 2 .
-The following keys apply:
-.Bl -ohang -offset indent
-.It Sy Core <integer>
-The largest size (in bytes) core file that may be created.
-.It Sy CPU <integer>
-The maximum amount of cpu time (in seconds) to be used by each process.
-.It Sy Data <integer>
-The maximum size (in bytes) of the data segment for a process; this defines how far a program may extend its break with the
-.Xr sbrk 2
-system call.
-.It Sy FileSize <integer>
-The largest size (in bytes) file that may be created.
-.It Sy MemoryLock <integer>
-The maximum size (in bytes) which a process may lock into memory using the
-.Xr mlock 2
-function.
-.It Sy NumberOfFiles <integer>
-The maximum number of open files for this process.
-Setting this value in a system wide daemon will set the
-.Xr sysctl 3
-kern.maxfiles (SoftResourceLimits) or kern.maxfilesperproc (HardResourceLimits) value in addition to the
-.Xr setrlimit 2
-values.
-.It Sy NumberOfProcesses <integer>
-The maximum number of simultaneous processes for this user id.
-Setting this value in a system wide daemon will set the
-.Xr sysctl 3
-kern.maxproc (SoftResourceLimits) or kern.maxprocperuid (HardResourceLimits)
-value in addition to the
-.Xr setrlimit 2
-values.
-.It Sy ResidentSetSize <integer>
-The maximum size (in bytes) to which a process's resident set size may grow.
-This imposes a limit on the amount of physical memory to be given to a process;
-if memory is tight, the system will prefer to take memory from processes that
-are exceeding their declared resident set size.
-.It Sy Stack <integer>
-The maximum size (in bytes) of the stack segment for a process; this defines
-how far a program's stack segment may be extended. Stack extension is
-performed automatically by the system.
-.El
-.It Sy Nice <integer>
-This optional key specifies what
-.Xr nice 3
-value should be applied to the daemon.
-.It Sy AbandonProcessGroup <boolean>
-When a job dies,
-.Nm launchd
-kills any remaining processes with the same process group ID as the job.
-Setting this key to true disables that behavior.
-.It Sy LowPriorityIO <boolean>
-This optional key specifies whether the kernel should consider this daemon to be low priority when doing file system I/O.
-.It Sy LaunchOnlyOnce <boolean>
-This optional key specifies whether the job can only be run once and only once.
-In other words, if the job cannot be safely respawned without a full machine
-reboot, then set this key to be true.
-.It Sy MachServices <dictionary of booleans or a dictionary of dictionaries>
-This optional key is used to specify Mach services to be registered with the
-Mach bootstrap sub-system. Each key in this dictionary should be the name of
-service to be advertised. The value of the key must be a boolean and set to true.
-Alternatively, a dictionary can be used instead of a simple true value.
-.Bl -ohang -offset indent
-.It Sy ResetAtClose <boolean>
-If this boolean is false, the port is recycled, thus leaving clients to remain oblivious
-to the demand nature of job. If the value is set to true, clients receive port
-death notifications when the job lets go of the receive right. The port will be
-recreated atomically with respect to bootstrap_look_up() calls, so that clients
-can trust that after receiving a port death notification, the new port will
-have already been recreated. Setting the value to true should be done with
-care. Not all clients may be able to handle this behavior. The default value is false.
-.It Sy HideUntilCheckIn <boolean>
-Reserve the name in the namespace, but cause bootstrap_look_up() to fail until the job has checked in with
-.Nm launchd .
-.El
-.Pp
-Finally, for the job itself, the values will be replaced with Mach ports at the time of check-in with
-.Nm launchd .
-.It Sy Sockets <dictionary of dictionaries... OR dictionary of array of dictionaries...>
-This optional key is used to specify launch on demand sockets that can be used to let
-.Nm launchd
-know when to run the job. The job must check-in to get a copy of the file descriptors using APIs outlined in
-.Xr launch 3 .
-The keys of the top level Sockets dictionary can be anything. They are meant for the application developer to use to
-differentiate which descriptors correspond to which application level protocols (e.g. http vs. ftp vs. DNS...).
-At check-in time, the value of each Sockets dictionary key will be an array of descriptors. Daemon/Agent writers should
-consider all descriptors of a given key to be to be effectively equivalent, even though each file descriptor likely represents
-a different networking protocol which conforms to the criteria specified in the job configuration file.
-.Pp
-The parameters below are used as inputs to call
-.Xr getaddrinfo 3 .
-.Bl -ohang -offset indent
-.It Sy SockType <string>
-This optional key tells
-.Nm launchctl
-what type of socket to create. The default is "stream" and other valid values for this key
-are "dgram" and "seqpacket" respectively.
-.It Sy SockPassive <boolean>
-This optional key specifies whether
-.Xr listen 2
-or
-.Xr connect 2
-should be called on the created file descriptor. The default is true ("to listen").
-.It Sy SockNodeName <string>
-This optional key specifies the node to
-.Xr connect 2
-or
-.Xr bind 2
-to.
-.It Sy SockServiceName <string>
-This optional key specifies the service on the node to
-.Xr connect 2
-or
-.Xr bind 2
-to.
-.It Sy SockFamily <string>
-This optional key can be used to specifically request that "IPv4" or "IPv6" socket(s) be created.
-.It Sy SockProtocol <string>
-This optional key specifies the protocol to be passed to
-.Xr socket 2 .
-The only value understood by this key at the moment is "TCP".
-.It Sy SockPathName <string>
-This optional key implies SockFamily is set to "Unix". It specifies the path to
-.Xr connect 2
-or
-.Xr bind 2
-to.
-.It Sy SecureSocketWithKey <string>
-This optional key is a variant of SockPathName. Instead of binding to a known
-path, a securely generated socket is created and the path is assigned to the
-environment variable that is inherited by all jobs spawned by launchd.
-.It Sy SockPathMode <integer>
-This optional key specifies the mode of the socket. Known bug: Property lists
-don't support octal, so please convert the value to decimal.
-.It Sy Bonjour <boolean or string or array of strings>
-This optional key can be used to request that the service be registered with the
-.Xr mDNSResponder 8 .
-If the value is boolean, the service name is inferred from the SockServiceName.
-.It Sy MulticastGroup <string>
-This optional key can be used to request that the datagram socket join a multicast group.
-If the value is a hostname, then
-.Xr getaddrinfo 3
-will be used to join the correct multicast address for a given socket family.
-If an explicit IPv4 or IPv6 address is given, it is required that the
-SockFamily family also be set, otherwise the results are undefined.
-.El
-.El
-.Pp
-.Sh DEPENDENCIES
-Unlike many bootstrapping daemons, launchd has no explicit dependency model.
-Interdependencies are expected to be solved through the use of IPC.
-It is therefore in the best interest of a job developer who expects dependents
-to define all of the sockets in the configuration file. This has the added
-benefit of making it possible to start the job based on demand instead of
-immediately.
-.Sh EXAMPLE XML PROPERTY LISTS
-.Pp
-The following XML Property List simply keeps "exampled" running continuously:
-.Pp
-.Dl <?xml version="1.0" encoding="UTF-8"?>
-.Dl <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-.Dl <plist version="1.0">
-.Dl <dict>
-.Dl <key>Label</key>
-.Dl <string>com.example.exampled</string>
-.Dl <key>ProgramArguments</key>
-.Dl <array>
-.Dl <string>exampled</string>
-.Dl </array>
-.Dl <key>KeepAlive</key>
-.Dl <true/>
-.Dl </dict>
-.Dl </plist>
-.Pp
-.Sh FILES
-.Bl -tag -width "/System/Library/LaunchDaemons" -compact
-.It Pa ~/Library/LaunchAgents
-Per-user agents provided by the user.
-.It Pa /Library/LaunchAgents
-Per-user agents provided by the administrator.
-.It Pa /Library/LaunchDaemons
-System-wide daemons provided by the administrator.
-.It Pa /System/Library/LaunchAgents
-Per-user agents provided by Mac OS X.
-.It Pa /System/Library/LaunchDaemons
-System-wide daemons provided by Mac OS X.
-.El
-.Sh SEE ALSO
-.Xr launchctl 1 ,
-.Xr sysctl 3 ,
-.Xr launchd 8 ,
-.Xr plist 5
+++ /dev/null
-/*
- * @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@
- */
-
-static const char *const __rcs_file_version__ = "$Revision: 26200 $";
-
-#include "config.h"
-#include "launchd_core_logic.h"
-#include "launch_internal.h"
-#include "launchd_helper.h"
-
-#include <TargetConditionals.h>
-#include <mach/mach.h>
-#include <mach/mach_error.h>
-#include <mach/boolean.h>
-#include <mach/message.h>
-#include <mach/notify.h>
-#include <mach/mig_errors.h>
-#include <mach/mach_traps.h>
-#include <mach/mach_interface.h>
-#include <mach/host_info.h>
-#include <mach/mach_host.h>
-#include <mach/exception.h>
-#include <mach/host_reboot.h>
-#include <sys/types.h>
-#include <sys/queue.h>
-#include <sys/event.h>
-#include <sys/stat.h>
-#include <sys/ucred.h>
-#include <sys/fcntl.h>
-#include <sys/un.h>
-#include <sys/reboot.h>
-#include <sys/wait.h>
-#include <sys/sysctl.h>
-#include <sys/sockio.h>
-#include <sys/time.h>
-#include <sys/resource.h>
-#include <sys/ioctl.h>
-#include <sys/mount.h>
-#include <sys/pipe.h>
-#include <sys/mman.h>
-#include <sys/socket.h>
-#include <sys/syscall.h>
-#include <net/if.h>
-#include <netinet/in.h>
-#include <netinet/in_var.h>
-#include <netinet6/nd6.h>
-#include <bsm/libbsm.h>
-#include <unistd.h>
-#include <signal.h>
-#include <errno.h>
-#include <libgen.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <paths.h>
-#include <pwd.h>
-#include <grp.h>
-#include <ttyent.h>
-#include <dlfcn.h>
-#include <dirent.h>
-#include <string.h>
-#include <ctype.h>
-#include <glob.h>
-#include <System/sys/spawn.h>
-#include <spawn.h>
-#include <time.h>
-#include <libinfo.h>
-
-#include <libproc.h>
-#include <malloc/malloc.h>
-#include <pthread.h>
-#include <libproc.h>
-#if HAVE_SANDBOX
-#define __APPLE_API_PRIVATE
-#include <sandbox.h>
-#endif
-#if HAVE_QUARANTINE
-#include <quarantine.h>
-#endif
-#if TARGET_OS_EMBEDDED
-#include <sys/kern_memorystatus.h>
-#else
-extern int gL1CacheEnabled;
-/* To make my life easier. */
-typedef struct jetsam_priority_entry {
- pid_t pid;
- uint32_t priority;
- uint32_t flags;
- int32_t hiwat_pages;
- int32_t hiwat_reserved1;
- int32_t hiwat_reserved2;
- int32_t hiwat_reserved3;
-} jetsam_priority_entry_t;
-
-enum {
- kJetsamFlagsFrontmost = (1 << 0),
- kJetsamFlagsKilled = (1 << 1)
-};
-#endif
-
-#include "launch.h"
-#include "launch_priv.h"
-#include "launch_internal.h"
-#include "bootstrap.h"
-#include "bootstrap_priv.h"
-#include "vproc.h"
-#include "vproc_internal.h"
-
-#include "reboot2.h"
-
-#include "launchd.h"
-#include "launchd_runtime.h"
-#include "launchd_unix_ipc.h"
-#include "protocol_vproc.h"
-#include "protocol_vprocServer.h"
-#include "protocol_job_reply.h"
-#include "protocol_job_forward.h"
-#include "mach_excServer.h"
-#if !TARGET_OS_EMBEDDED
-#include "domainServer.h"
-#include "init.h"
-#endif /* !TARGET_OS_EMBEDDED */
-#include "eventsServer.h"
-
-#ifndef POSIX_SPAWN_OSX_TALAPP_START
-#define POSIX_SPAWN_OSX_TALAPP_START 0x0400
-#endif
-
-#ifndef POSIX_SPAWN_OSX_WIDGET_START
-#define POSIX_SPAWN_OSX_WIDGET_START 0x0800
-#endif
-
-#ifndef POSIX_SPAWN_IOS_APP_START
-#define POSIX_SPAWN_IOS_APP_START 0x1000
-#endif
-
-/* LAUNCHD_DEFAULT_EXIT_TIMEOUT
- * If the job hasn't exited in the given number of seconds after sending
- * it a SIGTERM, SIGKILL it. Can be overriden in the job plist.
- */
-#define LAUNCHD_MIN_JOB_RUN_TIME 10
-#define LAUNCHD_DEFAULT_EXIT_TIMEOUT 20
-#define LAUNCHD_SIGKILL_TIMER 2
-#define LAUNCHD_LOG_FAILED_EXEC_FREQ 10
-
-#define SHUTDOWN_LOG_DIR "/var/log/shutdown"
-
-#define TAKE_SUBSET_NAME "TakeSubsetName"
-#define TAKE_SUBSET_PID "TakeSubsetPID"
-#define TAKE_SUBSET_PERPID "TakeSubsetPerPID"
-
-#define IS_POWER_OF_TWO(v) (!(v & (v - 1)) && v)
-
-extern char **environ;
-
-struct waiting_for_removal {
- SLIST_ENTRY(waiting_for_removal) sle;
- mach_port_t reply_port;
-};
-
-static bool waiting4removal_new(job_t j, mach_port_t rp);
-static void waiting4removal_delete(job_t j, struct waiting_for_removal *w4r);
-
-struct machservice {
- SLIST_ENTRY(machservice) sle;
- SLIST_ENTRY(machservice) special_port_sle;
- LIST_ENTRY(machservice) name_hash_sle;
- LIST_ENTRY(machservice) port_hash_sle;
- struct machservice *alias;
- job_t job;
- unsigned int gen_num;
- mach_port_name_t port;
- unsigned int
- isActive :1,
- reset :1,
- recv :1,
- hide :1,
- kUNCServer :1,
- per_user_hack :1,
- debug_on_close :1,
- per_pid :1,
- delete_on_destruction :1,
- drain_one_on_crash :1,
- drain_all_on_crash :1,
- event_update_port :1, /* The job which owns this port is the event monitor. */
- upfront :1, /* This service was declared in the plist. */
- event_channel :1, /* The job is to receive events on this channel. */
- /* Don't let the size of this field to get too small. It has to be large enough
- * to represent the reasonable range of special port numbers.
- */
- special_port_num :18;
- const char name[0];
-};
-
-static SLIST_HEAD(, machservice) special_ports; /* hack, this should be per jobmgr_t */
-
-#define PORT_HASH_SIZE 32
-#define HASH_PORT(x) (IS_POWER_OF_TWO(PORT_HASH_SIZE) ? (MACH_PORT_INDEX(x) & (PORT_HASH_SIZE - 1)) : (MACH_PORT_INDEX(x) % PORT_HASH_SIZE))
-
-static LIST_HEAD(, machservice) port_hash[PORT_HASH_SIZE];
-
-static void machservice_setup(launch_data_t obj, const char *key, void *context);
-static void machservice_setup_options(launch_data_t obj, const char *key, void *context);
-static void machservice_resetport(job_t j, struct machservice *ms);
-static struct machservice *machservice_new(job_t j, const char *name, mach_port_t *serviceport, bool pid_local);
-#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__
-static struct machservice *machservice_new_alias(job_t aj, struct machservice *orig);
-#endif /* __LAUNCH_DISABLE_XPC_SUPPORT__ */
-static void machservice_ignore(job_t j, struct machservice *ms);
-static void machservice_watch(job_t j, struct machservice *ms);
-static void machservice_delete(job_t j, struct machservice *, bool port_died);
-static void machservice_request_notifications(struct machservice *);
-static mach_port_t machservice_port(struct machservice *);
-static job_t machservice_job(struct machservice *);
-static bool machservice_hidden(struct machservice *);
-static bool machservice_active(struct machservice *);
-static const char *machservice_name(struct machservice *);
-static bootstrap_status_t machservice_status(struct machservice *);
-void machservice_drain_port(struct machservice *);
-static struct machservice *xpc_events_find_channel(job_t j, event_name_t stream, mach_port_t *p);
-
-struct socketgroup {
- SLIST_ENTRY(socketgroup) sle;
- int *fds;
- unsigned int junkfds:1, fd_cnt:31;
- union {
- const char name[0];
- char name_init[0];
- };
-};
-
-static bool socketgroup_new(job_t j, const char *name, int *fds, size_t fd_cnt, bool junkfds);
-static void socketgroup_delete(job_t j, struct socketgroup *sg);
-static void socketgroup_watch(job_t j, struct socketgroup *sg);
-static void socketgroup_ignore(job_t j, struct socketgroup *sg);
-static void socketgroup_callback(job_t j);
-static void socketgroup_setup(launch_data_t obj, const char *key, void *context);
-static void socketgroup_kevent_mod(job_t j, struct socketgroup *sg, bool do_add);
-
-struct calendarinterval {
- LIST_ENTRY(calendarinterval) global_sle;
- SLIST_ENTRY(calendarinterval) sle;
- job_t job;
- struct tm when;
- time_t when_next;
-};
-
-static LIST_HEAD(, calendarinterval) sorted_calendar_events;
-
-static bool calendarinterval_new(job_t j, struct tm *w);
-static bool calendarinterval_new_from_obj(job_t j, launch_data_t obj);
-static void calendarinterval_new_from_obj_dict_walk(launch_data_t obj, const char *key, void *context);
-static void calendarinterval_delete(job_t j, struct calendarinterval *ci);
-static void calendarinterval_setalarm(job_t j, struct calendarinterval *ci);
-static void calendarinterval_callback(void);
-static void calendarinterval_sanity_check(void);
-
-struct envitem {
- SLIST_ENTRY(envitem) sle;
- bool one_shot;
- char *value;
- union {
- const char key[0];
- char key_init[0];
- };
-};
-
-static bool envitem_new(job_t j, const char *k, const char *v, bool global, bool one_shot);
-static void envitem_delete(job_t j, struct envitem *ei, bool global);
-static void envitem_setup(launch_data_t obj, const char *key, void *context);
-static void envitem_setup_one_shot(launch_data_t obj, const char *key, void *context);
-
-struct limititem {
- SLIST_ENTRY(limititem) sle;
- struct rlimit lim;
- unsigned int setsoft:1, sethard:1, which:30;
-};
-
-static bool limititem_update(job_t j, int w, rlim_t r);
-static void limititem_delete(job_t j, struct limititem *li);
-static void limititem_setup(launch_data_t obj, const char *key, void *context);
-#if HAVE_SANDBOX
-static void seatbelt_setup_flags(launch_data_t obj, const char *key, void *context);
-#endif
-
-static void jetsam_property_setup(launch_data_t obj, const char *key, job_t j);
-
-typedef enum {
- NETWORK_UP = 1,
- NETWORK_DOWN,
- SUCCESSFUL_EXIT,
- FAILED_EXIT,
- CRASHED,
- DID_NOT_CRASH,
- PATH_EXISTS,
- PATH_MISSING,
- OTHER_JOB_ENABLED,
- OTHER_JOB_DISABLED,
- OTHER_JOB_ACTIVE,
- OTHER_JOB_INACTIVE,
- PATH_CHANGES,
- DIR_NOT_EMPTY,
- // FILESYSTEMTYPE_IS_MOUNTED, /* for nfsiod, but maybe others */
-} semaphore_reason_t;
-
-struct semaphoreitem {
- SLIST_ENTRY(semaphoreitem) sle;
- semaphore_reason_t why;
- bool watching_parent;
- int fd;
-
- union {
- const char what[0];
- char what_init[0];
- };
-};
-
-struct semaphoreitem_dict_iter_context {
- job_t j;
- semaphore_reason_t why_true;
- semaphore_reason_t why_false;
-};
-
-static bool semaphoreitem_new(job_t j, semaphore_reason_t why, const char *what);
-static void semaphoreitem_delete(job_t j, struct semaphoreitem *si);
-static void semaphoreitem_setup(launch_data_t obj, const char *key, void *context);
-static void semaphoreitem_setup_dict_iter(launch_data_t obj, const char *key, void *context);
-static void semaphoreitem_callback(job_t j, struct kevent *kev);
-static void semaphoreitem_watch(job_t j, struct semaphoreitem *si);
-static void semaphoreitem_ignore(job_t j, struct semaphoreitem *si);
-static void semaphoreitem_runtime_mod_ref(struct semaphoreitem *si, bool add);
-
-struct externalevent {
- LIST_ENTRY(externalevent) sys_le;
- LIST_ENTRY(externalevent) job_le;
- struct eventsystem *sys;
-
- uint64_t id;
- job_t job;
- bool state;
- bool wanted_state;
- launch_data_t event;
-
- char name[0];
-};
-
-struct externalevent_iter_ctx {
- job_t j;
- struct eventsystem *sys;
-};
-
-static bool externalevent_new(job_t j, struct eventsystem *sys, char *evname, launch_data_t event);
-static void externalevent_delete(struct externalevent *ee);
-static void externalevent_setup(launch_data_t obj, const char *key, void *context);
-static struct externalevent *externalevent_find(const char *sysname, uint64_t id);
-
-struct eventsystem {
- LIST_ENTRY(eventsystem) global_le;
- LIST_HEAD(, externalevent) events;
- uint64_t curid;
- bool has_updates;
- char name[0];
-};
-
-static struct eventsystem *eventsystem_new(const char *name);
-static void eventsystem_delete(struct eventsystem *sys);
-static void eventsystem_setup(launch_data_t obj, const char *key, void *context);
-static struct eventsystem *eventsystem_find(const char *name);
-static void eventsystem_ping(void);
-
-#define ACTIVE_JOB_HASH_SIZE 32
-#define ACTIVE_JOB_HASH(x) (IS_POWER_OF_TWO(ACTIVE_JOB_HASH_SIZE) ? (x & (ACTIVE_JOB_HASH_SIZE - 1)) : (x % ACTIVE_JOB_HASH_SIZE))
-
-#define MACHSERVICE_HASH_SIZE 37
-
-#define LABEL_HASH_SIZE 53
-struct jobmgr_s {
- kq_callback kqjobmgr_callback;
- LIST_ENTRY(jobmgr_s) xpc_le;
- SLIST_ENTRY(jobmgr_s) sle;
- SLIST_HEAD(, jobmgr_s) submgrs;
- LIST_HEAD(, job_s) jobs;
- LIST_HEAD(, job_s) jetsam_jobs;
-
- /* For legacy reasons, we keep all job labels that are imported in the
- * root job manager's label hash. If a job manager is an XPC domain, then
- * it gets its own label hash that is separate from the "global" one
- * stored in the root job manager.
- */
- LIST_HEAD(, job_s) label_hash[LABEL_HASH_SIZE];
- LIST_HEAD(, job_s) active_jobs[ACTIVE_JOB_HASH_SIZE];
- LIST_HEAD(, machservice) ms_hash[MACHSERVICE_HASH_SIZE];
- LIST_HEAD(, job_s) global_env_jobs;
- mach_port_t jm_port;
- mach_port_t req_port;
- jobmgr_t parentmgr;
- int reboot_flags;
- time_t shutdown_time;
- unsigned int global_on_demand_cnt;
- unsigned int normal_active_cnt;
- unsigned int jetsam_jobs_cnt;
- unsigned int
- shutting_down :1,
- session_initialized :1,
- killed_stray_jobs :1,
- monitor_shutdown :1,
- shutdown_jobs_dirtied :1,
- shutdown_jobs_cleaned :1,
- xpc_singleton :1;
- uint32_t properties;
- /* XPC-specific properties. */
- char owner[MAXCOMLEN];
- char *shortdesc;
- mach_port_t req_bsport;
- mach_port_t req_excport;
- mach_port_t req_asport;
- pid_t req_pid;
- uid_t req_euid;
- gid_t req_egid;
- au_asid_t req_asid;
- vm_offset_t req_ctx;
- mach_msg_type_number_t req_ctx_sz;
- mach_port_t req_rport;
- kern_return_t error;
- union {
- const char name[0];
- char name_init[0];
- };
-};
-
-/* Global XPC domains. */
-#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__
-static jobmgr_t _s_xpc_system_domain;
-static LIST_HEAD(, jobmgr_s) _s_xpc_user_domains;
-static LIST_HEAD(, jobmgr_s) _s_xpc_session_domains;
-#endif /* __LAUNCH_DISABLE_XPC_SUPPORT__ */
-
-#define jobmgr_assumes(jm, e) \
- (unlikely(!(e)) ? jobmgr_log_bug(jm, __LINE__), false : true)
-
-static jobmgr_t jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bool sflag, const char *name, bool no_init, mach_port_t asport);
-#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__
-static jobmgr_t jobmgr_new_xpc_singleton_domain(jobmgr_t jm, name_t name);
-static jobmgr_t jobmgr_find_xpc_per_user_domain(jobmgr_t jm, uid_t uid);
-static jobmgr_t jobmgr_find_xpc_per_session_domain(jobmgr_t jm, au_asid_t asid);
-static job_t xpc_domain_import_service(jobmgr_t jm, launch_data_t pload);
-#endif /* __LAUNCH_DISABLE_XPC_SUPPORT__ */
-static job_t jobmgr_import2(jobmgr_t jm, launch_data_t pload);
-static jobmgr_t jobmgr_parent(jobmgr_t jm);
-static jobmgr_t jobmgr_do_garbage_collection(jobmgr_t jm);
-static bool jobmgr_label_test(jobmgr_t jm, const char *str);
-static void jobmgr_reap_bulk(jobmgr_t jm, struct kevent *kev);
-static void jobmgr_log_stray_children(jobmgr_t jm, bool kill_strays);
-static void jobmgr_kill_stray_children(jobmgr_t jm, pid_t *p, size_t np);
-static void jobmgr_remove(jobmgr_t jm);
-static void jobmgr_dispatch_all(jobmgr_t jm, bool newmounthack);
-static job_t jobmgr_init_session(jobmgr_t jm, const char *session_type, bool sflag);
-static job_t jobmgr_find_by_pid_deep(jobmgr_t jm, pid_t p, bool anon_okay);
-static job_t jobmgr_find_by_pid(jobmgr_t jm, pid_t p, bool create_anon);
-static jobmgr_t jobmgr_find_by_name(jobmgr_t jm, const char *where);
-static job_t job_mig_intran2(jobmgr_t jm, mach_port_t mport, pid_t upid);
-static job_t jobmgr_lookup_per_user_context_internal(job_t j, uid_t which_user, mach_port_t *mp);
-static void job_export_all2(jobmgr_t jm, launch_data_t where);
-static void jobmgr_callback(void *obj, struct kevent *kev);
-static void jobmgr_setup_env_from_other_jobs(jobmgr_t jm);
-static void jobmgr_export_env_from_other_jobs(jobmgr_t jm, launch_data_t dict);
-static struct machservice *jobmgr_lookup_service(jobmgr_t jm, const char *name, bool check_parent, pid_t target_pid);
-static void jobmgr_logv(jobmgr_t jm, int pri, int err, const char *msg, va_list ap) __attribute__((format(printf, 4, 0)));
-static void jobmgr_log(jobmgr_t jm, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4)));
-/* static void jobmgr_log_error(jobmgr_t jm, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4))); */
-static void jobmgr_log_bug(jobmgr_t jm, unsigned int line);
-
-#define AUTO_PICK_LEGACY_LABEL (const char *)(~0)
-#define AUTO_PICK_ANONYMOUS_LABEL (const char *)(~1)
-#define AUTO_PICK_XPC_LABEL (const char *)(~2)
-
-struct suspended_peruser {
- LIST_ENTRY(suspended_peruser) sle;
- job_t j;
-};
-
-struct job_s {
- kq_callback kqjob_callback; /* MUST be first element of this structure for benefit of launchd's run loop. */
- LIST_ENTRY(job_s) sle;
- LIST_ENTRY(job_s) subjob_sle;
- LIST_ENTRY(job_s) needing_session_sle;
- LIST_ENTRY(job_s) jetsam_sle;
- LIST_ENTRY(job_s) pid_hash_sle;
- LIST_ENTRY(job_s) label_hash_sle;
- LIST_ENTRY(job_s) global_env_sle;
- SLIST_ENTRY(job_s) curious_jobs_sle;
- LIST_HEAD(, suspended_peruser) suspended_perusers;
- LIST_HEAD(, waiting_for_exit) exit_watchers;
- LIST_HEAD(, job_s) subjobs;
- LIST_HEAD(, externalevent) events;
- SLIST_HEAD(, socketgroup) sockets;
- SLIST_HEAD(, calendarinterval) cal_intervals;
- SLIST_HEAD(, envitem) global_env;
- SLIST_HEAD(, envitem) env;
- SLIST_HEAD(, limititem) limits;
- SLIST_HEAD(, machservice) machservices;
- SLIST_HEAD(, semaphoreitem) semaphores;
- SLIST_HEAD(, waiting_for_removal) removal_watchers;
- job_t alias;
- struct rusage ru;
- cpu_type_t *j_binpref;
- size_t j_binpref_cnt;
- mach_port_t j_port;
- mach_port_t exit_status_dest;
- mach_port_t exit_status_port;
- mach_port_t spawn_reply_port;
- uid_t mach_uid;
- jobmgr_t mgr;
- size_t argc;
- char **argv;
- char *prog;
- char *rootdir;
- char *workingdir;
- char *username;
- char *groupname;
- char *stdinpath;
- char *stdoutpath;
- char *stderrpath;
- char *alt_exc_handler;
- struct vproc_shmem_s *shmem;
- struct machservice *lastlookup;
- unsigned int lastlookup_gennum;
-#if HAVE_SANDBOX
- char *seatbelt_profile;
- uint64_t seatbelt_flags;
-#endif
-#if HAVE_QUARANTINE
- void *quarantine_data;
- size_t quarantine_data_sz;
-#endif
- pid_t p;
- int last_exit_status;
- int stdin_fd;
- int fork_fd;
- int log_redirect_fd;
- int nice;
- int stdout_err_fd;
- uint32_t pstype;
- int32_t jetsam_priority;
- int32_t jetsam_memlimit;
- int32_t jetsam_seq;
- int32_t main_thread_priority;
- uint32_t timeout;
- uint32_t exit_timeout;
- uint64_t sent_signal_time;
- uint64_t start_time;
- uint32_t min_run_time;
- uint32_t start_interval;
- uint32_t peruser_suspend_count; /* The number of jobs that have disabled this per-user launchd. */
- uuid_t instance_id;
- uint32_t fail_cnt;
-#if 0
- /* someday ... */
- enum {
- J_TYPE_ANONYMOUS = 1,
- J_TYPE_LANCHSERVICES,
- J_TYPE_MACHINIT,
- J_TYPE_INETD,
- } j_type;
-#endif
- bool
- debug :1, /* man launchd.plist --> Debug */
- ondemand :1, /* man launchd.plist --> KeepAlive == false */
- session_create :1, /* man launchd.plist --> SessionCreate */
- low_pri_io :1, /* man launchd.plist --> LowPriorityIO */
- no_init_groups :1, /* man launchd.plist --> InitGroups */
- priv_port_has_senders :1, /* a legacy mach_init concept to make bootstrap_create_server/service() work */
- importing_global_env :1, /* a hack during job importing */
- importing_hard_limits :1, /* a hack during job importing */
- setmask :1, /* man launchd.plist --> Umask */
- anonymous :1, /* a process that launchd knows about, but isn't managed by launchd */
- checkedin :1, /* a legacy mach_init concept to detect sick jobs */
- legacy_mach_job :1, /* a job created via bootstrap_create_server() */
- legacy_LS_job :1, /* a job created via spawn_via_launchd() */
- inetcompat :1, /* a legacy job that wants inetd compatible semantics */
- inetcompat_wait :1, /* a twist on inetd compatibility */
- start_pending :1, /* an event fired and the job should start, but not necessarily right away */
- globargv :1, /* man launchd.plist --> EnableGlobbing */
- wait4debugger :1, /* man launchd.plist --> WaitForDebugger */
- wait4debugger_oneshot :1, /* One-shot WaitForDebugger. */
- internal_exc_handler :1, /* MachExceptionHandler == true */
- stall_before_exec :1, /* a hack to support an option of spawn_via_launchd() */
- only_once :1, /* man launchd.plist --> LaunchOnlyOnce. Note: 5465184 Rename this to "HopefullyNeverExits" */
- currently_ignored :1, /* Make job_ignore() / job_watch() work. If these calls were balanced, then this wouldn't be necessarily. */
- forced_peers_to_demand_mode :1, /* A job that forced all other jobs to be temporarily launch-on-demand */
- setnice :1, /* man launchd.plist --> Nice */
- removal_pending :1, /* a job was asked to be unloaded/removed while running, we'll remove it after it exits */
- sent_sigkill :1, /* job_kill() was called */
- debug_before_kill :1, /* enter the kernel debugger before killing a job */
- weird_bootstrap :1, /* a hack that launchd+launchctl use during jobmgr_t creation */
- start_on_mount :1, /* man launchd.plist --> StartOnMount */
- per_user :1, /* This job is a per-user launchd managed by the PID 1 launchd */
- unload_at_mig_return :1, /* A job thoroughly confused launchd. We need to unload it ASAP */
- abandon_pg :1, /* man launchd.plist --> AbandonProcessGroup */
- ignore_pg_at_shutdown :1, /* During shutdown, do not send SIGTERM to stray processes in the process group of this job. */
- poll_for_vfs_changes :1, /* a hack to work around the fact that kqueues don't work on all filesystems */
- deny_job_creation :1, /* Don't let this job create new 'job_t' objects in launchd */
- kill_via_shmem :1, /* man launchd.plist --> EnableTransactions */
- sent_kill_via_shmem :1, /* We need to 'kill_via_shmem' once-and-only-once */
- clean_kill :1, /* The job was sent SIGKILL because it was clean. */
- kill_after_sample :1, /* The job is to be killed after sampling. */
- reap_after_trace :1, /* The job exited before sample did, so we should reap it after sample is done. */
- nosy :1, /* The job has an OtherJobEnabled KeepAlive criterion. */
- crashed :1, /* The job is the default Mach exception handler, and it crashed. */
- reaped :1, /* We've received NOTE_EXIT for the job. */
- stopped :1, /* job_stop() was called. */
- jetsam_frontmost :1, /* The job is considered "frontmost" by Jetsam. */
- needs_kickoff :1, /* The job is to be kept alive continuously, but it must be initially kicked off. */
- is_bootstrapper :1, /* The job is a bootstrapper. */
- has_console :1, /* The job owns the console. */
- embedded_special_privileges :1, /* The job runs as a non-root user on embedded but has select privileges of the root user. */
- did_exec :1, /* The job exec(2)ed successfully. */
- xpcproxy_did_exec :1, /* The job is an XPC service, and XPC proxy successfully exec(3)ed. */
- holds_ref :1, /* The (anonymous) job called vprocmgr_switch_to_session(). */
- jetsam_properties :1, /* The job has Jetsam limits in place. */
- dedicated_instance :1, /* This job was created as the result of a look up of a service provided by a per-lookup job. */
- multiple_instances :1, /* The job supports creating additional instances of itself. */
- former_subjob :1, /* The sub-job was already removed from the parent's list of sub-jobs. */
- event_monitor :1, /* The job is responsible for monitoring external events for this launchd. */
- removing :1, /* A lame hack. */
- disable_aslr :1, /* Disable ASLR when launching this job. */
- xpc_service :1, /* The job is an XPC Service. */
- shutdown_monitor :1, /* The job is the Performance team's shutdown monitor. */
- dirty_at_shutdown :1, /* We should open a transaction for the job when shutdown begins. */
- workaround9359725 :1, /* The job was sent SIGKILL but did not exit in a timely fashion, indicating a kernel bug. */
- xpc_bootstrapper :1;
-
- mode_t mask;
- pid_t tracing_pid;
- mach_port_t asport;
- /* Only set for per-user launchd's. */
- au_asid_t asid;
- uuid_t expected_audit_uuid;
- const char label[0];
-};
-
-static size_t hash_label(const char *label) __attribute__((pure));
-static size_t hash_ms(const char *msstr) __attribute__((pure));
-static SLIST_HEAD(, job_s) s_curious_jobs;
-
-#define job_assumes(j, e) \
- (unlikely(!(e)) ? job_log_bug(j, __LINE__), false : true)
-
-static void job_import_keys(launch_data_t obj, const char *key, void *context);
-static void job_import_bool(job_t j, const char *key, bool value);
-static void job_import_string(job_t j, const char *key, const char *value);
-static void job_import_integer(job_t j, const char *key, long long value);
-static void job_import_dictionary(job_t j, const char *key, launch_data_t value);
-static void job_import_array(job_t j, const char *key, launch_data_t value);
-static void job_import_opaque(job_t j, const char *key, launch_data_t value);
-static bool job_set_global_on_demand(job_t j, bool val);
-static const char *job_active(job_t j);
-static void job_watch(job_t j);
-static void job_ignore(job_t j);
-static void job_cleanup_after_tracer(job_t j);
-static void job_reap(job_t j);
-static bool job_useless(job_t j);
-static bool job_keepalive(job_t j);
-static void job_dispatch_curious_jobs(job_t j);
-static void job_start(job_t j);
-static void job_start_child(job_t j) __attribute__((noreturn));
-static void job_setup_attributes(job_t j);
-static bool job_setup_machport(job_t j);
-static kern_return_t job_setup_exit_port(job_t j);
-static void job_setup_fd(job_t j, int target_fd, const char *path, int flags);
-static void job_postfork_become_user(job_t j);
-static void job_postfork_test_user(job_t j);
-static void job_log_pids_with_weird_uids(job_t j);
-static void job_setup_exception_port(job_t j, task_t target_task);
-static void job_callback(void *obj, struct kevent *kev);
-static void job_callback_proc(job_t j, struct kevent *kev);
-static void job_callback_timer(job_t j, void *ident);
-static void job_callback_read(job_t j, int ident);
-static void job_log_stray_pg(job_t j);
-static void job_log_children_without_exec(job_t j);
-static job_t job_new_anonymous(jobmgr_t jm, pid_t anonpid) __attribute__((malloc, nonnull, warn_unused_result));
-static job_t job_new(jobmgr_t jm, const char *label, const char *prog, const char *const *argv) __attribute__((malloc, nonnull(1,2), warn_unused_result));
-#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__
-static job_t job_new_alias(jobmgr_t jm, job_t src);
-#endif /* __LAUNCH_DISABLE_XPC_SUPPORT__ */
-static job_t job_new_via_mach_init(job_t j, const char *cmd, uid_t uid, bool ond) __attribute__((malloc, nonnull, warn_unused_result));
-static job_t job_new_subjob(job_t j, uuid_t identifier);
-static void job_kill(job_t j);
-static void job_uncork_fork(job_t j);
-static void job_log_stdouterr(job_t j);
-static void job_logv(job_t j, int pri, int err, const char *msg, va_list ap) __attribute__((format(printf, 4, 0)));
-static void job_log_error(job_t j, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4)));
-static void job_log_bug(job_t j, unsigned int line);
-static void job_log_stdouterr2(job_t j, const char *msg, ...);
-static void job_set_exception_port(job_t j, mach_port_t port);
-static kern_return_t job_mig_spawn_internal(job_t j, vm_offset_t indata, mach_msg_type_number_t indataCnt, mach_port_t asport, job_t *outj);
-static void job_open_shutdown_transaction(job_t ji);
-static void job_close_shutdown_transaction(job_t ji);
-
-static const struct {
- const char *key;
- int val;
-} launchd_keys2limits[] = {
- { LAUNCH_JOBKEY_RESOURCELIMIT_CORE, RLIMIT_CORE },
- { LAUNCH_JOBKEY_RESOURCELIMIT_CPU, RLIMIT_CPU },
- { LAUNCH_JOBKEY_RESOURCELIMIT_DATA, RLIMIT_DATA },
- { LAUNCH_JOBKEY_RESOURCELIMIT_FSIZE, RLIMIT_FSIZE },
- { LAUNCH_JOBKEY_RESOURCELIMIT_MEMLOCK, RLIMIT_MEMLOCK },
- { LAUNCH_JOBKEY_RESOURCELIMIT_NOFILE, RLIMIT_NOFILE },
- { LAUNCH_JOBKEY_RESOURCELIMIT_NPROC, RLIMIT_NPROC },
- { LAUNCH_JOBKEY_RESOURCELIMIT_RSS, RLIMIT_RSS },
- { LAUNCH_JOBKEY_RESOURCELIMIT_STACK, RLIMIT_STACK },
-};
-
-static time_t cronemu(int mon, int mday, int hour, int min);
-static time_t cronemu_wday(int wday, int hour, int min);
-static bool cronemu_mon(struct tm *wtm, int mon, int mday, int hour, int min);
-static bool cronemu_mday(struct tm *wtm, int mday, int hour, int min);
-static bool cronemu_hour(struct tm *wtm, int hour, int min);
-static bool cronemu_min(struct tm *wtm, int min);
-
-/* These functions are a total nightmare to get to through headers.
- * See rdar://problem/8223092.
- */
-typedef __darwin_mach_port_t fileport_t;
-#define FILEPORT_NULL ((fileport_t)0)
-extern int fileport_makeport(int, fileport_t *);
-extern int fileport_makefd(fileport_t);
-
-/* miscellaneous file local functions */
-static size_t get_kern_max_proc(void);
-static int dir_has_files(job_t j, const char *path);
-static char **mach_cmd2argv(const char *string);
-static size_t our_strhash(const char *s) __attribute__((pure));
-static void extract_rcsid_substr(const char *i, char *o, size_t osz);
-
-void eliminate_double_reboot(void);
-
-/* file local globals */
-static size_t total_children;
-static size_t total_anon_children;
-static mach_port_t the_exception_server;
-static job_t workaround_5477111;
-static LIST_HEAD(, job_s) s_needing_sessions;
-static LIST_HEAD(, eventsystem) _s_event_systems;
-static job_t _s_event_monitor;
-static job_t _s_xpc_bootstrapper;
-static job_t _s_shutdown_monitor;
-static mach_port_t _s_event_update_port;
-mach_port_t g_audit_session_port = MACH_PORT_NULL;
-static uint32_t s_jetsam_sequence_id;
-
-#if !TARGET_OS_EMBEDDED
-static job_t s_embedded_privileged_job = (job_t)&root_jobmgr;
-au_asid_t g_audit_session = AU_DEFAUDITSID;
-#else
-static job_t s_embedded_privileged_job = NULL;
-pid_t g_audit_session = 0;
-#endif
-
-static int s_no_hang_fd = -1;
-
-/* process wide globals */
-mach_port_t inherited_bootstrap_port;
-jobmgr_t root_jobmgr;
-bool g_shutdown_debugging = false;
-bool g_verbose_boot = false;
-bool g_embedded_privileged_action = false;
-bool g_runtime_busy_time = false;
-
-void
-job_ignore(job_t j)
-{
- struct semaphoreitem *si;
- struct socketgroup *sg;
- struct machservice *ms;
-
- if (j->currently_ignored) {
- return;
- }
-
- job_log(j, LOG_DEBUG, "Ignoring...");
-
- j->currently_ignored = true;
-
- if (j->poll_for_vfs_changes) {
- j->poll_for_vfs_changes = false;
- (void)job_assumes(j, kevent_mod((uintptr_t)&j->semaphores, EVFILT_TIMER, EV_DELETE, 0, 0, j) != -1);
- }
-
- SLIST_FOREACH(sg, &j->sockets, sle) {
- socketgroup_ignore(j, sg);
- }
-
- SLIST_FOREACH(ms, &j->machservices, sle) {
- machservice_ignore(j, ms);
- }
-
- SLIST_FOREACH(si, &j->semaphores, sle) {
- semaphoreitem_ignore(j, si);
- }
-}
-
-void
-job_watch(job_t j)
-{
- struct semaphoreitem *si;
- struct socketgroup *sg;
- struct machservice *ms;
-
- if (!j->currently_ignored) {
- return;
- }
-
- job_log(j, LOG_DEBUG, "Watching...");
-
- j->currently_ignored = false;
-
- SLIST_FOREACH(sg, &j->sockets, sle) {
- socketgroup_watch(j, sg);
- }
-
- SLIST_FOREACH(ms, &j->machservices, sle) {
- machservice_watch(j, ms);
- }
-
- SLIST_FOREACH(si, &j->semaphores, sle) {
- semaphoreitem_watch(j, si);
- }
-}
-
-void
-job_stop(job_t j)
-{
- char extralog[100];
- int32_t newval = 1;
-
- if (unlikely(!j->p || j->stopped || j->anonymous)) {
- return;
- }
-
-#if TARGET_OS_EMBEDDED
- if (g_embedded_privileged_action && s_embedded_privileged_job) {
- if (!job_assumes(j, s_embedded_privileged_job->username != NULL && j->username != NULL)) {
- errno = EPERM;
- return;
- }
-
- if (strcmp(j->username, s_embedded_privileged_job->username) != 0) {
- errno = EPERM;
- return;
- }
- } else if (g_embedded_privileged_action) {
- errno = EINVAL;
- return;
- }
-#endif
-
- if (j->kill_via_shmem) {
- if (j->shmem) {
- if (!j->sent_kill_via_shmem) {
- j->shmem->vp_shmem_flags |= VPROC_SHMEM_EXITING;
- newval = __sync_sub_and_fetch(&j->shmem->vp_shmem_transaction_cnt, 1);
- j->sent_kill_via_shmem = true;
- } else {
- newval = j->shmem->vp_shmem_transaction_cnt;
- }
- } else {
- newval = -1;
- }
- }
-
- j->sent_signal_time = runtime_get_opaque_time();
-
- if (newval < 0) {
- j->clean_kill = true;
- job_kill(j);
- } else {
- (void)job_assumes(j, runtime_kill(j->p, SIGTERM) != -1);
-
- if (j->exit_timeout) {
- (void)job_assumes(j, kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, j->exit_timeout, j) != -1);
- } else {
- job_log(j, LOG_NOTICE, "This job has an infinite exit timeout");
- }
-
- if (j->kill_via_shmem) {
- snprintf(extralog, sizeof(extralog), ": %d remaining transactions", newval + 1);
- } else {
- extralog[0] = '\0';
- }
-
- job_log(j, LOG_DEBUG, "Sent SIGTERM signal%s", extralog);
- }
-
- j->stopped = true;
-}
-
-launch_data_t
-job_export(job_t j)
-{
- launch_data_t tmp, tmp2, tmp3, r = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
-
- if (r == NULL) {
- return NULL;
- }
-
- if ((tmp = launch_data_new_string(j->label))) {
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_LABEL);
- }
- if ((tmp = launch_data_new_string(j->mgr->name))) {
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE);
- }
- if ((tmp = launch_data_new_bool(j->ondemand))) {
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_ONDEMAND);
- }
- if ((tmp = launch_data_new_integer(j->last_exit_status))) {
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_LASTEXITSTATUS);
- }
- if (j->p && (tmp = launch_data_new_integer(j->p))) {
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_PID);
- }
- if ((tmp = launch_data_new_integer(j->timeout))) {
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_TIMEOUT);
- }
- if (j->prog && (tmp = launch_data_new_string(j->prog))) {
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_PROGRAM);
- }
- if (j->stdinpath && (tmp = launch_data_new_string(j->stdinpath))) {
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_STANDARDINPATH);
- }
- if (j->stdoutpath && (tmp = launch_data_new_string(j->stdoutpath))) {
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_STANDARDOUTPATH);
- }
- if (j->stderrpath && (tmp = launch_data_new_string(j->stderrpath))) {
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_STANDARDERRORPATH);
- }
- if (likely(j->argv) && (tmp = launch_data_alloc(LAUNCH_DATA_ARRAY))) {
- size_t i;
-
- for (i = 0; i < j->argc; i++) {
- if ((tmp2 = launch_data_new_string(j->argv[i]))) {
- launch_data_array_set_index(tmp, tmp2, i);
- }
- }
-
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_PROGRAMARGUMENTS);
- }
-
- if (j->kill_via_shmem && (tmp = launch_data_new_bool(true))) {
- int32_t tmp_cnt = -1;
-
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_ENABLETRANSACTIONS);
-
- if (j->shmem) {
- tmp_cnt = j->shmem->vp_shmem_transaction_cnt;
- }
-
- if (j->sent_kill_via_shmem) {
- tmp_cnt++;
- }
-
- if ((tmp = launch_data_new_integer(tmp_cnt))) {
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_TRANSACTIONCOUNT);
- }
- }
-
- if (j->session_create && (tmp = launch_data_new_bool(true))) {
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_SESSIONCREATE);
- }
-
- if (j->inetcompat && (tmp = launch_data_alloc(LAUNCH_DATA_DICTIONARY))) {
- if ((tmp2 = launch_data_new_bool(j->inetcompat_wait))) {
- launch_data_dict_insert(tmp, tmp2, LAUNCH_JOBINETDCOMPATIBILITY_WAIT);
- }
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_INETDCOMPATIBILITY);
- }
-
- if (!SLIST_EMPTY(&j->sockets) && (tmp = launch_data_alloc(LAUNCH_DATA_DICTIONARY))) {
- struct socketgroup *sg;
- int i;
-
- SLIST_FOREACH(sg, &j->sockets, sle) {
- if (sg->junkfds) {
- continue;
- }
- if ((tmp2 = launch_data_alloc(LAUNCH_DATA_ARRAY))) {
- for (i = 0; i < sg->fd_cnt; i++) {
- if ((tmp3 = launch_data_new_fd(sg->fds[i]))) {
- launch_data_array_set_index(tmp2, tmp3, i);
- }
- }
- launch_data_dict_insert(tmp, tmp2, sg->name);
- }
- }
-
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_SOCKETS);
- }
-
- if (!SLIST_EMPTY(&j->machservices) && (tmp = launch_data_alloc(LAUNCH_DATA_DICTIONARY))) {
- struct machservice *ms;
-
- tmp3 = NULL;
-
- SLIST_FOREACH(ms, &j->machservices, sle) {
- if (ms->per_pid) {
- if (tmp3 == NULL) {
- tmp3 = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
- }
- if (tmp3) {
- tmp2 = launch_data_new_machport(MACH_PORT_NULL);
- launch_data_dict_insert(tmp3, tmp2, ms->name);
- }
- } else {
- tmp2 = launch_data_new_machport(MACH_PORT_NULL);
- launch_data_dict_insert(tmp, tmp2, ms->name);
- }
- }
-
- launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_MACHSERVICES);
-
- if (tmp3) {
- launch_data_dict_insert(r, tmp3, LAUNCH_JOBKEY_PERJOBMACHSERVICES);
- }
- }
-
- return r;
-}
-
-static void
-jobmgr_log_active_jobs(jobmgr_t jm)
-{
- const char *why_active;
- jobmgr_t jmi;
- job_t ji;
-
- SLIST_FOREACH(jmi, &jm->submgrs, sle) {
- jobmgr_log_active_jobs(jmi);
- }
-
- LIST_FOREACH(ji, &jm->jobs, sle) {
- if ((why_active = job_active(ji))) {
- if (ji->p != 1) {
- job_log(ji, LOG_DEBUG | LOG_CONSOLE, "%s", why_active);
- }
- }
- }
-}
-
-static void
-jobmgr_still_alive_with_check(jobmgr_t jm)
-{
- jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Still alive with %lu/%lu (normal/anonymous) children.", total_children, total_anon_children);
- jobmgr_log_active_jobs(jm);
-}
-
-jobmgr_t
-jobmgr_shutdown(jobmgr_t jm)
-{
- jobmgr_t jmi, jmn;
- jobmgr_log(jm, LOG_DEBUG, "Beginning job manager shutdown with flags: %s", reboot_flags_to_C_names(jm->reboot_flags));
-
- jm->shutdown_time = runtime_get_wall_time() / USEC_PER_SEC;
-
- struct tm curtime;
- (void)localtime_r(&jm->shutdown_time, &curtime);
-
- char date[26];
- (void)asctime_r(&curtime, date);
- /* Trim the new line that asctime_r(3) puts there for some reason. */
- date[24] = 0;
-
- if (jm == root_jobmgr && pid1_magic) {
- jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Userspace shutdown begun at: %s", date);
- } else {
- jobmgr_log(jm, LOG_DEBUG, "Job manager shutdown begun at: %s", date);
- }
-
- jm->shutting_down = true;
-
- SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) {
- jobmgr_shutdown(jmi);
- }
-
- if (jm->parentmgr == NULL && pid1_magic) {
- (void)jobmgr_assumes(jm, kevent_mod((uintptr_t)jm, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, 5, jm));
-
- /* Spawn the shutdown monitor. */
- if (_s_shutdown_monitor && !_s_shutdown_monitor->p) {
- job_log(_s_shutdown_monitor, LOG_NOTICE | LOG_CONSOLE, "Starting shutdown monitor.");
- job_dispatch(_s_shutdown_monitor, true);
- }
- }
-
- return jobmgr_do_garbage_collection(jm);
-}
-
-void
-jobmgr_remove(jobmgr_t jm)
-{
- jobmgr_t jmi;
- job_t ji;
-
- jobmgr_log(jm, LOG_DEBUG, "Removing job manager.");
- if (!jobmgr_assumes(jm, SLIST_EMPTY(&jm->submgrs))) {
- while ((jmi = SLIST_FIRST(&jm->submgrs))) {
- jobmgr_remove(jmi);
- }
- }
-
- while ((ji = LIST_FIRST(&jm->jobs))) {
- if (!ji->anonymous && !job_assumes(ji, ji->p == 0)) {
- ji->p = 0;
- }
- job_remove(ji);
- }
-
- if (jm->req_port) {
- (void)jobmgr_assumes(jm, launchd_mport_deallocate(jm->req_port) == KERN_SUCCESS);
- }
- if (jm->jm_port) {
- (void)jobmgr_assumes(jm, launchd_mport_close_recv(jm->jm_port) == KERN_SUCCESS);
- }
-
- if (jm->req_bsport) {
- (void)jobmgr_assumes(jm, launchd_mport_deallocate(jm->req_bsport) == KERN_SUCCESS);
- }
- if (jm->req_excport) {
- (void)jobmgr_assumes(jm, launchd_mport_deallocate(jm->req_excport) == KERN_SUCCESS);
- }
- if (jm->req_asport) {
- (void)jobmgr_assumes(jm, launchd_mport_deallocate(jm->req_asport) == KERN_SUCCESS);
- }
-#if !TARGET_OS_EMBEDDED
- if (jm->req_rport) {
- kern_return_t kr = xpc_call_wakeup(jm->req_rport, jm->error);
- if (!(kr == KERN_SUCCESS || kr == MACH_SEND_INVALID_DEST)) {
- /* If the originator went away, the reply port will be a dead name,
- * and we expect this to fail.
- */
- errno = kr;
- (void)jobmgr_assumes(jm, kr == KERN_SUCCESS);
- }
- }
-#endif /* !TARGET_OS_EMBEDDED */
- if (jm->req_ctx) {
- (void)jobmgr_assumes(jm, vm_deallocate(mach_task_self(), jm->req_ctx, jm->req_ctx_sz) == KERN_SUCCESS);
- }
-
- time_t ts = runtime_get_wall_time() / USEC_PER_SEC;
- struct tm curtime;
- (void)localtime_r(&ts, &curtime);
-
- char date[26];
- (void)asctime_r(&curtime, date);
- date[24] = 0;
-
- time_t delta = ts - jm->shutdown_time;
- if (jm == root_jobmgr && pid1_magic) {
- jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Userspace shutdown finished at: %s", date);
- jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Userspace shutdown took approximately %ld second%s.", delta, (delta != 1) ? "s" : "");
- } else {
- jobmgr_log(jm, LOG_DEBUG, "Job manager shutdown finished at: %s", date);
- jobmgr_log(jm, LOG_DEBUG, "Job manager shutdown took approximately %ld second%s.", delta, (delta != 1) ? "s" : "");
- }
-
- if (jm->parentmgr) {
- runtime_del_weak_ref();
- SLIST_REMOVE(&jm->parentmgr->submgrs, jm, jobmgr_s, sle);
- } else if (pid1_magic) {
- eliminate_double_reboot();
- launchd_log_vm_stats();
- jobmgr_log_stray_children(jm, true);
- jobmgr_log(root_jobmgr, LOG_NOTICE | LOG_CONSOLE, "About to call: reboot(%s).", reboot_flags_to_C_names(jm->reboot_flags));
- runtime_closelog();
- (void)jobmgr_assumes(jm, reboot(jm->reboot_flags) != -1);
- } else {
- jobmgr_log(jm, LOG_DEBUG, "About to exit");
- runtime_closelog();
- exit(EXIT_SUCCESS);
- }
-
- free(jm);
-}
-
-void
-job_remove(job_t j)
-{
- struct waiting_for_removal *w4r;
- struct calendarinterval *ci;
- struct semaphoreitem *si;
- struct socketgroup *sg;
- struct machservice *ms;
- struct limititem *li;
- struct envitem *ei;
-
- if (j->alias) {
- /* HACK: Egregious code duplication. But as with machservice_delete(),
- * job aliases can't (and shouldn't) have any complex behaviors
- * associated with them.
- */
- while ((ms = SLIST_FIRST(&j->machservices))) {
- machservice_delete(j, ms, false);
- }
-
- LIST_REMOVE(j, sle);
- LIST_REMOVE(j, label_hash_sle);
- free(j);
- return;
- }
-
-#if TARGET_OS_EMBEDDED
- if (g_embedded_privileged_action && s_embedded_privileged_job) {
- if (!job_assumes(j, s_embedded_privileged_job->username != NULL && j->username != NULL)) {
- errno = EPERM;
- return;
- }
-
- if (strcmp(j->username, s_embedded_privileged_job->username) != 0) {
- errno = EPERM;
- return;
- }
- } else if (g_embedded_privileged_action) {
- errno = EINVAL;
- return;
- }
-#endif
-
- /* Do this BEFORE we check and see whether the job is still active. If we're a
- * sub-job, we're being removed due to the parent job removing us. Therefore, the
- * parent job will free itself after this call completes. So if we defer removing
- * ourselves from the parent's list, we'll crash when we finally get around to it.
- */
- if (j->dedicated_instance && !j->former_subjob) {
- LIST_REMOVE(j, subjob_sle);
- j->former_subjob = true;
- }
-
- if (unlikely(j->p)) {
- if (j->anonymous) {
- job_reap(j);
- } else {
- job_log(j, LOG_DEBUG, "Removal pended until the job exits");
-
- if (!j->removal_pending) {
- j->removal_pending = true;
- job_stop(j);
- }
-
- return;
- }
- }
-
- if (!j->removing) {
- j->removing = true;
- job_dispatch_curious_jobs(j);
- }
-
- ipc_close_all_with_job(j);
-
- job_log(j, LOG_INFO, "Total rusage: utime %ld.%06u stime %ld.%06u maxrss %lu ixrss %lu idrss %lu isrss %lu minflt %lu majflt %lu nswap %lu inblock %lu oublock %lu msgsnd %lu msgrcv %lu nsignals %lu nvcsw %lu nivcsw %lu",
- j->ru.ru_utime.tv_sec, j->ru.ru_utime.tv_usec,
- j->ru.ru_stime.tv_sec, j->ru.ru_stime.tv_usec,
- j->ru.ru_maxrss, j->ru.ru_ixrss, j->ru.ru_idrss, j->ru.ru_isrss,
- j->ru.ru_minflt, j->ru.ru_majflt,
- j->ru.ru_nswap, j->ru.ru_inblock, j->ru.ru_oublock,
- j->ru.ru_msgsnd, j->ru.ru_msgrcv,
- j->ru.ru_nsignals, j->ru.ru_nvcsw, j->ru.ru_nivcsw);
-
- if (j->forced_peers_to_demand_mode) {
- job_set_global_on_demand(j, false);
- }
-
- if (!job_assumes(j, j->fork_fd == 0)) {
- (void)job_assumes(j, runtime_close(j->fork_fd) != -1);
- }
-
- if (j->stdin_fd) {
- (void)job_assumes(j, runtime_close(j->stdin_fd) != -1);
- }
-
- if (!job_assumes(j, j->log_redirect_fd == 0)) {
- (void)job_assumes(j, runtime_close(j->log_redirect_fd) != -1);
- }
-
- if (j->j_port) {
- (void)job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS);
- }
-
- while ((sg = SLIST_FIRST(&j->sockets))) {
- socketgroup_delete(j, sg);
- }
- while ((ci = SLIST_FIRST(&j->cal_intervals))) {
- calendarinterval_delete(j, ci);
- }
- while ((ei = SLIST_FIRST(&j->env))) {
- envitem_delete(j, ei, false);
- }
- while ((ei = SLIST_FIRST(&j->global_env))) {
- envitem_delete(j, ei, true);
- }
- while ((li = SLIST_FIRST(&j->limits))) {
- limititem_delete(j, li);
- }
- while ((ms = SLIST_FIRST(&j->machservices))) {
- machservice_delete(j, ms, false);
- }
- while ((si = SLIST_FIRST(&j->semaphores))) {
- semaphoreitem_delete(j, si);
- }
- while ((w4r = SLIST_FIRST(&j->removal_watchers))) {
- waiting4removal_delete(j, w4r);
- }
-
- struct externalevent *eei = NULL;
- while ((eei = LIST_FIRST(&j->events))) {
- eventsystem_ping();
- externalevent_delete(eei);
- }
-
-#if 0
- /* Event systems exist independently of an actual monitor job. They're
- * created on-demand when a job has a LaunchEvents dictionary. So we
- * really don't need to get rid of them.
- */
- if (j->event_monitor) {
- struct eventsystem *esi = NULL;
- while ((esi = LIST_FIRST(&_s_event_systems))) {
- eventsystem_delete(esi);
- }
- }
-#else
- if (false) {
- /* Make gcc happy. */
- eventsystem_delete(NULL);
- }
- if (j->event_monitor) {
- if (_s_event_update_port != MACH_PORT_NULL) {
- (void)job_assumes(j, launchd_mport_deallocate(_s_event_update_port) == KERN_SUCCESS);
- _s_event_update_port = MACH_PORT_NULL;
- }
- _s_event_monitor = NULL;
- }
-#endif
-
- if (j->prog) {
- free(j->prog);
- }
- if (j->argv) {
- free(j->argv);
- }
- if (j->rootdir) {
- free(j->rootdir);
- }
- if (j->workingdir) {
- free(j->workingdir);
- }
- if (j->username) {
- free(j->username);
- }
- if (j->groupname) {
- free(j->groupname);
- }
- if (j->stdinpath) {
- free(j->stdinpath);
- }
- if (j->stdoutpath) {
- free(j->stdoutpath);
- }
- if (j->stderrpath) {
- free(j->stderrpath);
- }
- if (j->alt_exc_handler) {
- free(j->alt_exc_handler);
- }
-#if HAVE_SANDBOX
- if (j->seatbelt_profile) {
- free(j->seatbelt_profile);
- }
-#endif
-#if HAVE_QUARANTINE
- if (j->quarantine_data) {
- free(j->quarantine_data);
- }
-#endif
- if (j->j_binpref) {
- free(j->j_binpref);
- }
- if (j->start_interval) {
- runtime_del_weak_ref();
- (void)job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_DELETE, 0, 0, NULL) != -1);
- }
- if (j->poll_for_vfs_changes) {
- (void)job_assumes(j, kevent_mod((uintptr_t)&j->semaphores, EVFILT_TIMER, EV_DELETE, 0, 0, j) != -1);
- }
- if (j->exit_timeout) {
- /* Not a big deal if this fails. It means that the timer's already been freed. */
- kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
- }
- if (j->jetsam_properties) {
- LIST_REMOVE(j, jetsam_sle);
- j->mgr->jetsam_jobs_cnt--;
- }
- if (j->asport != MACH_PORT_NULL) {
- (void)job_assumes(j, launchd_mport_deallocate(j->asport) == KERN_SUCCESS);
- }
- if (!uuid_is_null(j->expected_audit_uuid)) {
- LIST_REMOVE(j, needing_session_sle);
- }
- if (j->embedded_special_privileges) {
- s_embedded_privileged_job = NULL;
- }
- if (j->shutdown_monitor) {
- _s_shutdown_monitor = NULL;
- }
-
- kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
-
- LIST_REMOVE(j, sle);
- LIST_REMOVE(j, label_hash_sle);
-
- job_t ji = NULL;
- job_t jit = NULL;
- LIST_FOREACH_SAFE(ji, &j->subjobs, subjob_sle, jit) {
- job_remove(ji);
- }
-
- job_log(j, LOG_DEBUG, "Removed");
-
- j->kqjob_callback = (kq_callback)0x8badf00d;
- free(j);
-}
-
-void
-socketgroup_setup(launch_data_t obj, const char *key, void *context)
-{
- launch_data_t tmp_oai;
- job_t j = context;
- size_t i, fd_cnt = 1;
- int *fds;
-
- if (launch_data_get_type(obj) == LAUNCH_DATA_ARRAY) {
- fd_cnt = launch_data_array_get_count(obj);
- }
-
- fds = alloca(fd_cnt * sizeof(int));
-
- for (i = 0; i < fd_cnt; i++) {
- if (launch_data_get_type(obj) == LAUNCH_DATA_ARRAY) {
- tmp_oai = launch_data_array_get_index(obj, i);
- } else {
- tmp_oai = obj;
- }
-
- fds[i] = launch_data_get_fd(tmp_oai);
- }
-
- socketgroup_new(j, key, fds, fd_cnt, strcmp(key, LAUNCH_JOBKEY_BONJOURFDS) == 0);
-
- ipc_revoke_fds(obj);
-}
-
-bool
-job_set_global_on_demand(job_t j, bool val)
-{
- if (j->forced_peers_to_demand_mode && val) {
- return false;
- } else if (!j->forced_peers_to_demand_mode && !val) {
- return false;
- }
-
- if ((j->forced_peers_to_demand_mode = val)) {
- j->mgr->global_on_demand_cnt++;
- } else {
- j->mgr->global_on_demand_cnt--;
- }
-
- if (j->mgr->global_on_demand_cnt == 0) {
- jobmgr_dispatch_all(j->mgr, false);
- }
-
- return true;
-}
-
-bool
-job_setup_machport(job_t j)
-{
- mach_msg_size_t mxmsgsz;
-
- if (!job_assumes(j, launchd_mport_create_recv(&j->j_port) == KERN_SUCCESS)) {
- goto out_bad;
- }
-
- /* Sigh... at the moment, MIG has maxsize == sizeof(reply union) */
- mxmsgsz = (typeof(mxmsgsz)) sizeof(union __RequestUnion__job_mig_protocol_vproc_subsystem);
- if (job_mig_protocol_vproc_subsystem.maxsize > mxmsgsz) {
- mxmsgsz = job_mig_protocol_vproc_subsystem.maxsize;
- }
-
- if (!job_assumes(j, runtime_add_mport(j->j_port, protocol_vproc_server, mxmsgsz) == KERN_SUCCESS)) {
- goto out_bad2;
- }
-
- if (!job_assumes(j, launchd_mport_notify_req(j->j_port, MACH_NOTIFY_NO_SENDERS) == KERN_SUCCESS)) {
- (void)job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS);
- goto out_bad;
- }
-
- return true;
-out_bad2:
- (void)job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS);
-out_bad:
- return false;
-}
-
-kern_return_t
-job_setup_exit_port(job_t j)
-{
- kern_return_t kr = launchd_mport_create_recv(&j->exit_status_port);
- if (!job_assumes(j, kr == KERN_SUCCESS)) {
- return MACH_PORT_NULL;
- }
-
- struct mach_port_limits limits = {
- .mpl_qlimit = 1,
- };
- kr = mach_port_set_attributes(mach_task_self(), j->exit_status_port, MACH_PORT_LIMITS_INFO, (mach_port_info_t)&limits, sizeof(limits));
- (void)job_assumes(j, kr == KERN_SUCCESS);
-
- kr = launchd_mport_make_send_once(j->exit_status_port, &j->exit_status_dest);
- if (!job_assumes(j, kr == KERN_SUCCESS)) {
- (void)job_assumes(j, launchd_mport_close_recv(j->exit_status_port) == KERN_SUCCESS);
- j->exit_status_port = MACH_PORT_NULL;
- }
-
- return kr;
-}
-
-job_t
-job_new_via_mach_init(job_t j, const char *cmd, uid_t uid, bool ond)
-{
- const char **argv = (const char **)mach_cmd2argv(cmd);
- job_t jr = NULL;
-
- if (!job_assumes(j, argv != NULL)) {
- goto out_bad;
- }
-
- jr = job_new(j->mgr, AUTO_PICK_LEGACY_LABEL, NULL, argv);
-
- free(argv);
-
- /* jobs can easily be denied creation during shutdown */
- if (unlikely(jr == NULL)) {
- goto out_bad;
- }
-
- jr->mach_uid = uid;
- jr->ondemand = ond;
- jr->legacy_mach_job = true;
- jr->abandon_pg = true;
- jr->priv_port_has_senders = true; /* the IPC that called us will make-send on this port */
-
- if (!job_setup_machport(jr)) {
- goto out_bad;
- }
-
- job_log(jr, LOG_INFO, "Legacy%s server created", ond ? " on-demand" : "");
-
- return jr;
-
-out_bad:
- if (jr) {
- job_remove(jr);
- }
- return NULL;
-}
-
-job_t
-job_new_anonymous(jobmgr_t jm, pid_t anonpid)
-{
- struct proc_bsdshortinfo proc;
- bool shutdown_state;
- job_t jp = NULL, jr = NULL;
- uid_t kp_euid, kp_uid, kp_svuid;
- gid_t kp_egid, kp_gid, kp_svgid;
-
- if (!jobmgr_assumes(jm, anonpid != 0)) {
- errno = EINVAL;
- return NULL;
- }
-
- if (!jobmgr_assumes(jm, anonpid < 100000)) {
- /* The kernel current defines PID_MAX to be 99999, but that define isn't exported */
- errno = EINVAL;
- return NULL;
- }
-
- /* libproc returns the number of bytes written into the buffer upon success,
- * zero on failure.
- */
- if (proc_pidinfo(anonpid, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
- if (errno != ESRCH) {
- (void)jobmgr_assumes(jm, errno == 0);
- }
- return NULL;
- }
-
- if (!jobmgr_assumes(jm, proc.pbsi_comm[0] != '\0')) {
- errno = EINVAL;
- return NULL;
- }
-
- if (unlikely(proc.pbsi_status == SZOMB)) {
- jobmgr_log(jm, LOG_DEBUG, "Tried to create an anonymous job for zombie PID %u: %s", anonpid, proc.pbsi_comm);
- }
-
- if (unlikely(proc.pbsi_flags & P_SUGID)) {
- jobmgr_log(jm, LOG_DEBUG, "Inconsistency: P_SUGID is set on PID %u: %s", anonpid, proc.pbsi_comm);
- }
-
- kp_euid = proc.pbsi_uid;
- kp_uid = proc.pbsi_ruid;
- kp_svuid = proc.pbsi_svuid;
- kp_egid = proc.pbsi_gid;
- kp_gid = proc.pbsi_rgid;
- kp_svgid = proc.pbsi_svgid;
-
- if (unlikely(kp_euid != kp_uid || kp_euid != kp_svuid || kp_uid != kp_svuid || kp_egid != kp_gid || kp_egid != kp_svgid || kp_gid != kp_svgid)) {
- jobmgr_log(jm, LOG_DEBUG, "Inconsistency: Mixed credentials (e/r/s UID %u/%u/%u GID %u/%u/%u) detected on PID %u: %s",
- kp_euid, kp_uid, kp_svuid, kp_egid, kp_gid, kp_svgid, anonpid, proc.pbsi_comm);
- }
-
- /* "Fix" for a problem that shouldn't even exist.
- * See rdar://problem/7264615 for the symptom and rdar://problem/5020256
- * as to why this can happen.
- */
- if (!jobmgr_assumes(jm, (pid_t)proc.pbsi_ppid != anonpid)) {
- jobmgr_log(jm, LOG_WARNING, "Process has become its own parent through ptrace(3). It should find a different way to do whatever it's doing. Setting PPID to 0: %s", proc.pbsi_comm);
- errno = EINVAL;
- return NULL;
- }
-
- /* A total hack: Normally, job_new() returns an error during shutdown, but anonymous jobs are special. */
- if (unlikely(shutdown_state = jm->shutting_down)) {
- jm->shutting_down = false;
- }
-
- /* We only set requestor_pid for XPC domains. */
- const char *whichlabel = (jm->req_pid == anonpid) ? AUTO_PICK_XPC_LABEL : AUTO_PICK_ANONYMOUS_LABEL;
- if (jobmgr_assumes(jm, (jr = job_new(jm, whichlabel, proc.pbsi_comm, NULL)) != NULL)) {
- u_int proc_fflags = NOTE_EXEC|NOTE_FORK|NOTE_EXIT;
-
- total_anon_children++;
- jr->anonymous = true;
- jr->p = anonpid;
-
- /* anonymous process reaping is messy */
- LIST_INSERT_HEAD(&jm->active_jobs[ACTIVE_JOB_HASH(jr->p)], jr, pid_hash_sle);
-
- if (unlikely(kevent_mod(jr->p, EVFILT_PROC, EV_ADD, proc_fflags, 0, root_jobmgr) == -1) && job_assumes(jr, errno == ESRCH)) {
- /* zombies are weird */
- job_log(jr, LOG_ERR, "Failed to add kevent for PID %u. Will unload at MIG return", jr->p);
- jr->unload_at_mig_return = true;
- }
-
- if (unlikely(shutdown_state)) {
- job_log(jr, LOG_SCOLDING, "This process showed up to the party while all the guests were leaving. Odds are that it will have a miserable time.");
- }
-
- job_log(jr, LOG_DEBUG, "Created PID %u anonymously by PPID %u%s%s", anonpid, proc.pbsi_ppid, jp ? ": " : "", jp ? jp->label : "");
- }
-
- if (unlikely(shutdown_state)) {
- jm->shutting_down = true;
- }
-
- /* This is down here to mitigate the effects of rdar://problem/7264615, in which a process
- * attaches to its own parent. We need to make sure that the anonymous job has been added
- * to the process list so that, if it's used ptrace(3) to cause a cycle in the process
- * tree (thereby making it not a tree anymore), we'll find the tracing parent PID of the
- * parent process, which is the child, when we go looking for it in jobmgr_find_by_pid().
- */
- switch (proc.pbsi_ppid) {
- case 0:
- /* the kernel */
- break;
- case 1:
- if (!pid1_magic) {
- /* we cannot possibly find a parent job_t that is useful in this function */
- break;
- }
- /* fall through */
- default:
- jp = jobmgr_find_by_pid(jm, proc.pbsi_ppid, true);
- if (jobmgr_assumes(jm, jp != NULL)) {
- if (jp && !jp->anonymous && unlikely(!(proc.pbsi_flags & P_EXEC))) {
- job_log(jp, LOG_DEBUG, "Called *fork(). Please switch to posix_spawn*(), pthreads or launchd. Child PID %u", proc.pbsi_pid);
- }
- }
- break;
- }
-
- return jr;
-}
-
-job_t
-job_new_subjob(job_t j, uuid_t identifier)
-{
- char label[0];
- uuid_string_t idstr;
- uuid_unparse(identifier, idstr);
- size_t label_sz = snprintf(label, 0, "%s.%s", j->label, idstr);
-
- job_t nj = (struct job_s *)calloc(1, sizeof(struct job_s) + label_sz + 1);
- if (launchd_assumes(nj != NULL)) {
- nj->kqjob_callback = job_callback;
- nj->mgr = j->mgr;
- nj->min_run_time = j->min_run_time;
- nj->timeout = j->timeout;
- nj->exit_timeout = j->exit_timeout;
-
- snprintf((char *)nj->label, label_sz + 1, "%s.%s", j->label, idstr);
-
- /* Set all our simple Booleans that are applicable. */
- nj->debug = j->debug;
- nj->ondemand = j->ondemand;
- nj->checkedin = true;
- nj->low_pri_io = j->low_pri_io;
- nj->setmask = j->setmask;
- nj->wait4debugger = j->wait4debugger;
- nj->internal_exc_handler = j->internal_exc_handler;
- nj->setnice = j->setnice;
- nj->abandon_pg = j->abandon_pg;
- nj->ignore_pg_at_shutdown = j->ignore_pg_at_shutdown;
- nj->deny_job_creation = j->deny_job_creation;
- nj->kill_via_shmem = j->kill_via_shmem;
- nj->needs_kickoff = j->needs_kickoff;
- nj->currently_ignored = true;
- nj->dedicated_instance = true;
- nj->xpc_service = j->xpc_service;
- nj->xpc_bootstrapper = j->xpc_bootstrapper;
-
- nj->mask = j->mask;
- uuid_copy(nj->instance_id, identifier);
-
- /* These jobs are purely on-demand Mach jobs. */
-
- /* {Hard | Soft}ResourceLimits are not supported. */
-
- struct machservice *msi = NULL;
- SLIST_FOREACH(msi, &j->machservices, sle) {
- /* Only copy MachServices that were actually declared in the plist.
- * So skip over per-PID ones and ones that were created via
- * bootstrap_register().
- */
- if (msi->upfront) {
- mach_port_t mp = MACH_PORT_NULL;
- struct machservice *msj = machservice_new(nj, msi->name, &mp, msi->per_pid);
- if (job_assumes(nj, msj != NULL)) {
- msj->reset = msi->reset;
- msj->delete_on_destruction = msi->delete_on_destruction;
- msj->drain_one_on_crash = msi->drain_one_on_crash;
- msj->drain_all_on_crash = msi->drain_all_on_crash;
- }
- }
- }
-
- if (j->prog) {
- nj->prog = strdup(j->prog);
- }
- if (j->argv) {
- size_t sz = malloc_size(j->argv);
- nj->argv = (char **)malloc(sz);
- if (job_assumes(nj, nj->argv != NULL)) {
- /* This is the start of our strings. */
- char *p = ((char *)nj->argv) + ((j->argc + 1) * sizeof(char *));
-
- size_t i = 0;
- for (i = 0; i < j->argc; i++) {
- (void)strcpy(p, j->argv[i]);
- nj->argv[i] = p;
- p += (strlen(j->argv[i]) + 1);
- }
- nj->argv[i] = NULL;
- }
-
- nj->argc = j->argc;
- }
-
- /* We ignore global environment variables. */
- struct envitem *ei = NULL;
- SLIST_FOREACH(ei, &j->env, sle) {
- (void)job_assumes(nj, envitem_new(nj, ei->key, ei->value, false, false));
- }
- uuid_string_t val;
- uuid_unparse(identifier, val);
- (void)job_assumes(nj, envitem_new(nj, LAUNCH_ENV_INSTANCEID, val, false, false));
-
- if (j->rootdir) {
- nj->rootdir = strdup(j->rootdir);
- }
- if (j->workingdir) {
- nj->workingdir = strdup(j->workingdir);
- }
- if (j->username) {
- nj->username = strdup(j->username);
- }
- if (j->groupname) {
- nj->groupname = strdup(j->groupname);
- }
- /* FIXME: We shouldn't redirect all the output from these jobs to the same
- * file. We should uniquify the file names.
- */
- if (j->stdinpath) {
- nj->stdinpath = strdup(j->stdinpath);
- }
- if (j->stdoutpath) {
- nj->stdoutpath = strdup(j->stdinpath);
- }
- if (j->stderrpath) {
- nj->stderrpath = strdup(j->stderrpath);
- }
- if (j->alt_exc_handler) {
- nj->alt_exc_handler = strdup(j->alt_exc_handler);
- }
- #if HAVE_SANDBOX
- if (j->seatbelt_profile) {
- nj->seatbelt_profile = strdup(j->seatbelt_profile);
- }
- #endif
-
- #if HAVE_QUARANTINE
- if (j->quarantine_data) {
- nj->quarantine_data = strdup(j->quarantine_data);
- }
- nj->quarantine_data_sz = j->quarantine_data_sz;
- #endif
- if (j->j_binpref) {
- size_t sz = malloc_size(j->j_binpref);
- nj->j_binpref = (cpu_type_t *)malloc(sz);
- if (job_assumes(nj, nj->j_binpref)) {
- memcpy(&nj->j_binpref, &j->j_binpref, sz);
- }
- }
-
- /* JetsamPriority is unsupported. */
-
- if (j->asport != MACH_PORT_NULL) {
- (void)job_assumes(nj, launchd_mport_copy_send(j->asport) == KERN_SUCCESS);
- nj->asport = j->asport;
- }
-
- LIST_INSERT_HEAD(&nj->mgr->jobs, nj, sle);
-
- jobmgr_t where2put = root_jobmgr;
- if (j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) {
- where2put = j->mgr;
- }
- LIST_INSERT_HEAD(&where2put->label_hash[hash_label(nj->label)], nj, label_hash_sle);
- LIST_INSERT_HEAD(&j->subjobs, nj, subjob_sle);
- }
-
- return nj;
-}
-
-job_t
-job_new(jobmgr_t jm, const char *label, const char *prog, const char *const *argv)
-{
- const char *const *argv_tmp = argv;
- char tmp_path[PATH_MAX];
- char auto_label[1000];
- const char *bn = NULL;
- char *co;
- size_t minlabel_len;
- size_t i, cc = 0;
- job_t j;
-
- launchd_assert(offsetof(struct job_s, kqjob_callback) == 0);
-
- if (unlikely(jm->shutting_down)) {
- errno = EINVAL;
- return NULL;
- }
-
- if (unlikely(prog == NULL && argv == NULL)) {
- errno = EINVAL;
- return NULL;
- }
-
- char *anon_or_legacy = (label == AUTO_PICK_ANONYMOUS_LABEL) ? "anonymous" : "mach_init";
- if (unlikely(label == AUTO_PICK_LEGACY_LABEL || label == AUTO_PICK_ANONYMOUS_LABEL)) {
- if (prog) {
- bn = prog;
- } else {
- strlcpy(tmp_path, argv[0], sizeof(tmp_path));
- bn = basename(tmp_path); /* prog for auto labels is kp.kp_kproc.p_comm */
- }
- snprintf(auto_label, sizeof(auto_label), "%s.%s.%s", sizeof(void *) == 8 ? "0xdeadbeeffeedface" : "0xbabecafe", anon_or_legacy, bn);
- label = auto_label;
- /* This is so we can do gross things later. See NOTE_EXEC for anonymous jobs */
- minlabel_len = strlen(label) + MAXCOMLEN;
- } else {
- if (label == AUTO_PICK_XPC_LABEL) {
- minlabel_len = snprintf(auto_label, sizeof(auto_label), "com.apple.xpc.domain-owner.%s", jm->owner);
- } else {
- minlabel_len = strlen(label);
- }
- }
-
- j = calloc(1, sizeof(struct job_s) + minlabel_len + 1);
-
- if (!jobmgr_assumes(jm, j != NULL)) {
- return NULL;
- }
-
- if (unlikely(label == auto_label)) {
- snprintf((char *)j->label, strlen(label) + 1, "%p.%s.%s", j, anon_or_legacy, bn);
- } else {
- strcpy((char *)j->label, (label == AUTO_PICK_XPC_LABEL) ? auto_label : label);
- }
- j->kqjob_callback = job_callback;
- j->mgr = jm;
- j->min_run_time = LAUNCHD_MIN_JOB_RUN_TIME;
- j->timeout = RUNTIME_ADVISABLE_IDLE_TIMEOUT;
- j->exit_timeout = LAUNCHD_DEFAULT_EXIT_TIMEOUT;
- j->currently_ignored = true;
- j->ondemand = true;
- j->checkedin = true;
- j->jetsam_priority = -1;
- j->jetsam_memlimit = -1;
- j->jetsam_seq = -1;
- uuid_clear(j->expected_audit_uuid);
-
- if (prog) {
- j->prog = strdup(prog);
- if (!job_assumes(j, j->prog != NULL)) {
- goto out_bad;
- }
- }
-
- if (likely(argv)) {
- while (*argv_tmp++) {
- j->argc++;
- }
-
- for (i = 0; i < j->argc; i++) {
- cc += strlen(argv[i]) + 1;
- }
-
- j->argv = malloc((j->argc + 1) * sizeof(char *) + cc);
-
- if (!job_assumes(j, j->argv != NULL)) {
- goto out_bad;
- }
-
- co = ((char *)j->argv) + ((j->argc + 1) * sizeof(char *));
-
- for (i = 0; i < j->argc; i++) {
- j->argv[i] = co;
- strcpy(co, argv[i]);
- co += strlen(argv[i]) + 1;
- }
- j->argv[i] = NULL;
- }
-
- if (strcmp(j->label, "com.apple.WindowServer") == 0) {
- j->has_console = true;
- }
-
- LIST_INSERT_HEAD(&jm->jobs, j, sle);
-
- jobmgr_t where2put_label = root_jobmgr;
- if (j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) {
- where2put_label = j->mgr;
- }
- LIST_INSERT_HEAD(&where2put_label->label_hash[hash_label(j->label)], j, label_hash_sle);
- uuid_clear(j->expected_audit_uuid);
-
- job_log(j, LOG_DEBUG, "Conceived");
-
- return j;
-
-out_bad:
- if (j->prog) {
- free(j->prog);
- }
- free(j);
-
- return NULL;
-}
-
-#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__
-job_t
-job_new_alias(jobmgr_t jm, job_t src)
-{
- job_t j = NULL;
- if (job_find(jm, src->label)) {
- errno = EEXIST;
- } else {
- j = calloc(1, sizeof(struct job_s) + strlen(src->label) + 1);
- if (jobmgr_assumes(jm, j != NULL)) {
- strcpy((char *)j->label, src->label);
- LIST_INSERT_HEAD(&jm->jobs, j, sle);
- LIST_INSERT_HEAD(&jm->label_hash[hash_label(j->label)], j, label_hash_sle);
- /* Bad jump address. The kqueue callback for aliases should never be
- * invoked.
- */
- j->kqjob_callback = (kq_callback)0xfa1afe1;
- j->alias = src;
- j->mgr = jm;
-
- struct machservice *msi = NULL;
- SLIST_FOREACH(msi, &src->machservices, sle) {
- if (!machservice_new_alias(j, msi)) {
- jobmgr_log(jm, LOG_ERR, "Failed to alias job: %s", src->label);
- errno = EINVAL;
- job_remove(j);
- j = NULL;
- break;
- }
- }
- }
-
- if (j) {
- job_log(j, LOG_DEBUG, "Aliased service into domain: %s", jm->name);
- }
- }
-
- return j;
-}
-#endif /* __LAUNCH_DISABLE_XPC_SUPPORT__ */
-
-job_t
-job_import(launch_data_t pload)
-{
- job_t j = jobmgr_import2(root_jobmgr, pload);
-
- if (unlikely(j == NULL)) {
- return NULL;
- }
-
- /* Since jobs are effectively stalled until they get security sessions assigned
- * to them, we may wish to reconsider this behavior of calling the job "enabled"
- * as far as other jobs with the OtherJobEnabled KeepAlive criterion set.
- */
- job_dispatch_curious_jobs(j);
- return job_dispatch(j, false);
-}
-
-launch_data_t
-job_import_bulk(launch_data_t pload)
-{
- launch_data_t resp = launch_data_alloc(LAUNCH_DATA_ARRAY);
- job_t *ja;
- size_t i, c = launch_data_array_get_count(pload);
-
- ja = alloca(c * sizeof(job_t));
-
- for (i = 0; i < c; i++) {
- if ((likely(ja[i] = jobmgr_import2(root_jobmgr, launch_data_array_get_index(pload, i)))) && errno != ENEEDAUTH) {
- errno = 0;
- }
- launch_data_array_set_index(resp, launch_data_new_errno(errno), i);
- }
-
- for (i = 0; i < c; i++) {
- if (likely(ja[i])) {
- job_dispatch_curious_jobs(ja[i]);
- job_dispatch(ja[i], false);
- }
- }
-
- return resp;
-}
-
-void
-job_import_bool(job_t j, const char *key, bool value)
-{
- bool found_key = false;
-
- switch (key[0]) {
- case 'a':
- case 'A':
- if (strcasecmp(key, LAUNCH_JOBKEY_ABANDONPROCESSGROUP) == 0) {
- j->abandon_pg = value;
- found_key = true;
- }
- break;
- case 'b':
- case 'B':
- if (strcasecmp(key, LAUNCH_JOBKEY_BEGINTRANSACTIONATSHUTDOWN) == 0) {
- j->dirty_at_shutdown = value;
- found_key = true;
- }
- break;
- case 'k':
- case 'K':
- if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE) == 0) {
- j->ondemand = !value;
- found_key = true;
- }
- break;
- case 'o':
- case 'O':
- if (strcasecmp(key, LAUNCH_JOBKEY_ONDEMAND) == 0) {
- j->ondemand = value;
- found_key = true;
- }
- break;
- case 'd':
- case 'D':
- if (strcasecmp(key, LAUNCH_JOBKEY_DEBUG) == 0) {
- j->debug = value;
- found_key = true;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_DISABLED) == 0) {
- (void)job_assumes(j, !value);
- found_key = true;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_DISABLEASLR) == 0) {
- j->disable_aslr = value;
- found_key = true;
- }
- break;
- case 'h':
- case 'H':
- if (strcasecmp(key, LAUNCH_JOBKEY_HOPEFULLYEXITSLAST) == 0) {
- job_log(j, LOG_INFO, "%s has been deprecated. Please use the new %s key instead and add EnableTransactions to your launchd.plist.", LAUNCH_JOBKEY_HOPEFULLYEXITSLAST, LAUNCH_JOBKEY_BEGINTRANSACTIONATSHUTDOWN);
- j->dirty_at_shutdown = value;
- found_key = true;
- }
- break;
- case 's':
- case 'S':
- if (strcasecmp(key, LAUNCH_JOBKEY_SESSIONCREATE) == 0) {
- j->session_create = value;
- found_key = true;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_STARTONMOUNT) == 0) {
- j->start_on_mount = value;
- found_key = true;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_SERVICEIPC) == 0) {
- /* this only does something on Mac OS X 10.4 "Tiger" */
- found_key = true;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_SHUTDOWNMONITOR) == 0) {
- if (_s_shutdown_monitor) {
- job_log(j, LOG_ERR, "Only one job may monitor shutdown.");
- } else {
- j->shutdown_monitor = true;
- _s_shutdown_monitor = j;
- }
- found_key = true;
- }
- break;
- case 'l':
- case 'L':
- if (strcasecmp(key, LAUNCH_JOBKEY_LOWPRIORITYIO) == 0) {
- j->low_pri_io = value;
- found_key = true;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_LAUNCHONLYONCE) == 0) {
- j->only_once = value;
- found_key = true;
- }
- break;
- case 'm':
- case 'M':
- if (strcasecmp(key, LAUNCH_JOBKEY_MACHEXCEPTIONHANDLER) == 0) {
- j->internal_exc_handler = value;
- found_key = true;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_MULTIPLEINSTANCES) == 0) {
- j->multiple_instances = value;
- found_key = true;
- }
- break;
- case 'i':
- case 'I':
- if (strcasecmp(key, LAUNCH_JOBKEY_INITGROUPS) == 0) {
- if (getuid() != 0) {
- job_log(j, LOG_WARNING, "Ignored this key: %s", key);
- return;
- }
- j->no_init_groups = !value;
- found_key = true;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_IGNOREPROCESSGROUPATSHUTDOWN) == 0) {
- j->ignore_pg_at_shutdown = value;
- found_key = true;
- }
- break;
- case 'r':
- case 'R':
- if (strcasecmp(key, LAUNCH_JOBKEY_RUNATLOAD) == 0) {
- if (value) {
- /* We don't want value == false to change j->start_pending */
- j->start_pending = true;
- }
- found_key = true;
- }
- break;
- case 'e':
- case 'E':
- if (strcasecmp(key, LAUNCH_JOBKEY_ENABLEGLOBBING) == 0) {
- j->globargv = value;
- found_key = true;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_ENABLETRANSACTIONS) == 0) {
- j->kill_via_shmem = value;
- found_key = true;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_ENTERKERNELDEBUGGERBEFOREKILL) == 0) {
- j->debug_before_kill = value;
- found_key = true;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_EMBEDDEDPRIVILEGEDISPENSATION) == 0) {
- if (!s_embedded_privileged_job) {
- j->embedded_special_privileges = value;
- s_embedded_privileged_job = j;
- } else {
- job_log(j, LOG_ERR, "Job tried to claim %s after it has already been claimed.", key);
- }
- found_key = true;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_EVENTMONITOR) == 0) {
- if (job_assumes(j, _s_event_monitor == NULL)) {
- j->event_monitor = value;
- if (value) {
- _s_event_monitor = j;
- }
- } else {
- job_log(j, LOG_NOTICE, "Job tried to steal event monitoring responsibility!");
- }
- found_key = true;
- }
- break;
- case 'w':
- case 'W':
- if (strcasecmp(key, LAUNCH_JOBKEY_WAITFORDEBUGGER) == 0) {
- j->wait4debugger = value;
- found_key = true;
- }
- break;
- case 'x':
- case 'X':
- if (strcasecmp(key, LAUNCH_JOBKEY_XPCDOMAINBOOTSTRAPPER) == 0) {
- if (pid1_magic) {
- if (_s_xpc_bootstrapper) {
- job_log(j, LOG_ERR, "This job tried to steal the XPC domain bootstrapper property from the following job: %s", _s_xpc_bootstrapper->label);
- } else {
- _s_xpc_bootstrapper = j;
- j->xpc_bootstrapper = value;
- }
- } else {
- job_log(j, LOG_ERR, "Non-daemon tried to claim XPC bootstrapper property.");
- }
- }
- found_key = true;
- break;
- default:
- break;
- }
-
- if (unlikely(!found_key)) {
- job_log(j, LOG_WARNING, "Unknown key for boolean: %s", key);
- }
-}
-
-void
-job_import_string(job_t j, const char *key, const char *value)
-{
- char **where2put = NULL;
-
- switch (key[0]) {
- case 'm':
- case 'M':
- if (strcasecmp(key, LAUNCH_JOBKEY_MACHEXCEPTIONHANDLER) == 0) {
- where2put = &j->alt_exc_handler;
- }
- break;
- case 'p':
- case 'P':
- if (strcasecmp(key, LAUNCH_JOBKEY_PROGRAM) == 0) {
- return;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_POSIXSPAWNTYPE) == 0) {
- if (strcasecmp(value, LAUNCH_KEY_POSIXSPAWNTYPE_TALAPP) == 0) {
- j->pstype = POSIX_SPAWN_OSX_TALAPP_START;
- } else if (strcasecmp(value, LAUNCH_KEY_POSIXSPAWNTYPE_WIDGET) == 0) {
- j->pstype = POSIX_SPAWN_OSX_WIDGET_START;
- }
-#if TARGET_OS_EMBEDDED
- else if (strcasecmp(value, LAUNCH_KEY_POSIXSPAWNTYPE_IOSAPP) == 0) {
- j->pstype = POSIX_SPAWN_IOS_APP_START;
- }
-#endif /* TARGET_OS_EMBEDDED */
- else {
- job_log(j, LOG_ERR, "Unknown value for key %s: %s", key, value);
- }
- return;
- }
- break;
- case 'l':
- case 'L':
- if (strcasecmp(key, LAUNCH_JOBKEY_LABEL) == 0) {
- return;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOHOSTS) == 0) {
- return;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADFROMHOSTS) == 0) {
- return;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE) == 0) {
- return;
- }
- break;
- case 'r':
- case 'R':
- if (strcasecmp(key, LAUNCH_JOBKEY_ROOTDIRECTORY) == 0) {
- if (getuid() != 0) {
- job_log(j, LOG_WARNING, "Ignored this key: %s", key);
- return;
- }
- where2put = &j->rootdir;
- }
- break;
- case 'w':
- case 'W':
- if (strcasecmp(key, LAUNCH_JOBKEY_WORKINGDIRECTORY) == 0) {
- where2put = &j->workingdir;
- }
- break;
- case 'u':
- case 'U':
- if (strcasecmp(key, LAUNCH_JOBKEY_USERNAME) == 0) {
- if (getuid() != 0) {
- job_log(j, LOG_WARNING, "Ignored this key: %s", key);
- return;
- } else if (strcmp(value, "root") == 0) {
- return;
- }
- where2put = &j->username;
- }
- break;
- case 'g':
- case 'G':
- if (strcasecmp(key, LAUNCH_JOBKEY_GROUPNAME) == 0) {
- if (getuid() != 0) {
- job_log(j, LOG_WARNING, "Ignored this key: %s", key);
- return;
- } else if (strcmp(value, "wheel") == 0) {
- return;
- }
- where2put = &j->groupname;
- }
- break;
- case 's':
- case 'S':
- if (strcasecmp(key, LAUNCH_JOBKEY_STANDARDOUTPATH) == 0) {
- where2put = &j->stdoutpath;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_STANDARDERRORPATH) == 0) {
- where2put = &j->stderrpath;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_STANDARDINPATH) == 0) {
- where2put = &j->stdinpath;
- j->stdin_fd = _fd(open(value, O_RDONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, DEFFILEMODE));
- if (job_assumes(j, j->stdin_fd != -1)) {
- /* open() should not block, but regular IO by the job should */
- (void)job_assumes(j, fcntl(j->stdin_fd, F_SETFL, 0) != -1);
- /* XXX -- EV_CLEAR should make named pipes happy? */
- (void)job_assumes(j, kevent_mod(j->stdin_fd, EVFILT_READ, EV_ADD|EV_CLEAR, 0, 0, j) != -1);
- } else {
- j->stdin_fd = 0;
- }
-#if HAVE_SANDBOX
- } else if (strcasecmp(key, LAUNCH_JOBKEY_SANDBOXPROFILE) == 0) {
- where2put = &j->seatbelt_profile;
-#endif
- }
- break;
- case 'X':
- case 'x':
- if (strcasecmp(key, LAUNCH_JOBKEY_XPCDOMAIN) == 0) {
- return;
- }
- break;
- default:
- job_log(j, LOG_WARNING, "Unknown key for string: %s", key);
- break;
- }
-
- if (likely(where2put)) {
- (void)job_assumes(j, (*where2put = strdup(value)) != NULL);
- } else {
- /* See rdar://problem/5496612. These two are okay. */
- if (strncmp(key, "SHAuthorizationRight", sizeof("SHAuthorizationRight")) != 0 && strncmp(key, "ServiceDescription", sizeof("ServiceDescription")) != 0) {
- job_log(j, LOG_WARNING, "Unknown key: %s", key);
- }
- }
-}
-
-void
-job_import_integer(job_t j, const char *key, long long value)
-{
- switch (key[0]) {
- case 'e':
- case 'E':
- if (strcasecmp(key, LAUNCH_JOBKEY_EXITTIMEOUT) == 0) {
- if (unlikely(value < 0)) {
- job_log(j, LOG_WARNING, "%s less than zero. Ignoring.", LAUNCH_JOBKEY_EXITTIMEOUT);
- } else if (unlikely(value > UINT32_MAX)) {
- job_log(j, LOG_WARNING, "%s is too large. Ignoring.", LAUNCH_JOBKEY_EXITTIMEOUT);
- } else {
- j->exit_timeout = (typeof(j->exit_timeout)) value;
- }
- } else if (strcasecmp(key, LAUNCH_JOBKEY_EMBEDDEDMAINTHREADPRIORITY) == 0) {
- j->main_thread_priority = value;
- }
- break;
- case 'j':
- case 'J':
- if (strcasecmp(key, LAUNCH_JOBKEY_JETSAMPRIORITY) == 0) {
- job_log(j, LOG_WARNING | LOG_CONSOLE, "Please change the JetsamPriority key to be in a dictionary named JetsamProperties.");
-
- launch_data_t pri = launch_data_new_integer(value);
- if (job_assumes(j, pri != NULL)) {
- jetsam_property_setup(pri, LAUNCH_JOBKEY_JETSAMPRIORITY, j);
- launch_data_free(pri);
- }
- }
- case 'n':
- case 'N':
- if (strcasecmp(key, LAUNCH_JOBKEY_NICE) == 0) {
- if (unlikely(value < PRIO_MIN)) {
- job_log(j, LOG_WARNING, "%s less than %d. Ignoring.", LAUNCH_JOBKEY_NICE, PRIO_MIN);
- } else if (unlikely(value > PRIO_MAX)) {
- job_log(j, LOG_WARNING, "%s is greater than %d. Ignoring.", LAUNCH_JOBKEY_NICE, PRIO_MAX);
- } else {
- j->nice = (typeof(j->nice)) value;
- j->setnice = true;
- }
- }
- break;
- case 't':
- case 'T':
- if (strcasecmp(key, LAUNCH_JOBKEY_TIMEOUT) == 0) {
- if (unlikely(value < 0)) {
- job_log(j, LOG_WARNING, "%s less than zero. Ignoring.", LAUNCH_JOBKEY_TIMEOUT);
- } else if (unlikely(value > UINT32_MAX)) {
- job_log(j, LOG_WARNING, "%s is too large. Ignoring.", LAUNCH_JOBKEY_TIMEOUT);
- } else {
- j->timeout = (typeof(j->timeout)) value;
- }
- } else if (strcasecmp(key, LAUNCH_JOBKEY_THROTTLEINTERVAL) == 0) {
- if (value < 0) {
- job_log(j, LOG_WARNING, "%s less than zero. Ignoring.", LAUNCH_JOBKEY_THROTTLEINTERVAL);
- } else if (value > UINT32_MAX) {
- job_log(j, LOG_WARNING, "%s is too large. Ignoring.", LAUNCH_JOBKEY_THROTTLEINTERVAL);
- } else {
- j->min_run_time = (typeof(j->min_run_time)) value;
- }
- }
- break;
- case 'u':
- case 'U':
- if (strcasecmp(key, LAUNCH_JOBKEY_UMASK) == 0) {
- j->mask = value;
- j->setmask = true;
- }
- break;
- case 's':
- case 'S':
- if (strcasecmp(key, LAUNCH_JOBKEY_STARTINTERVAL) == 0) {
- if (unlikely(value <= 0)) {
- job_log(j, LOG_WARNING, "%s is not greater than zero. Ignoring.", LAUNCH_JOBKEY_STARTINTERVAL);
- } else if (unlikely(value > UINT32_MAX)) {
- job_log(j, LOG_WARNING, "%s is too large. Ignoring.", LAUNCH_JOBKEY_STARTINTERVAL);
- } else {
- runtime_add_weak_ref();
- j->start_interval = (typeof(j->start_interval)) value;
-
- (void)job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, j->start_interval, j) != -1);
- }
-#if HAVE_SANDBOX
- } else if (strcasecmp(key, LAUNCH_JOBKEY_SANDBOXFLAGS) == 0) {
- j->seatbelt_flags = value;
-#endif
- }
-
- break;
- default:
- job_log(j, LOG_WARNING, "Unknown key for integer: %s", key);
- break;
- }
-}
-
-void
-job_import_opaque(job_t j __attribute__((unused)),
- const char *key, launch_data_t value __attribute__((unused)))
-{
- switch (key[0]) {
- case 'q':
- case 'Q':
-#if HAVE_QUARANTINE
- if (strcasecmp(key, LAUNCH_JOBKEY_QUARANTINEDATA) == 0) {
- size_t tmpsz = launch_data_get_opaque_size(value);
-
- if (job_assumes(j, j->quarantine_data = malloc(tmpsz))) {
- memcpy(j->quarantine_data, launch_data_get_opaque(value), tmpsz);
- j->quarantine_data_sz = tmpsz;
- }
- }
-#endif
- case 's':
- case 'S':
- if (strcasecmp(key, LAUNCH_JOBKEY_SECURITYSESSIONUUID) == 0) {
- size_t tmpsz = launch_data_get_opaque_size(value);
- if (job_assumes(j, tmpsz == sizeof(uuid_t))) {
- memcpy(j->expected_audit_uuid, launch_data_get_opaque(value), sizeof(uuid_t));
- }
- }
- break;
- default:
- break;
- }
-}
-
-static void
-policy_setup(launch_data_t obj, const char *key, void *context)
-{
- job_t j = context;
- bool found_key = false;
-
- switch (key[0]) {
- case 'd':
- case 'D':
- if (strcasecmp(key, LAUNCH_JOBPOLICY_DENYCREATINGOTHERJOBS) == 0) {
- j->deny_job_creation = launch_data_get_bool(obj);
- found_key = true;
- }
- break;
- default:
- break;
- }
-
- if (unlikely(!found_key)) {
- job_log(j, LOG_WARNING, "Unknown policy: %s", key);
- }
-}
-
-void
-job_import_dictionary(job_t j, const char *key, launch_data_t value)
-{
- launch_data_t tmp;
-
- switch (key[0]) {
- case 'p':
- case 'P':
- if (strcasecmp(key, LAUNCH_JOBKEY_POLICIES) == 0) {
- launch_data_dict_iterate(value, policy_setup, j);
- }
- break;
- case 'k':
- case 'K':
- if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE) == 0) {
- launch_data_dict_iterate(value, semaphoreitem_setup, j);
- }
- break;
- case 'i':
- case 'I':
- if (strcasecmp(key, LAUNCH_JOBKEY_INETDCOMPATIBILITY) == 0) {
- j->inetcompat = true;
- j->abandon_pg = true;
- if ((tmp = launch_data_dict_lookup(value, LAUNCH_JOBINETDCOMPATIBILITY_WAIT))) {
- j->inetcompat_wait = launch_data_get_bool(tmp);
- }
- }
- break;
- case 'j':
- case 'J':
- if (strcasecmp(key, LAUNCH_JOBKEY_JETSAMPROPERTIES) == 0) {
- launch_data_dict_iterate(value, (void (*)(launch_data_t, const char *, void *))jetsam_property_setup, j);
- }
- case 'e':
- case 'E':
- if (strcasecmp(key, LAUNCH_JOBKEY_ENVIRONMENTVARIABLES) == 0) {
- launch_data_dict_iterate(value, envitem_setup, j);
- }
- break;
- case 'u':
- case 'U':
- if (strcasecmp(key, LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES) == 0) {
- j->importing_global_env = true;
- launch_data_dict_iterate(value, envitem_setup, j);
- j->importing_global_env = false;
- }
- break;
- case 's':
- case 'S':
- if (strcasecmp(key, LAUNCH_JOBKEY_SOCKETS) == 0) {
- launch_data_dict_iterate(value, socketgroup_setup, j);
- } else if (strcasecmp(key, LAUNCH_JOBKEY_STARTCALENDARINTERVAL) == 0) {
- calendarinterval_new_from_obj(j, value);
- } else if (strcasecmp(key, LAUNCH_JOBKEY_SOFTRESOURCELIMITS) == 0) {
- launch_data_dict_iterate(value, limititem_setup, j);
-#if HAVE_SANDBOX
- } else if (strcasecmp(key, LAUNCH_JOBKEY_SANDBOXFLAGS) == 0) {
- launch_data_dict_iterate(value, seatbelt_setup_flags, j);
-#endif
- }
- break;
- case 'h':
- case 'H':
- if (strcasecmp(key, LAUNCH_JOBKEY_HARDRESOURCELIMITS) == 0) {
- j->importing_hard_limits = true;
- launch_data_dict_iterate(value, limititem_setup, j);
- j->importing_hard_limits = false;
- }
- break;
- case 'm':
- case 'M':
- if (strcasecmp(key, LAUNCH_JOBKEY_MACHSERVICES) == 0) {
- launch_data_dict_iterate(value, machservice_setup, j);
- }
- break;
- case 'l':
- case 'L':
- if (strcasecmp(key, LAUNCH_JOBKEY_LAUNCHEVENTS) == 0) {
- launch_data_dict_iterate(value, eventsystem_setup, j);
- } else {
- if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOHARDWARE) == 0) {
- return;
- }
- if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADFROMHARDWARE) == 0) {
- return;
- }
- }
- break;
- default:
- job_log(j, LOG_WARNING, "Unknown key for dictionary: %s", key);
- break;
- }
-}
-
-void
-job_import_array(job_t j, const char *key, launch_data_t value)
-{
- size_t i, value_cnt = launch_data_array_get_count(value);
- const char *str;
-
- switch (key[0]) {
- case 'p':
- case 'P':
- if (strcasecmp(key, LAUNCH_JOBKEY_PROGRAMARGUMENTS) == 0) {
- return;
- }
- break;
- case 'l':
- case 'L':
- if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOHOSTS) == 0) {
- return;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADFROMHOSTS) == 0) {
- return;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE) == 0) {
- job_log(j, LOG_NOTICE, "launchctl should have transformed the \"%s\" array to a string", LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE);
- return;
- }
- break;
- case 'q':
- case 'Q':
- if (strcasecmp(key, LAUNCH_JOBKEY_QUEUEDIRECTORIES) == 0) {
- for (i = 0; i < value_cnt; i++) {
- str = launch_data_get_string(launch_data_array_get_index(value, i));
- if (job_assumes(j, str != NULL)) {
- semaphoreitem_new(j, DIR_NOT_EMPTY, str);
- }
- }
-
- }
- break;
- case 'w':
- case 'W':
- if (strcasecmp(key, LAUNCH_JOBKEY_WATCHPATHS) == 0) {
- for (i = 0; i < value_cnt; i++) {
- str = launch_data_get_string(launch_data_array_get_index(value, i));
- if (job_assumes(j, str != NULL)) {
- semaphoreitem_new(j, PATH_CHANGES, str);
- }
- }
- }
- break;
- case 'b':
- case 'B':
- if (strcasecmp(key, LAUNCH_JOBKEY_BONJOURFDS) == 0) {
- socketgroup_setup(value, LAUNCH_JOBKEY_BONJOURFDS, j);
- } else if (strcasecmp(key, LAUNCH_JOBKEY_BINARYORDERPREFERENCE) == 0) {
- if (job_assumes(j, j->j_binpref = malloc(value_cnt * sizeof(*j->j_binpref)))) {
- j->j_binpref_cnt = value_cnt;
- for (i = 0; i < value_cnt; i++) {
- j->j_binpref[i] = (cpu_type_t) launch_data_get_integer(launch_data_array_get_index(value, i));
- }
- }
- }
- break;
- case 's':
- case 'S':
- if (strcasecmp(key, LAUNCH_JOBKEY_STARTCALENDARINTERVAL) == 0) {
- for (i = 0; i < value_cnt; i++) {
- calendarinterval_new_from_obj(j, launch_data_array_get_index(value, i));
- }
- }
- break;
- default:
- job_log(j, LOG_WARNING, "Unknown key for array: %s", key);
- break;
- }
-}
-
-void
-job_import_keys(launch_data_t obj, const char *key, void *context)
-{
- job_t j = context;
- launch_data_type_t kind;
-
- if (!launchd_assumes(obj != NULL)) {
- return;
- }
-
- kind = launch_data_get_type(obj);
-
- switch (kind) {
- case LAUNCH_DATA_BOOL:
- job_import_bool(j, key, launch_data_get_bool(obj));
- break;
- case LAUNCH_DATA_STRING:
- job_import_string(j, key, launch_data_get_string(obj));
- break;
- case LAUNCH_DATA_INTEGER:
- job_import_integer(j, key, launch_data_get_integer(obj));
- break;
- case LAUNCH_DATA_DICTIONARY:
- job_import_dictionary(j, key, obj);
- break;
- case LAUNCH_DATA_ARRAY:
- job_import_array(j, key, obj);
- break;
- case LAUNCH_DATA_OPAQUE:
- job_import_opaque(j, key, obj);
- break;
- default:
- job_log(j, LOG_WARNING, "Unknown value type '%d' for key: %s", kind, key);
- break;
- }
-}
-
-job_t
-jobmgr_import2(jobmgr_t jm, launch_data_t pload)
-{
- launch_data_t tmp, ldpa;
- const char *label = NULL, *prog = NULL;
- const char **argv = NULL;
- job_t j;
-
- if (!jobmgr_assumes(jm, pload != NULL)) {
- errno = EINVAL;
- return NULL;
- }
-
- if (unlikely(launch_data_get_type(pload) != LAUNCH_DATA_DICTIONARY)) {
- errno = EINVAL;
- return NULL;
- }
-
- if (unlikely(!(tmp = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_LABEL)))) {
- errno = EINVAL;
- return NULL;
- }
-
- if (unlikely(launch_data_get_type(tmp) != LAUNCH_DATA_STRING)) {
- errno = EINVAL;
- return NULL;
- }
-
- if (unlikely(!(label = launch_data_get_string(tmp)))) {
- errno = EINVAL;
- return NULL;
- }
-
-#if TARGET_OS_EMBEDDED
- if (unlikely(g_embedded_privileged_action && s_embedded_privileged_job)) {
- if (unlikely(!(tmp = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_USERNAME)))) {
- errno = EPERM;
- return NULL;
- }
-
- const char *username = NULL;
- if (likely(tmp && launch_data_get_type(tmp) == LAUNCH_DATA_STRING)) {
- username = launch_data_get_string(tmp);
- } else {
- errno = EPERM;
- return NULL;
- }
-
- if (!jobmgr_assumes(jm, s_embedded_privileged_job->username != NULL && username != NULL)) {
- errno = EPERM;
- return NULL;
- }
-
- if (unlikely(strcmp(s_embedded_privileged_job->username, username) != 0)) {
- errno = EPERM;
- return NULL;
- }
- } else if (g_embedded_privileged_action) {
- errno = EINVAL;
- return NULL;
- }
-#endif
-
- if ((tmp = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAM)) &&
- (launch_data_get_type(tmp) == LAUNCH_DATA_STRING)) {
- prog = launch_data_get_string(tmp);
- }
-
- int argc = 0;
- if ((ldpa = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAMARGUMENTS))) {
- size_t i, c;
-
- if (launch_data_get_type(ldpa) != LAUNCH_DATA_ARRAY) {
- errno = EINVAL;
- return NULL;
- }
-
- c = launch_data_array_get_count(ldpa);
-
- argv = alloca((c + 1) * sizeof(char *));
-
- for (i = 0; i < c; i++) {
- tmp = launch_data_array_get_index(ldpa, i);
-
- if (launch_data_get_type(tmp) != LAUNCH_DATA_STRING) {
- errno = EINVAL;
- return NULL;
- }
-
- argv[i] = launch_data_get_string(tmp);
- }
-
- argv[i] = NULL;
- argc = i;
- }
-
- if (!prog && argc == 0) {
- jobmgr_log(jm, LOG_ERR, "Job specifies neither Program nor ProgramArguments: %s", label);
- errno = EINVAL;
- return NULL;
- }
-
- /* Find the requested session. You cannot load services into XPC domains in
- * this manner.
- */
- launch_data_t session = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE);
- if (session) {
- jobmgr_t jmt = NULL;
- if (launch_data_get_type(session) == LAUNCH_DATA_STRING) {
- jmt = jobmgr_find_by_name(jm, launch_data_get_string(session));
- if (!jmt) {
- jobmgr_log(jm, LOG_ERR, "Could not find requested session: %s", launch_data_get_string(session));
- } else {
- jm = jmt;
- }
- } else {
- jobmgr_log(jm, LOG_ERR, "Session type is not a string.");
- }
-
- if (!jmt) {
- errno = EINVAL;
- return NULL;
- }
- }
-
- /* For legacy reasons, we have a global hash of all labels in all job
- * managers. So rather than make it a global, we store it in the root job
- * manager. But for an XPC domain, we store a local hash of all services in
- * the domain.
- */
- jobmgr_t where2look = (jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) ? jm : root_jobmgr;
- if (unlikely((j = job_find(where2look, label)) != NULL)) {
- if (jm->xpc_singleton) {
- /* There can (and probably will be) multiple attemtps to import the
- * same XPC service from the same framework. This is okay. It's
- * treated as a singleton, so just return the existing one so that
- * it may be aliased into the requesting process' XPC domain.
- */
- return j;
- } else {
- /* If we're not a global XPC domain, then it's an error to try
- * importing the same job/service multiple times.
- */
- errno = EEXIST;
- return NULL;
- }
- } else if (unlikely(!jobmgr_label_test(where2look, label))) {
- errno = EINVAL;
- return NULL;
- }
- jobmgr_log(jm, LOG_DEBUG, "Importing %s.", label);
-
- if (likely(j = job_new(jm, label, prog, argv))) {
- launch_data_dict_iterate(pload, job_import_keys, j);
- if (!uuid_is_null(j->expected_audit_uuid)) {
- uuid_string_t uuid_str;
- uuid_unparse(j->expected_audit_uuid, uuid_str);
- job_log(j, LOG_DEBUG, "Imported job. Waiting for session for UUID %s.", uuid_str);
- LIST_INSERT_HEAD(&s_needing_sessions, j, needing_session_sle);
- errno = ENEEDAUTH;
- } else {
- job_log(j, LOG_DEBUG, "No security session specified.");
- j->asport = MACH_PORT_NULL;
- }
-
- if (j->event_monitor) {
- if (job_assumes(j, LIST_FIRST(&j->events) == NULL)) {
- struct machservice *msi = NULL;
- SLIST_FOREACH(msi, &j->machservices, sle) {
- if (msi->event_update_port) {
- break;
- }
- }
-
- if (job_assumes(j, msi != NULL)) {
- /* Create our send-once right so we can kick things off. */
- (void)job_assumes(j, launchd_mport_make_send_once(msi->port, &_s_event_update_port) == KERN_SUCCESS);
- if (!LIST_EMPTY(&_s_event_systems)) {
- eventsystem_ping();
- }
- }
- } else {
- job_log(j, LOG_ERR, "The event monitor job may not have a LaunchEvents dictionary.");
- job_remove(j);
- j = NULL;
- }
- }
- }
-
- return j;
-}
-
-bool
-jobmgr_label_test(jobmgr_t jm, const char *str)
-{
- const char *ptr;
-
- if (str[0] == '\0') {
- jobmgr_log(jm, LOG_ERR, "Empty job labels are not allowed");
- return false;
- }
-
- for (ptr = str; *ptr; ptr++) {
- if (iscntrl(*ptr)) {
- jobmgr_log(jm, LOG_ERR, "ASCII control characters are not allowed in job labels. Index: %td Value: 0x%hhx", ptr - str, *ptr);
- return false;
- }
- }
-
- if ((strncasecmp(str, "com.apple.launchd", strlen("com.apple.launchd")) == 0) ||
- (strncasecmp(str, "com.apple.launchctl", strlen("com.apple.launchctl")) == 0)) {
- jobmgr_log(jm, LOG_ERR, "Job labels are not allowed to use a reserved prefix: %s", str);
- return false;
- }
-
- return true;
-}
-
-job_t
-job_find(jobmgr_t jm, const char *label)
-{
- job_t ji;
-
- if (!jm) {
- jm = root_jobmgr;
- }
-
- LIST_FOREACH(ji, &jm->label_hash[hash_label(label)], label_hash_sle) {
- if (unlikely(ji->removal_pending || ji->mgr->shutting_down)) {
- continue; /* 5351245 and 5488633 respectively */
- }
-
- if (strcmp(ji->label, label) == 0) {
- return ji;
- }
- }
-
- errno = ESRCH;
- return NULL;
-}
-
-/* Should try and consolidate with job_mig_intran2() and jobmgr_find_by_pid(). */
-job_t
-jobmgr_find_by_pid_deep(jobmgr_t jm, pid_t p, bool anon_okay)
-{
- job_t ji = NULL;
- LIST_FOREACH(ji, &jm->active_jobs[ACTIVE_JOB_HASH(p)], pid_hash_sle) {
- if (ji->p == p && (!ji->anonymous || (ji->anonymous && anon_okay)) ) {
- return ji;
- }
- }
-
- jobmgr_t jmi = NULL;
- SLIST_FOREACH(jmi, &jm->submgrs, sle) {
- if ((ji = jobmgr_find_by_pid_deep(jmi, p, anon_okay))) {
- break;
- }
- }
-
- return ji;
-}
-
-job_t
-jobmgr_find_by_pid(jobmgr_t jm, pid_t p, bool create_anon)
-{
- job_t ji;
-
- LIST_FOREACH(ji, &jm->active_jobs[ACTIVE_JOB_HASH(p)], pid_hash_sle) {
- if (ji->p == p) {
- return ji;
- }
- }
-
- return create_anon ? job_new_anonymous(jm, p) : NULL;
-}
-
-job_t
-job_mig_intran2(jobmgr_t jm, mach_port_t mport, pid_t upid)
-{
- jobmgr_t jmi;
- job_t ji;
-
- if (jm->jm_port == mport) {
- return jobmgr_find_by_pid(jm, upid, true);
- }
-
- SLIST_FOREACH(jmi, &jm->submgrs, sle) {
- job_t jr;
-
- if ((jr = job_mig_intran2(jmi, mport, upid))) {
- return jr;
- }
- }
-
- LIST_FOREACH(ji, &jm->jobs, sle) {
- if (ji->j_port == mport) {
- return ji;
- }
- }
-
- return NULL;
-}
-
-job_t
-job_mig_intran(mach_port_t p)
-{
- struct ldcred *ldc = runtime_get_caller_creds();
- job_t jr;
-
- jr = job_mig_intran2(root_jobmgr, p, ldc->pid);
-
- if (!jobmgr_assumes(root_jobmgr, jr != NULL)) {
- struct proc_bsdshortinfo proc;
- if (proc_pidinfo(ldc->pid, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
- if (errno != ESRCH) {
- (void)jobmgr_assumes(root_jobmgr, errno == 0);
- } else {
- jobmgr_log(root_jobmgr, LOG_ERR, "%s() was confused by PID %u UID %u EUID %u Mach Port 0x%x: %s", __func__, ldc->pid, ldc->uid, ldc->euid, p, proc.pbsi_comm);
- }
- }
- }
-
- return jr;
-}
-
-job_t
-job_find_by_service_port(mach_port_t p)
-{
- struct machservice *ms;
-
- LIST_FOREACH(ms, &port_hash[HASH_PORT(p)], port_hash_sle) {
- if (ms->recv && (ms->port == p)) {
- return ms->job;
- }
- }
-
- return NULL;
-}
-
-void
-job_mig_destructor(job_t j)
-{
- /*
- * 5477111
- *
- * 'j' can be invalid at this point. We should fix this up after Leopard ships.
- */
-
- if (unlikely(j && (j != workaround_5477111) && j->unload_at_mig_return)) {
- job_log(j, LOG_NOTICE, "Unloading PID %u at MIG return.", j->p);
- job_remove(j);
- }
-
- workaround_5477111 = NULL;
-
- calendarinterval_sanity_check();
-}
-
-void
-job_export_all2(jobmgr_t jm, launch_data_t where)
-{
- jobmgr_t jmi;
- job_t ji;
-
- SLIST_FOREACH(jmi, &jm->submgrs, sle) {
- job_export_all2(jmi, where);
- }
-
- LIST_FOREACH(ji, &jm->jobs, sle) {
- launch_data_t tmp;
-
- if (jobmgr_assumes(jm, (tmp = job_export(ji)) != NULL)) {
- launch_data_dict_insert(where, tmp, ji->label);
- }
- }
-}
-
-launch_data_t
-job_export_all(void)
-{
- launch_data_t resp = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
-
- if (launchd_assumes(resp != NULL)) {
- job_export_all2(root_jobmgr, resp);
- }
-
- return resp;
-}
-
-void
-job_log_stray_pg(job_t j)
-{
- pid_t *pids = NULL;
- size_t len = sizeof(pid_t) * get_kern_max_proc();
- int i = 0, kp_cnt = 0;
-
- if (!do_apple_internal_logging) {
- return;
- }
-
- runtime_ktrace(RTKT_LAUNCHD_FINDING_STRAY_PG, j->p, 0, 0);
-
- if (!job_assumes(j, (pids = malloc(len)) != NULL)) {
- return;
- }
- if (!job_assumes(j, (kp_cnt = proc_listpgrppids(j->p, pids, len)) != -1)) {
- goto out;
- }
-
- for (i = 0; i < kp_cnt; i++) {
- pid_t p_i = pids[i];
- if (p_i == j->p) {
- continue;
- } else if (!job_assumes(j, p_i != 0 && p_i != 1)) {
- continue;
- }
-
- struct proc_bsdshortinfo proc;
- if (proc_pidinfo(p_i, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
- if (errno != ESRCH) {
- job_assumes(j, errno == 0);
- }
- continue;
- }
-
- pid_t pp_i = proc.pbsi_ppid;
- const char *z = (proc.pbsi_status == SZOMB) ? "zombie " : "";
- const char *n = proc.pbsi_comm;
-
- job_log(j, LOG_WARNING, "Stray %sprocess with PGID equal to this dead job: PID %u PPID %u PGID %u %s", z, p_i, pp_i, proc.pbsi_pgid, n);
- }
-
-out:
- free(pids);
-}
-
-void
-job_reap(job_t j)
-{
- struct rusage ru;
- int status;
-
- bool is_system_bootstrapper = j->is_bootstrapper && pid1_magic && !j->mgr->parentmgr;
-
- job_log(j, LOG_DEBUG, "Reaping");
-
- if (j->shmem) {
- (void)job_assumes(j, vm_deallocate(mach_task_self(), (vm_address_t)j->shmem, getpagesize()) == 0);
- j->shmem = NULL;
- }
-
- if (unlikely(j->weird_bootstrap)) {
- int64_t junk = 0;
- job_mig_swap_integer(j, VPROC_GSK_WEIRD_BOOTSTRAP, 0, 0, &junk);
- }
-
- if (j->log_redirect_fd && !j->legacy_LS_job) {
- job_log_stdouterr(j); /* one last chance */
-
- if (j->log_redirect_fd) {
- (void)job_assumes(j, runtime_close(j->log_redirect_fd) != -1);
- j->log_redirect_fd = 0;
- }
- }
-
- if (j->fork_fd) {
- (void)job_assumes(j, runtime_close(j->fork_fd) != -1);
- j->fork_fd = 0;
- }
-
- if (j->anonymous) {
- status = 0;
- memset(&ru, 0, sizeof(ru));
- } else {
- /*
- * The job is dead. While the PID/PGID is still known to be
- * valid, try to kill abandoned descendant processes.
- */
- job_log_stray_pg(j);
- if (!j->abandon_pg) {
- if (unlikely(runtime_killpg(j->p, SIGTERM) == -1 && errno != ESRCH)) {
-#ifdef __LP64__
- job_log(j, LOG_APPLEONLY, "Bug: 5487498");
-#else
- (void)job_assumes(j, false);
-#endif
- }
- }
-
- /* We have to work around one of two kernel bugs here. ptrace(3) may
- * have abducted the child away from us and reparented it to the tracing
- * process. If the process then exits, we still get NOTE_EXIT, but we
- * cannot reap it because the kernel may not have restored the true
- * parent/child relationship in time.
- *
- * See <rdar://problem/5020256>.
- *
- * The other bug is if the shutdown monitor has suspended a task and not
- * resumed it before exiting. In this case, the kernel will not clean up
- * after the shutdown monitor. It will, instead, leave the task
- * task suspended and not process any pending signals on the event loop
- * for the task.
- *
- * There are a variety of other kernel bugs that could prevent a process
- * from exiting, usually having to do with faulty hardware or talking to
- * misbehaving drivers that mark a thread as uninterruptible and
- * deadlock/hang before unmarking it as such. So we have to work around
- * that too.
- *
- * See <rdar://problem/9284889&9359725>.
- */
- if (j->workaround9359725) {
- job_log(j, LOG_NOTICE, "Simulated exit: <rdar://problem/9359725>");
- status = W_EXITCODE(-1, SIGSEGV);
- memset(&ru, 0, sizeof(ru));
- } else if (wait4(j->p, &status, 0, &ru) == -1) {
- job_log(j, LOG_NOTICE, "Assuming job exited: <rdar://problem/5020256>: %d: %s", errno, strerror(errno));
- status = W_EXITCODE(-1, SIGSEGV);
- memset(&ru, 0, sizeof(ru));
- }
- }
-
- if (j->exit_timeout) {
- kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
- }
-
- LIST_REMOVE(j, pid_hash_sle);
-
- if (j->sent_signal_time) {
- uint64_t td_sec, td_usec, td = runtime_get_nanoseconds_since(j->sent_signal_time);
-
- td_sec = td / NSEC_PER_SEC;
- td_usec = (td % NSEC_PER_SEC) / NSEC_PER_USEC;
-
- job_log(j, LOG_DEBUG, "Exited %llu.%06llu seconds after the first signal was sent", td_sec, td_usec);
- }
-
- timeradd(&ru.ru_utime, &j->ru.ru_utime, &j->ru.ru_utime);
- timeradd(&ru.ru_stime, &j->ru.ru_stime, &j->ru.ru_stime);
- j->ru.ru_maxrss += ru.ru_maxrss;
- j->ru.ru_ixrss += ru.ru_ixrss;
- j->ru.ru_idrss += ru.ru_idrss;
- j->ru.ru_isrss += ru.ru_isrss;
- j->ru.ru_minflt += ru.ru_minflt;
- j->ru.ru_majflt += ru.ru_majflt;
- j->ru.ru_nswap += ru.ru_nswap;
- j->ru.ru_inblock += ru.ru_inblock;
- j->ru.ru_oublock += ru.ru_oublock;
- j->ru.ru_msgsnd += ru.ru_msgsnd;
- j->ru.ru_msgrcv += ru.ru_msgrcv;
- j->ru.ru_nsignals += ru.ru_nsignals;
- j->ru.ru_nvcsw += ru.ru_nvcsw;
- j->ru.ru_nivcsw += ru.ru_nivcsw;
-
- if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
- int level = LOG_WARNING;
- if (!j->did_exec && (j->fail_cnt++ % LAUNCHD_LOG_FAILED_EXEC_FREQ) != 0) {
- level = LOG_DEBUG;
- }
-
- job_log(j, level, "Exited with code: %d", WEXITSTATUS(status));
- } else {
- j->fail_cnt = 0;
- }
-
- if (WIFSIGNALED(status)) {
- int s = WTERMSIG(status);
- if ((SIGKILL == s || SIGTERM == s) && !j->stopped) {
- job_log(j, LOG_NOTICE, "Exited: %s", strsignal(s));
- } else if (!j->stopped && !j->clean_kill) {
- switch (s) {
- /* Signals which indicate a crash. */
- case SIGILL:
- case SIGABRT:
- case SIGFPE:
- case SIGBUS:
- case SIGSEGV:
- case SIGSYS:
- /* If the kernel has posted NOTE_EXIT and the signal sent to the process was
- * SIGTRAP, assume that it's a crash.
- */
- case SIGTRAP:
- j->crashed = true;
- job_log(j, LOG_WARNING, "Job appears to have crashed: %s", strsignal(s));
- break;
- default:
- job_log(j, LOG_WARNING, "Exited abnormally: %s", strsignal(s));
- break;
- }
-
- if (is_system_bootstrapper && j->crashed) {
- job_log(j, LOG_ERR | LOG_CONSOLE, "The %s bootstrapper has crashed: %s", j->mgr->name, strsignal(s));
- }
- }
- }
-
- j->reaped = true;
-
- struct machservice *msi = NULL;
- if (j->crashed || !(j->did_exec || j->anonymous)) {
- SLIST_FOREACH(msi, &j->machservices, sle) {
- if (j->crashed && !msi->isActive && (msi->drain_one_on_crash || msi->drain_all_on_crash)) {
- machservice_drain_port(msi);
- }
-
- if (!j->did_exec && msi->reset && job_assumes(j, !msi->isActive)) {
- machservice_resetport(j, msi);
- }
- }
- }
-
- /* HACK: Essentially duplicating the logic directly above. But this has
- * gotten really hairy, and I don't want to try consolidating it right now.
- */
- if (j->xpc_service && !j->xpcproxy_did_exec) {
- job_log(j, LOG_ERR, "XPC Service could not exec(3). Resetting port.");
- SLIST_FOREACH(msi, &j->machservices, sle) {
- /* Drain the messages but do not reset the port. If xpcproxy could
- * not exec(3), then we don't want to continue trying, since there
- * is very likely a serious configuration error with the service.
- *
- * <rdar://problem/8986802>
- */
- machservice_resetport(j, msi);
- }
- }
-
- struct suspended_peruser *spi = NULL;
- while ((spi = LIST_FIRST(&j->suspended_perusers))) {
- job_log(j, LOG_ERR, "Job exited before resuming per-user launchd for UID %u. Will forcibly resume.", spi->j->mach_uid);
- spi->j->peruser_suspend_count--;
- if (spi->j->peruser_suspend_count == 0) {
- job_dispatch(spi->j, false);
- }
- LIST_REMOVE(spi, sle);
- free(spi);
- }
-
- j->last_exit_status = status;
-
- if (j->exit_status_dest) {
- errno = helper_downcall_wait(j->exit_status_dest, j->last_exit_status);
- if (errno && errno != MACH_SEND_INVALID_DEST) {
- (void)job_assumes(j, errno == 0);
- }
-
- j->exit_status_dest = MACH_PORT_NULL;
- }
-
- if (j->spawn_reply_port) {
- /* If the child never called exec(3), we must send a spawn() reply so
- * that the requestor can get exit status from it. If we fail to send
- * the reply for some reason, we have to deallocate the exit status port
- * ourselves.
- */
- kern_return_t kr = job_mig_spawn2_reply(j->spawn_reply_port, BOOTSTRAP_SUCCESS, j->p, j->exit_status_port);
- if (kr) {
- if (kr != MACH_SEND_INVALID_DEST) {
- errno = kr;
- (void)job_assumes(j, errno == KERN_SUCCESS);
- }
-
- (void)job_assumes(j, launchd_mport_close_recv(j->exit_status_port) == KERN_SUCCESS);
- }
-
- j->exit_status_port = MACH_PORT_NULL;
- j->spawn_reply_port = MACH_PORT_NULL;
- }
-
- if (j->anonymous) {
- total_anon_children--;
- if (j->holds_ref) {
- runtime_del_ref();
- }
- } else {
- runtime_del_ref();
- total_children--;
- }
-
- if (j->has_console) {
- g_wsp = 0;
- }
-
- if (j->shutdown_monitor) {
- job_log(j, LOG_NOTICE | LOG_CONSOLE, "Shutdown monitor has exited.");
- _s_shutdown_monitor = NULL;
- j->shutdown_monitor = false;
- }
-
- if (j->event_monitor && !j->mgr->shutting_down) {
- msi = NULL;
- SLIST_FOREACH(msi, &j->machservices, sle) {
- if (msi->event_update_port) {
- break;
- }
- }
- /* Only do this if we've gotten the port-destroyed notification already.
- * If we haven't yet, the port destruction handler will do this.
- */
- if (job_assumes(j, msi != NULL) && !msi->isActive) {
- if (_s_event_update_port == MACH_PORT_NULL) {
- (void)job_assumes(j, launchd_mport_make_send_once(msi->port, &_s_event_update_port) == KERN_SUCCESS);
- }
- eventsystem_ping();
- }
- }
-
- if (!j->anonymous) {
- j->mgr->normal_active_cnt--;
- }
- j->sent_signal_time = 0;
- j->sent_sigkill = false;
- j->clean_kill = false;
- j->sent_kill_via_shmem = false;
- j->lastlookup = NULL;
- j->lastlookup_gennum = 0;
- j->p = 0;
-}
-
-void
-jobmgr_dispatch_all(jobmgr_t jm, bool newmounthack)
-{
- jobmgr_t jmi, jmn;
- job_t ji, jn;
-
- if (jm->shutting_down) {
- return;
- }
-
- SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) {
- jobmgr_dispatch_all(jmi, newmounthack);
- }
-
- LIST_FOREACH_SAFE(ji, &jm->jobs, sle, jn) {
- if (newmounthack && ji->start_on_mount) {
- ji->start_pending = true;
- }
-
- job_dispatch(ji, false);
- }
-}
-
-void
-job_dispatch_curious_jobs(job_t j)
-{
- job_t ji = NULL, jt = NULL;
- SLIST_FOREACH_SAFE(ji, &s_curious_jobs, curious_jobs_sle, jt) {
- struct semaphoreitem *si = NULL;
- SLIST_FOREACH(si, &ji->semaphores, sle) {
- if (!(si->why == OTHER_JOB_ENABLED || si->why == OTHER_JOB_DISABLED)) {
- continue;
- }
-
- if (strcmp(si->what, j->label) == 0) {
- job_log(ji, LOG_DEBUG, "Dispatching out of interest in \"%s\".", j->label);
-
- if (!ji->removing) {
- job_dispatch(ji, false);
- } else {
- job_log(ji, LOG_NOTICE, "The following job is circularly dependent upon this one: %s", j->label);
- }
-
- /* ji could be removed here, so don't do anything with it or its semaphores
- * after this point.
- */
- break;
- }
- }
- }
-}
-
-job_t
-job_dispatch(job_t j, bool kickstart)
-{
- /* Don't dispatch a job if it has no audit session set. */
- if (!uuid_is_null(j->expected_audit_uuid)) {
- return NULL;
- }
- if (j->alias) {
- j = j->alias;
- }
-
-#if TARGET_OS_EMBEDDED
- if (g_embedded_privileged_action && s_embedded_privileged_job) {
- if (!job_assumes(j, s_embedded_privileged_job->username != NULL && j->username != NULL)) {
- errno = EPERM;
- return NULL;
- }
-
- if (strcmp(j->username, s_embedded_privileged_job->username) != 0) {
- errno = EPERM;
- return NULL;
- }
- } else if (g_embedded_privileged_action) {
- errno = EINVAL;
- return NULL;
- }
-#endif
-
- /*
- * The whole job removal logic needs to be consolidated. The fact that
- * a job can be removed from just about anywhere makes it easy to have
- * stale pointers left behind somewhere on the stack that might get
- * used after the deallocation. In particular, during job iteration.
- *
- * This is a classic example. The act of dispatching a job may delete it.
- */
- if (!job_active(j)) {
- if (job_useless(j)) {
- job_remove(j);
- return NULL;
- }
- if (unlikely(j->per_user && j->peruser_suspend_count > 0)) {
- return NULL;
- }
-
- if (kickstart || job_keepalive(j)) {
- job_log(j, LOG_DEBUG, "Starting job (kickstart = %s)", kickstart ? "true" : "false");
- job_start(j);
- } else {
- job_log(j, LOG_DEBUG, "Watching job (kickstart = %s)", kickstart ? "true" : "false");
- job_watch(j);
-
- /*
- * 5455720
- *
- * Path checking and monitoring is really racy right now.
- * We should clean this up post Leopard.
- */
- if (job_keepalive(j)) {
- job_start(j);
- }
- }
- } else {
- job_log(j, LOG_DEBUG, "Tried to dispatch an already active job (%s).", job_active(j));
- }
-
- return j;
-}
-
-void
-job_log_stdouterr2(job_t j, const char *msg, ...)
-{
- struct runtime_syslog_attr attr = { j->label, j->label, j->mgr->name, LOG_NOTICE, getuid(), j->p, j->p };
- va_list ap;
-
- va_start(ap, msg);
- runtime_vsyslog(&attr, msg, ap);
- va_end(ap);
-}
-
-void
-job_log_stdouterr(job_t j)
-{
- char *msg, *bufindex, *buf = malloc(BIG_PIPE_SIZE + 1);
- bool close_log_redir = false;
- ssize_t rsz;
-
- if (!job_assumes(j, buf != NULL)) {
- return;
- }
-
- bufindex = buf;
-
- rsz = read(j->log_redirect_fd, buf, BIG_PIPE_SIZE);
-
- if (unlikely(rsz == 0)) {
- job_log(j, LOG_DEBUG, "Standard out/error pipe closed");
- close_log_redir = true;
- } else if (rsz == -1) {
- if (!job_assumes(j, errno == EAGAIN)) {
- close_log_redir = true;
- }
- } else {
- buf[rsz] = '\0';
-
- while ((msg = strsep(&bufindex, "\n\r"))) {
- if (msg[0]) {
- job_log_stdouterr2(j, "%s", msg);
- }
- }
- }
-
- free(buf);
-
- if (unlikely(close_log_redir)) {
- (void)job_assumes(j, runtime_close(j->log_redirect_fd) != -1);
- j->log_redirect_fd = 0;
- job_dispatch(j, false);
- }
-}
-
-void
-job_kill(job_t j)
-{
- if (unlikely(!j->p || j->anonymous)) {
- return;
- }
-
- (void)job_assumes(j, runtime_kill(j->p, SIGKILL) != -1);
-
- j->sent_sigkill = true;
- (void)job_assumes(j, kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, LAUNCHD_SIGKILL_TIMER, j) != -1);
-
- job_log(j, LOG_DEBUG, "Sent SIGKILL signal");
-}
-
-void
-job_open_shutdown_transaction(job_t j)
-{
- if (j->kill_via_shmem) {
- if (j->shmem) {
- job_log(j, LOG_DEBUG, "Opening shutdown transaction for job.");
- (void)__sync_add_and_fetch(&j->shmem->vp_shmem_transaction_cnt, 1);
- } else {
- job_log(j, LOG_DEBUG, "Job wants to be dirty at shutdown, but it has not set up shared memory. Treating normally.");
- j->dirty_at_shutdown = false;
- }
- } else {
- job_log(j, LOG_DEBUG, "Job wants to be dirty at shutdown, but it is not Instant Off-compliant. Treating normally.");
- j->dirty_at_shutdown = false;
- }
-}
-
-void
-job_close_shutdown_transaction(job_t j)
-{
- if (j->dirty_at_shutdown) {
- job_log(j, LOG_DEBUG, "Closing shutdown transaction for job.");
- if (__sync_sub_and_fetch(&j->shmem->vp_shmem_transaction_cnt, 1) == -1) {
- job_log(j, LOG_DEBUG, "Job is now clean. Killing.");
- job_kill(j);
- }
- j->dirty_at_shutdown = false;
- }
-}
-
-void
-job_log_children_without_exec(job_t j)
-{
- pid_t *pids = NULL;
- size_t len = sizeof(pid_t) * get_kern_max_proc();
- int i = 0, kp_cnt = 0;
-
- if (!do_apple_internal_logging || j->anonymous || j->per_user) {
- return;
- }
-
- if (!job_assumes(j, (pids = malloc(len)) != NULL)) {
- return;
- }
- if (!job_assumes(j, (kp_cnt = proc_listchildpids(j->p, pids, len)) != -1)) {
- goto out;
- }
-
- for (i = 0; i < kp_cnt; i++) {
- struct proc_bsdshortinfo proc;
- if (proc_pidinfo(pids[i], PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
- if (errno != ESRCH) {
- job_assumes(j, errno == 0);
- }
- continue;
- }
- if (proc.pbsi_flags & P_EXEC) {
- continue;
- }
-
- job_log(j, LOG_DEBUG, "Called *fork(). Please switch to posix_spawn*(), pthreads or launchd. Child PID %u", pids[i]);
- }
-
-out:
- free(pids);
-}
-
-void
-job_cleanup_after_tracer(job_t j)
-{
- j->tracing_pid = 0;
- if (j->reap_after_trace) {
- job_log(j, LOG_DEBUG | LOG_CONSOLE, "Reaping job now that attached tracer is gone.");
- struct kevent kev;
- EV_SET(&kev, j->p, 0, 0, NOTE_EXIT, 0, 0);
-
- /* Fake a kevent to keep our logic consistent. */
- job_callback_proc(j, &kev);
-
- /* Normally, after getting a EVFILT_PROC event, we do garbage collection
- * on the root job manager. To make our fakery complete, we will do garbage
- * collection at the beginning of the next run loop cycle (after we're done
- * draining the current queue of kevents).
- */
- (void)job_assumes(j, kevent_mod((uintptr_t)&root_jobmgr->reboot_flags, EVFILT_TIMER, EV_ADD | EV_ONESHOT, NOTE_NSECONDS, 1, root_jobmgr) != -1);
- }
-}
-
-void
-job_callback_proc(job_t j, struct kevent *kev)
-{
- bool program_changed = false;
- int fflags = kev->fflags;
-
- job_log(j, LOG_DEBUG, "EVFILT_PROC event for job.");
- log_kevent_struct(LOG_DEBUG, kev, 0);
-
- if (fflags & NOTE_EXIT) {
- if (j->p == (pid_t)kev->ident && !j->anonymous) {
- /* Note that the third argument to proc_pidinfo() is a magic argument for
- * PROC_PIDT_SHORTBSDINFO. Specifically, passing 1 means "don't fail on a zombie
- * PID".
- */
- struct proc_bsdshortinfo proc;
- if (job_assumes(j, proc_pidinfo(j->p, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) > 0)) {
- if (!job_assumes(j, (pid_t)proc.pbsi_ppid == getpid())) {
- /* Someone has attached to the process with ptrace(). There's a race here.
- * If we determine that we are not the parent process and then fail to attach
- * a kevent to the parent PID (who is probably using ptrace()), we can take that as an
- * indication that the parent exited between sysctl(3) and kevent_mod(). The
- * reparenting of the PID should be atomic to us, so in that case, we reap the
- * job as normal.
- *
- * Otherwise, we wait for the death of the parent tracer and then reap, just as we
- * would if a job died while we were sampling it at shutdown.
- *
- * Note that we foolishly assume that in the process *tree* a node cannot be its
- * own parent. Apparently, that is not correct. If this is the case, we forsake
- * the process to its own devices. Let it reap itself.
- */
- if (!job_assumes(j, proc.pbsi_ppid != kev->ident)) {
- job_log(j, LOG_WARNING, "Job is its own parent and has (somehow) exited. Leaving it to waste away.");
- return;
- }
- if (job_assumes(j, kevent_mod(proc.pbsi_ppid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, j) != -1)) {
- j->tracing_pid = proc.pbsi_ppid;
- j->reap_after_trace = true;
- return;
- }
- }
- }
- } else if (!j->anonymous) {
- if (j->tracing_pid == (pid_t)kev->ident) {
- job_cleanup_after_tracer(j);
-
- return;
- } else if (j->tracing_pid && !j->reap_after_trace) {
- /* The job exited before our sample completed. */
- job_log(j, LOG_DEBUG | LOG_CONSOLE, "Job has exited. Will reap after tracing PID %i exits.", j->tracing_pid);
- j->reap_after_trace = true;
- return;
- }
- }
- }
-
- if (fflags & NOTE_EXEC) {
- program_changed = true;
-
- if (j->anonymous) {
- struct proc_bsdshortinfo proc;
- if (proc_pidinfo(j->p, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) > 0) {
- char newlabel[1000];
-
- snprintf(newlabel, sizeof(newlabel), "%p.anonymous.%s", j, proc.pbsi_comm);
-
- job_log(j, LOG_INFO, "Program changed. Updating the label to: %s", newlabel);
- j->lastlookup = NULL;
- j->lastlookup_gennum = 0;
-
- LIST_REMOVE(j, label_hash_sle);
- strcpy((char *)j->label, newlabel);
-
- jobmgr_t where2put = root_jobmgr;
- if (j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) {
- where2put = j->mgr;
- }
- LIST_INSERT_HEAD(&where2put->label_hash[hash_label(j->label)], j, label_hash_sle);
- } else if (errno != ESRCH) {
- job_assumes(j, errno == 0);
- }
- } else {
- if (j->spawn_reply_port) {
- errno = job_mig_spawn2_reply(j->spawn_reply_port, BOOTSTRAP_SUCCESS, j->p, j->exit_status_port);
- if (errno) {
- if (errno != MACH_SEND_INVALID_DEST) {
- (void)job_assumes(j, errno == KERN_SUCCESS);
- }
- (void)job_assumes(j, launchd_mport_close_recv(j->exit_status_port) == KERN_SUCCESS);
- }
-
- j->spawn_reply_port = MACH_PORT_NULL;
- j->exit_status_port = MACH_PORT_NULL;
- }
-
- if (j->xpc_service && j->did_exec) {
- j->xpcproxy_did_exec = true;
- }
-
- j->did_exec = true;
- job_log(j, LOG_DEBUG, "Program changed");
- }
- }
-
- if (fflags & NOTE_FORK) {
- job_log(j, LOG_DEBUG, "fork()ed%s", program_changed ? ". For this message only: We don't know whether this event happened before or after execve()." : "");
- job_log_children_without_exec(j);
- }
-
- if (fflags & NOTE_EXIT) {
- job_reap(j);
-
- if (j->anonymous) {
- job_remove(j);
- j = NULL;
- } else {
- j = job_dispatch(j, false);
- }
- }
-}
-
-void
-job_callback_timer(job_t j, void *ident)
-{
- if (j == ident) {
- job_log(j, LOG_DEBUG, "j == ident (%p)", ident);
- job_dispatch(j, true);
- } else if (&j->semaphores == ident) {
- job_log(j, LOG_DEBUG, "&j->semaphores == ident (%p)", ident);
- job_dispatch(j, false);
- } else if (&j->start_interval == ident) {
- job_log(j, LOG_DEBUG, "&j->start_interval == ident (%p)", ident);
- j->start_pending = true;
- job_dispatch(j, false);
- } else if (&j->exit_timeout == ident) {
- if (!job_assumes(j, j->p != 0)) {
- return;
- }
-
- if (j->sent_sigkill) {
- uint64_t td = runtime_get_nanoseconds_since(j->sent_signal_time);
-
- td /= NSEC_PER_SEC;
- td -= j->clean_kill ? 0 : j->exit_timeout;
-
- job_log(j, LOG_WARNING | LOG_CONSOLE, "Job has not died after being %skilled %llu seconds ago. Simulating exit.", j->clean_kill ? "cleanly " : "", td);
- j->workaround9359725 = true;
-
- if (g_trap_sigkill_bugs) {
- job_log(j, LOG_NOTICE | LOG_CONSOLE, "Trapping into kernel debugger. You can continue the machine after it has been debugged, and shutdown will proceed normally.");
- (void)job_assumes(j, host_reboot(mach_host_self(), HOST_REBOOT_DEBUGGER) == KERN_SUCCESS);
- }
-
- /* We've simulated the exit, so we have to cancel the kevent for
- * this job, otherwise we may get a kevent later down the road that
- * has a stale context pointer (if we've removed the job). Or worse,
- * it'll corrupt our data structures if the job still exists or the
- * allocation was recycled.
- *
- * If the failing process had a tracer attached to it, we need to
- * remove out NOTE_EXIT for that tracer too, otherwise the same
- * thing might happen.
- *
- * Note that, if we're not shutting down, this will result in a
- * zombie process just hanging around forever. But if the process
- * didn't exit after receiving SIGKILL, odds are it would've just
- * stuck around forever anyway.
- *
- * See <rdar://problem/9481630>.
- */
- kevent_mod((uintptr_t)j->p, EVFILT_PROC, EV_DELETE, 0, 0, NULL);
- if (j->tracing_pid) {
- kevent_mod((uintptr_t)j->tracing_pid, EVFILT_PROC, EV_DELETE, 0, 0, NULL);
- }
-
- struct kevent bogus_exit;
- EV_SET(&bogus_exit, j->p, EVFILT_PROC, 0, NOTE_EXIT, 0, 0);
- jobmgr_callback(j->mgr, &bogus_exit);
- } else {
- if (unlikely(j->debug_before_kill)) {
- job_log(j, LOG_NOTICE, "Exit timeout elapsed. Entering the kernel debugger");
- (void)job_assumes(j, host_reboot(mach_host_self(), HOST_REBOOT_DEBUGGER) == KERN_SUCCESS);
- }
-
- job_log(j, LOG_WARNING | LOG_CONSOLE, "Exit timeout elapsed (%u seconds). Killing", j->exit_timeout);
- job_kill(j);
- }
- } else {
- (void)job_assumes(j, false);
- }
-}
-
-void
-job_callback_read(job_t j, int ident)
-{
- if (ident == j->log_redirect_fd) {
- job_log_stdouterr(j);
- } else if (ident == j->stdin_fd) {
- job_dispatch(j, true);
- } else {
- socketgroup_callback(j);
- }
-}
-
-void
-jobmgr_reap_bulk(jobmgr_t jm, struct kevent *kev)
-{
- jobmgr_t jmi;
- job_t j;
-
- SLIST_FOREACH(jmi, &jm->submgrs, sle) {
- jobmgr_reap_bulk(jmi, kev);
- }
-
- if ((j = jobmgr_find_by_pid(jm, (pid_t)kev->ident, false))) {
- kev->udata = j;
- job_callback(j, kev);
- }
-}
-
-void
-jobmgr_callback(void *obj, struct kevent *kev)
-{
- jobmgr_t jm = obj;
- job_t ji;
-
- switch (kev->filter) {
- case EVFILT_PROC:
- jobmgr_reap_bulk(jm, kev);
- root_jobmgr = jobmgr_do_garbage_collection(root_jobmgr);
- break;
- case EVFILT_SIGNAL:
- switch (kev->ident) {
- case SIGTERM:
- jobmgr_log(jm, LOG_DEBUG, "Got SIGTERM. Shutting down.");
- return launchd_shutdown();
- case SIGUSR1:
- return calendarinterval_callback();
- case SIGUSR2:
- fake_shutdown_in_progress = true;
- runtime_setlogmask(LOG_UPTO(LOG_DEBUG));
-
- runtime_closelog(); /* HACK -- force 'start' time to be set */
-
- if (pid1_magic) {
- int64_t now = runtime_get_wall_time();
-
- jobmgr_log(jm, LOG_NOTICE, "Anticipatory shutdown began at: %lld.%06llu", now / USEC_PER_SEC, now % USEC_PER_SEC);
-
- LIST_FOREACH(ji, &root_jobmgr->jobs, sle) {
- if (ji->per_user && ji->p) {
- (void)job_assumes(ji, runtime_kill(ji->p, SIGUSR2) != -1);
- }
- }
- } else {
- jobmgr_log(jm, LOG_NOTICE, "Anticipatory per-user launchd shutdown");
- }
-
- return;
- default:
- return (void)jobmgr_assumes(jm, false);
- }
- break;
- case EVFILT_FS:
- if (kev->fflags & VQ_MOUNT) {
- jobmgr_dispatch_all(jm, true);
- }
- jobmgr_dispatch_all_semaphores(jm);
- break;
- case EVFILT_TIMER:
- if (kev->ident == (uintptr_t)&sorted_calendar_events) {
- calendarinterval_callback();
- } else if (kev->ident == (uintptr_t)jm) {
- jobmgr_log(jm, LOG_DEBUG, "Shutdown timer firing.");
- jobmgr_still_alive_with_check(jm);
- } else if (kev->ident == (uintptr_t)&jm->reboot_flags) {
- jobmgr_do_garbage_collection(jm);
- } else if (kev->ident == (uintptr_t)&g_runtime_busy_time) {
- jobmgr_log(jm, LOG_DEBUG, "Idle exit timer fired. Shutting down.");
- if (jobmgr_assumes(jm, runtime_busy_cnt == 0)) {
- return launchd_shutdown();
- }
- }
- break;
- case EVFILT_VNODE:
- if (kev->ident == (uintptr_t)s_no_hang_fd) {
- int _no_hang_fd = open("/dev/autofs_nowait", O_EVTONLY | O_NONBLOCK);
- if (unlikely(_no_hang_fd != -1)) {
- jobmgr_log(root_jobmgr, LOG_DEBUG, "/dev/autofs_nowait has appeared!");
- (void)jobmgr_assumes(root_jobmgr, kevent_mod((uintptr_t)s_no_hang_fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL) != -1);
- (void)jobmgr_assumes(root_jobmgr, runtime_close(s_no_hang_fd) != -1);
- s_no_hang_fd = _fd(_no_hang_fd);
- }
- } else if (pid1_magic && g_console && kev->ident == (uintptr_t)fileno(g_console)) {
- int cfd = -1;
- if (launchd_assumes((cfd = open(_PATH_CONSOLE, O_WRONLY | O_NOCTTY)) != -1)) {
- _fd(cfd);
- if (!launchd_assumes((g_console = fdopen(cfd, "w")) != NULL)) {
- close(cfd);
- }
- }
- }
- break;
- default:
- return (void)jobmgr_assumes(jm, false);
- }
-}
-
-void
-job_callback(void *obj, struct kevent *kev)
-{
- job_t j = obj;
-
- job_log(j, LOG_DEBUG, "Dispatching kevent callback.");
-
- switch (kev->filter) {
- case EVFILT_PROC:
- return job_callback_proc(j, kev);
- case EVFILT_TIMER:
- return job_callback_timer(j, (void *) kev->ident);
- case EVFILT_VNODE:
- return semaphoreitem_callback(j, kev);
- case EVFILT_READ:
- return job_callback_read(j, (int) kev->ident);
- case EVFILT_MACHPORT:
- return (void)job_dispatch(j, true);
- default:
- return (void)job_assumes(j, false);
- }
-}
-
-void
-job_start(job_t j)
-{
- uint64_t td;
- int spair[2];
- int execspair[2];
- int oepair[2];
- char nbuf[64];
- pid_t c;
- bool sipc = false;
- u_int proc_fflags = NOTE_EXIT|NOTE_FORK|NOTE_EXEC;
-
- if (!job_assumes(j, j->mgr != NULL)) {
- return;
- }
-
- if (unlikely(job_active(j))) {
- job_log(j, LOG_DEBUG, "Already started");
- return;
- }
-
- /*
- * Some users adjust the wall-clock and then expect software to not notice.
- * Therefore, launchd must use an absolute clock instead of the wall clock
- * wherever possible.
- */
- td = runtime_get_nanoseconds_since(j->start_time);
- td /= NSEC_PER_SEC;
-
- if (j->start_time && (td < j->min_run_time) && !j->legacy_mach_job && !j->inetcompat) {
- time_t respawn_delta = j->min_run_time - (uint32_t)td;
-
- /*
- * We technically should ref-count throttled jobs to prevent idle exit,
- * but we're not directly tracking the 'throttled' state at the moment.
- */
- int level = LOG_WARNING;
- if (!j->did_exec && ((j->fail_cnt - 1) % LAUNCHD_LOG_FAILED_EXEC_FREQ) != 0) {
- level = LOG_DEBUG;
- }
-
- job_log(j, level, "Throttling respawn: Will start in %ld seconds", respawn_delta);
- (void)job_assumes(j, kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, respawn_delta, j) != -1);
- job_ignore(j);
- return;
- }
-
- if (likely(!j->legacy_mach_job)) {
- sipc = ((!SLIST_EMPTY(&j->sockets) || !SLIST_EMPTY(&j->machservices)) && !j->deny_job_creation) || j->embedded_special_privileges;
- }
-
- if (sipc) {
- (void)job_assumes(j, socketpair(AF_UNIX, SOCK_STREAM, 0, spair) != -1);
- }
-
- (void)job_assumes(j, socketpair(AF_UNIX, SOCK_STREAM, 0, execspair) != -1);
-
- if (likely(!j->legacy_mach_job) && job_assumes(j, pipe(oepair) != -1)) {
- j->log_redirect_fd = _fd(oepair[0]);
- (void)job_assumes(j, fcntl(j->log_redirect_fd, F_SETFL, O_NONBLOCK) != -1);
- (void)job_assumes(j, kevent_mod(j->log_redirect_fd, EVFILT_READ, EV_ADD, 0, 0, j) != -1);
- }
-
- switch (c = runtime_fork(j->weird_bootstrap ? j->j_port : j->mgr->jm_port)) {
- case -1:
- job_log_error(j, LOG_ERR, "fork() failed, will try again in one second");
- (void)job_assumes(j, kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, 1, j) != -1);
- job_ignore(j);
-
- (void)job_assumes(j, runtime_close(execspair[0]) == 0);
- (void)job_assumes(j, runtime_close(execspair[1]) == 0);
- if (sipc) {
- (void)job_assumes(j, runtime_close(spair[0]) == 0);
- (void)job_assumes(j, runtime_close(spair[1]) == 0);
- }
- if (likely(!j->legacy_mach_job)) {
- (void)job_assumes(j, runtime_close(oepair[0]) != -1);
- (void)job_assumes(j, runtime_close(oepair[1]) != -1);
- j->log_redirect_fd = 0;
- }
- break;
- case 0:
- if (unlikely(_vproc_post_fork_ping())) {
- _exit(EXIT_FAILURE);
- }
- if (!j->legacy_mach_job) {
- (void)job_assumes(j, dup2(oepair[1], STDOUT_FILENO) != -1);
- (void)job_assumes(j, dup2(oepair[1], STDERR_FILENO) != -1);
- (void)job_assumes(j, runtime_close(oepair[1]) != -1);
- }
- (void)job_assumes(j, runtime_close(execspair[0]) == 0);
- /* wait for our parent to say they've attached a kevent to us */
- read(_fd(execspair[1]), &c, sizeof(c));
-
- if (sipc) {
- (void)job_assumes(j, runtime_close(spair[0]) == 0);
- snprintf(nbuf, sizeof(nbuf), "%d", spair[1]);
- setenv(LAUNCHD_TRUSTED_FD_ENV, nbuf, 1);
- }
- job_start_child(j);
- break;
- default:
- j->start_time = runtime_get_opaque_time();
-
- job_log(j, LOG_DEBUG, "Started as PID: %u", c);
-
- j->did_exec = false;
- j->xpcproxy_did_exec = false;
- j->checkedin = false;
- j->start_pending = false;
- j->reaped = false;
- j->crashed = false;
- j->stopped = false;
- if (j->needs_kickoff) {
- j->needs_kickoff = false;
-
- if (SLIST_EMPTY(&j->semaphores)) {
- j->ondemand = false;
- }
- }
-
- if (j->has_console) {
- g_wsp = c;
- }
-
- runtime_add_ref();
- total_children++;
- LIST_INSERT_HEAD(&j->mgr->active_jobs[ACTIVE_JOB_HASH(c)], j, pid_hash_sle);
-
- if (likely(!j->legacy_mach_job)) {
- (void)job_assumes(j, runtime_close(oepair[1]) != -1);
- }
- j->p = c;
-
- j->mgr->normal_active_cnt++;
- j->fork_fd = _fd(execspair[0]);
- (void)job_assumes(j, runtime_close(execspair[1]) == 0);
- if (sipc) {
- (void)job_assumes(j, runtime_close(spair[1]) == 0);
- ipc_open(_fd(spair[0]), j);
- }
- if (job_assumes(j, kevent_mod(c, EVFILT_PROC, EV_ADD, proc_fflags, 0, root_jobmgr ? root_jobmgr : j->mgr) != -1)) {
- job_ignore(j);
- } else {
- job_reap(j);
- }
-
- j->wait4debugger_oneshot = false;
-
- struct envitem *ei = NULL, *et = NULL;
- SLIST_FOREACH_SAFE(ei, &j->env, sle, et) {
- if (ei->one_shot) {
- SLIST_REMOVE(&j->env, ei, envitem, sle);
- }
- }
-
- if (likely(!j->stall_before_exec)) {
- job_uncork_fork(j);
- }
- break;
- }
-}
-
-void
-job_start_child(job_t j)
-{
- typeof(posix_spawn) *psf;
- const char *file2exec = "/usr/libexec/launchproxy";
- const char **argv;
- posix_spawnattr_t spattr;
- int gflags = GLOB_NOSORT|GLOB_NOCHECK|GLOB_TILDE|GLOB_DOOFFS;
- glob_t g;
- short spflags = POSIX_SPAWN_SETEXEC;
- size_t binpref_out_cnt = 0;
- size_t i;
-
- (void)job_assumes(j, posix_spawnattr_init(&spattr) == 0);
-
- job_setup_attributes(j);
-
- if (unlikely(j->argv && j->globargv)) {
- g.gl_offs = 1;
- for (i = 0; i < j->argc; i++) {
- if (i > 0) {
- gflags |= GLOB_APPEND;
- }
- if (glob(j->argv[i], gflags, NULL, &g) != 0) {
- job_log_error(j, LOG_ERR, "glob(\"%s\")", j->argv[i]);
- exit(EXIT_FAILURE);
- }
- }
- g.gl_pathv[0] = (char *)file2exec;
- argv = (const char **)g.gl_pathv;
- } else if (likely(j->argv)) {
- argv = alloca((j->argc + 2) * sizeof(char *));
- argv[0] = file2exec;
- for (i = 0; i < j->argc; i++) {
- argv[i + 1] = j->argv[i];
- }
- argv[i + 1] = NULL;
- } else {
- argv = alloca(3 * sizeof(char *));
- argv[0] = file2exec;
- argv[1] = j->prog;
- argv[2] = NULL;
- }
-
- if (likely(!j->inetcompat)) {
- argv++;
- }
-
- if (unlikely(j->wait4debugger || j->wait4debugger_oneshot)) {
- if (!j->legacy_LS_job) {
- job_log(j, LOG_WARNING, "Spawned and waiting for the debugger to attach before continuing...");
- }
- spflags |= POSIX_SPAWN_START_SUSPENDED;
- }
-
- if (unlikely(j->disable_aslr)) {
- spflags |= _POSIX_SPAWN_DISABLE_ASLR;
- }
- spflags |= j->pstype;
-
- (void)job_assumes(j, posix_spawnattr_setflags(&spattr, spflags) == 0);
-
- if (unlikely(j->j_binpref_cnt)) {
- (void)job_assumes(j, posix_spawnattr_setbinpref_np(&spattr, j->j_binpref_cnt, j->j_binpref, &binpref_out_cnt) == 0);
- (void)job_assumes(j, binpref_out_cnt == j->j_binpref_cnt);
- }
-
-#if HAVE_QUARANTINE
- if (j->quarantine_data) {
- qtn_proc_t qp;
-
- if (job_assumes(j, qp = qtn_proc_alloc())) {
- if (job_assumes(j, qtn_proc_init_with_data(qp, j->quarantine_data, j->quarantine_data_sz) == 0)) {
- (void)job_assumes(j, qtn_proc_apply_to_self(qp) == 0);
- }
- }
- }
-#endif
-
-#if HAVE_SANDBOX
- if (j->seatbelt_profile) {
- char *seatbelt_err_buf = NULL;
-
- if (!job_assumes(j, sandbox_init(j->seatbelt_profile, j->seatbelt_flags, &seatbelt_err_buf) != -1)) {
- if (seatbelt_err_buf) {
- job_log(j, LOG_ERR, "Sandbox failed to init: %s", seatbelt_err_buf);
- }
- goto out_bad;
- }
- }
-#endif
-
- psf = j->prog ? posix_spawn : posix_spawnp;
-
- if (likely(!j->inetcompat)) {
- file2exec = j->prog ? j->prog : argv[0];
- }
-
- errno = psf(NULL, file2exec, NULL, &spattr, (char *const *)argv, environ);
- if (errno != EBADARCH) {
- int level = LOG_ERR;
- if ((j->fail_cnt++ % LAUNCHD_LOG_FAILED_EXEC_FREQ) != 0) {
- level = LOG_DEBUG;
- }
- job_log_error(j, level, "posix_spawn(\"%s\", ...)", file2exec);
- errno = EXIT_FAILURE;
- }
-
-#if HAVE_SANDBOX
-out_bad:
-#endif
- _exit(errno);
-}
-
-void
-jobmgr_export_env_from_other_jobs(jobmgr_t jm, launch_data_t dict)
-{
- launch_data_t tmp;
- struct envitem *ei;
- job_t ji;
-
- if (jm->parentmgr) {
- jobmgr_export_env_from_other_jobs(jm->parentmgr, dict);
- } else {
- char **tmpenviron = environ;
- for (; *tmpenviron; tmpenviron++) {
- char envkey[1024];
- launch_data_t s = launch_data_alloc(LAUNCH_DATA_STRING);
- launch_data_set_string(s, strchr(*tmpenviron, '=') + 1);
- strncpy(envkey, *tmpenviron, sizeof(envkey));
- *(strchr(envkey, '=')) = '\0';
- launch_data_dict_insert(dict, s, envkey);
- }
- }
-
- LIST_FOREACH(ji, &jm->jobs, sle) {
- SLIST_FOREACH(ei, &ji->global_env, sle) {
- if ((tmp = launch_data_new_string(ei->value))) {
- launch_data_dict_insert(dict, tmp, ei->key);
- }
- }
- }
-}
-
-void
-jobmgr_setup_env_from_other_jobs(jobmgr_t jm)
-{
- struct envitem *ei;
- job_t ji;
-
- if (jm->parentmgr) {
- jobmgr_setup_env_from_other_jobs(jm->parentmgr);
- }
-
- LIST_FOREACH(ji, &jm->global_env_jobs, global_env_sle) {
- SLIST_FOREACH(ei, &ji->global_env, sle) {
- setenv(ei->key, ei->value, 1);
- }
- }
-}
-
-void
-job_log_pids_with_weird_uids(job_t j)
-{
- size_t len = sizeof(pid_t) * get_kern_max_proc();
- pid_t *pids = NULL;
- uid_t u = j->mach_uid;
- int i = 0, kp_cnt = 0;
-
- if (!do_apple_internal_logging) {
- return;
- }
-
- pids = malloc(len);
- if (!job_assumes(j, pids != NULL)) {
- return;
- }
-
- runtime_ktrace(RTKT_LAUNCHD_FINDING_WEIRD_UIDS, j->p, u, 0);
-
- /* libproc actually has some serious performance drawbacks when used over sysctl(3) in
- * scenarios like this. Whereas sysctl(3) can give us back all the kinfo_proc's in
- * one kernel call, libproc requires that we get a list of PIDs we're interested in
- * (in this case, all PIDs on the system) and then get a single proc_bsdshortinfo
- * struct back in a single call for each one.
- *
- * This kind of thing is also more inherently racy than sysctl(3). While sysctl(3)
- * returns a snapshot, it returns the whole shebang at once. Any PIDs given to us by
- * libproc could go stale before we call proc_pidinfo().
- *
- * Note that proc_list*() APIs return the number of PIDs given back, not the number
- * of bytes written to the buffer.
- */
- if (!job_assumes(j, (kp_cnt = proc_listallpids(pids, len)) != -1)) {
- goto out;
- }
-
- for (i = 0; i < kp_cnt; i++) {
- struct proc_bsdshortinfo proc;
- /* We perhaps should not log a bug here if we get ESRCH back, due to the race
- * detailed above.
- */
- if (proc_pidinfo(pids[i], PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
- if (errno != ESRCH) {
- job_assumes(j, errno == 0);
- }
- continue;
- }
-
- uid_t i_euid = proc.pbsi_uid;
- uid_t i_uid = proc.pbsi_ruid;
- uid_t i_svuid = proc.pbsi_svuid;
- pid_t i_pid = pids[i];
-
- if (i_euid != u && i_uid != u && i_svuid != u) {
- continue;
- }
-
- job_log(j, LOG_ERR, "PID %u \"%s\" has no account to back it! Real/effective/saved UIDs: %u/%u/%u", i_pid, proc.pbsi_comm, i_uid, i_euid, i_svuid);
-
-/* Temporarily disabled due to 5423935 and 4946119. */
-#if 0
- /* Ask the accountless process to exit. */
- (void)job_assumes(j, runtime_kill(i_pid, SIGTERM) != -1);
-#endif
- }
-
-out:
- free(pids);
-}
-
-static struct passwd *
-job_getpwnam(job_t j, const char *name)
-{
- /*
- * methodology for system daemons
- *
- * first lookup user record without any opendirectoryd interaction,
- * we don't know what interprocess dependencies might be in flight.
- * if that fails, we re-enable opendirectoryd interaction and
- * re-issue the lookup. We have to disable the libinfo L1 cache
- * otherwise libinfo will return the negative cache entry on the retry
- */
-
-#if !TARGET_OS_EMBEDDED
- struct passwd *pw = NULL;
-
- if (pid1_magic && j->mgr == root_jobmgr) {
- si_search_module_set_flags("ds", 1 /* SEARCH_MODULE_FLAG_DISABLED */);
- gL1CacheEnabled = false;
-
- pw = getpwnam(name);
-
- si_search_module_set_flags("ds", 0);
- }
-
- if (pw == NULL) {
- pw = getpwnam(name);
- }
-
- return pw;
-#else
- return getpwnam(name);
-#endif
-}
-
-static struct group *
-job_getgrnam(job_t j, const char *name)
-{
-#if !TARGET_OS_EMBEDDED
- struct group *gr = NULL;
-
- if (pid1_magic && j->mgr == root_jobmgr) {
- si_search_module_set_flags("ds", 1 /* SEARCH_MODULE_FLAG_DISABLED */);
- gL1CacheEnabled = false;
-
- gr = getgrnam(name);
-
- si_search_module_set_flags("ds", 0);
- }
-
- if (gr == NULL) {
- gr = getgrnam(name);
- }
-
- return gr;
-#else
-#pragma unused (j)
- return getgrnam(name);
-#endif
-}
-
-void
-job_postfork_test_user(job_t j)
-{
- /* This function is all about 5201578 */
-
- const char *home_env_var = getenv("HOME");
- const char *user_env_var = getenv("USER");
- const char *logname_env_var = getenv("LOGNAME");
- uid_t tmp_uid, local_uid = getuid();
- gid_t tmp_gid, local_gid = getgid();
- char shellpath[PATH_MAX];
- char homedir[PATH_MAX];
- char loginname[2000];
- struct passwd *pwe;
-
-
- if (!job_assumes(j, home_env_var && user_env_var && logname_env_var
- && strcmp(user_env_var, logname_env_var) == 0)) {
- goto out_bad;
- }
-
- if ((pwe = job_getpwnam(j, user_env_var)) == NULL) {
- job_log(j, LOG_ERR, "The account \"%s\" has been deleted out from under us!", user_env_var);
- goto out_bad;
- }
-
- /*
- * We must copy the results of getpw*().
- *
- * Why? Because subsequent API calls may call getpw*() as a part of
- * their implementation. Since getpw*() returns a [now thread scoped]
- * global, we must therefore cache the results before continuing.
- */
-
- tmp_uid = pwe->pw_uid;
- tmp_gid = pwe->pw_gid;
-
- strlcpy(shellpath, pwe->pw_shell, sizeof(shellpath));
- strlcpy(loginname, pwe->pw_name, sizeof(loginname));
- strlcpy(homedir, pwe->pw_dir, sizeof(homedir));
-
- if (strcmp(loginname, logname_env_var) != 0) {
- job_log(j, LOG_ERR, "The %s environmental variable changed out from under us!", "USER");
- goto out_bad;
- }
- if (strcmp(homedir, home_env_var) != 0) {
- job_log(j, LOG_ERR, "The %s environmental variable changed out from under us!", "HOME");
- goto out_bad;
- }
- if (local_uid != tmp_uid) {
- job_log(j, LOG_ERR, "The %cID of the account (%u) changed out from under us (%u)!",
- 'U', tmp_uid, local_uid);
- goto out_bad;
- }
- if (local_gid != tmp_gid) {
- job_log(j, LOG_ERR, "The %cID of the account (%u) changed out from under us (%u)!",
- 'G', tmp_gid, local_gid);
- goto out_bad;
- }
-
- return;
-out_bad:
-#if 0
- (void)job_assumes(j, runtime_kill(getppid(), SIGTERM) != -1);
- _exit(EXIT_FAILURE);
-#else
- job_log(j, LOG_WARNING, "In a future build of the OS, this error will be fatal.");
-#endif
-}
-
-void
-job_postfork_become_user(job_t j)
-{
- char loginname[2000];
- char tmpdirpath[PATH_MAX];
- char shellpath[PATH_MAX];
- char homedir[PATH_MAX];
- struct passwd *pwe;
- size_t r;
- gid_t desired_gid = -1;
- uid_t desired_uid = -1;
-
- if (getuid() != 0) {
- return job_postfork_test_user(j);
- }
-
- /*
- * I contend that having UID == 0 and GID != 0 is of dubious value.
- * Nevertheless, this used to work in Tiger. See: 5425348
- */
- if (j->groupname && !j->username) {
- j->username = "root";
- }
-
- if (j->username) {
- if ((pwe = job_getpwnam(j, j->username)) == NULL) {
- job_log(j, LOG_ERR, "getpwnam(\"%s\") failed", j->username);
- _exit(EXIT_FAILURE);
- }
- } else if (j->mach_uid) {
- if ((pwe = getpwuid(j->mach_uid)) == NULL) {
- job_log(j, LOG_ERR, "getpwuid(\"%u\") failed", j->mach_uid);
- job_log_pids_with_weird_uids(j);
- _exit(EXIT_FAILURE);
- }
- } else {
- return;
- }
-
- /*
- * We must copy the results of getpw*().
- *
- * Why? Because subsequent API calls may call getpw*() as a part of
- * their implementation. Since getpw*() returns a [now thread scoped]
- * global, we must therefore cache the results before continuing.
- */
-
- desired_uid = pwe->pw_uid;
- desired_gid = pwe->pw_gid;
-
- strlcpy(shellpath, pwe->pw_shell, sizeof(shellpath));
- strlcpy(loginname, pwe->pw_name, sizeof(loginname));
- strlcpy(homedir, pwe->pw_dir, sizeof(homedir));
-
- if (unlikely(pwe->pw_expire && time(NULL) >= pwe->pw_expire)) {
- job_log(j, LOG_ERR, "Expired account");
- _exit(EXIT_FAILURE);
- }
-
-
- if (unlikely(j->username && strcmp(j->username, loginname) != 0)) {
- job_log(j, LOG_WARNING, "Suspicious setup: User \"%s\" maps to user: %s", j->username, loginname);
- } else if (unlikely(j->mach_uid && (j->mach_uid != desired_uid))) {
- job_log(j, LOG_WARNING, "Suspicious setup: UID %u maps to UID %u", j->mach_uid, desired_uid);
- }
-
- if (j->groupname) {
- struct group *gre;
-
- if (unlikely((gre = job_getgrnam(j, j->groupname)) == NULL)) {
- job_log(j, LOG_ERR, "getgrnam(\"%s\") failed", j->groupname);
- _exit(EXIT_FAILURE);
- }
-
- desired_gid = gre->gr_gid;
- }
-
- if (!job_assumes(j, setlogin(loginname) != -1)) {
- _exit(EXIT_FAILURE);
- }
-
- if (!job_assumes(j, setgid(desired_gid) != -1)) {
- _exit(EXIT_FAILURE);
- }
-
- /*
- * The kernel team and the DirectoryServices team want initgroups()
- * called after setgid(). See 4616864 for more information.
- */
-
- if (likely(!j->no_init_groups)) {
- #if 1
- if (!job_assumes(j, initgroups(loginname, desired_gid) != -1)) {
- _exit(EXIT_FAILURE);
- }
- #else
- /* Do our own little initgroups(). We do this to guarantee that we're
- * always opted into dynamic group resolution in the kernel. initgroups(3)
- * does not make this guarantee.
- */
- int groups[NGROUPS], ngroups;
-
- /* A failure here isn't fatal, and we'll still get data we can use. */
- (void)job_assumes(j, getgrouplist(j->username, desired_gid, groups, &ngroups) != -1);
-
- if (!job_assumes(j, syscall(SYS_initgroups, ngroups, groups, desired_uid) != -1)) {
- _exit(EXIT_FAILURE);
- }
- #endif
- }
-
- if (!job_assumes(j, setuid(desired_uid) != -1)) {
- _exit(EXIT_FAILURE);
- }
-
- r = confstr(_CS_DARWIN_USER_TEMP_DIR, tmpdirpath, sizeof(tmpdirpath));
-
- if (likely(r > 0 && r < sizeof(tmpdirpath))) {
- setenv("TMPDIR", tmpdirpath, 0);
- }
-
- setenv("SHELL", shellpath, 0);
- setenv("HOME", homedir, 0);
- setenv("USER", loginname, 0);
- setenv("LOGNAME", loginname, 0);
-}
-
-void
-job_setup_attributes(job_t j)
-{
- struct limititem *li;
- struct envitem *ei;
-
- if (unlikely(j->setnice)) {
- (void)job_assumes(j, setpriority(PRIO_PROCESS, 0, j->nice) != -1);
- }
-
- SLIST_FOREACH(li, &j->limits, sle) {
- struct rlimit rl;
-
- if (!job_assumes(j, getrlimit(li->which, &rl) != -1)) {
- continue;
- }
-
- if (li->sethard) {
- rl.rlim_max = li->lim.rlim_max;
- }
- if (li->setsoft) {
- rl.rlim_cur = li->lim.rlim_cur;
- }
-
- if (setrlimit(li->which, &rl) == -1) {
- job_log_error(j, LOG_WARNING, "setrlimit()");
- }
- }
-
- if (unlikely(!j->inetcompat && j->session_create)) {
- launchd_SessionCreate();
- }
-
- if (unlikely(j->low_pri_io)) {
- (void)job_assumes(j, setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_THROTTLE) != -1);
- }
- if (unlikely(j->rootdir)) {
- (void)job_assumes(j, chroot(j->rootdir) != -1);
- (void)job_assumes(j, chdir(".") != -1);
- }
-
- job_postfork_become_user(j);
-
- if (unlikely(j->workingdir)) {
- (void)job_assumes(j, chdir(j->workingdir) != -1);
- }
-
- if (unlikely(j->setmask)) {
- umask(j->mask);
- }
-
- if (j->stdin_fd) {
- (void)job_assumes(j, dup2(j->stdin_fd, STDIN_FILENO) != -1);
- } else {
- job_setup_fd(j, STDIN_FILENO, j->stdinpath, O_RDONLY|O_CREAT);
- }
- job_setup_fd(j, STDOUT_FILENO, j->stdoutpath, O_WRONLY|O_CREAT|O_APPEND);
- job_setup_fd(j, STDERR_FILENO, j->stderrpath, O_WRONLY|O_CREAT|O_APPEND);
-
- jobmgr_setup_env_from_other_jobs(j->mgr);
-
- SLIST_FOREACH(ei, &j->env, sle) {
- setenv(ei->key, ei->value, 1);
- }
-
- if (do_apple_internal_logging) {
- setenv(LAUNCHD_DO_APPLE_INTERNAL_LOGGING, "true", 1);
- }
-
-#if !TARGET_OS_EMBEDDED
- if (j->jetsam_properties) {
- (void)job_assumes(j, proc_setpcontrol(PROC_SETPC_TERMINATE) == 0);
- }
-#endif
-
-#if TARGET_OS_EMBEDDED
- if (j->main_thread_priority != 0) {
- struct sched_param params;
- bzero(¶ms, sizeof(params));
- params.sched_priority = j->main_thread_priority;
- (void)job_assumes(j, pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶ms) != -1);
- }
-#endif
-
- /*
- * We'd like to call setsid() unconditionally, but we have reason to
- * believe that prevents launchd from being able to send signals to
- * setuid children. We'll settle for process-groups.
- */
- if (getppid() != 1) {
- (void)job_assumes(j, setpgid(0, 0) != -1);
- } else {
- (void)job_assumes(j, setsid() != -1);
- }
-}
-
-void
-job_setup_fd(job_t j, int target_fd, const char *path, int flags)
-{
- int fd;
-
- if (!path) {
- return;
- }
-
- if ((fd = open(path, flags|O_NOCTTY, DEFFILEMODE)) == -1) {
- job_log_error(j, LOG_WARNING, "open(\"%s\", ...)", path);
- return;
- }
-
- (void)job_assumes(j, dup2(fd, target_fd) != -1);
- (void)job_assumes(j, runtime_close(fd) == 0);
-}
-
-int
-dir_has_files(job_t j, const char *path)
-{
- DIR *dd = opendir(path);
- struct dirent *de;
- bool r = 0;
-
- if (unlikely(!dd)) {
- return -1;
- }
-
- while ((de = readdir(dd))) {
- if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) {
- r = 1;
- break;
- }
- }
-
- (void)job_assumes(j, closedir(dd) == 0);
- return r;
-}
-
-void
-calendarinterval_setalarm(job_t j, struct calendarinterval *ci)
-{
- struct calendarinterval *ci_iter, *ci_prev = NULL;
- time_t later, head_later;
-
- later = cronemu(ci->when.tm_mon, ci->when.tm_mday, ci->when.tm_hour, ci->when.tm_min);
-
- if (ci->when.tm_wday != -1) {
- time_t otherlater = cronemu_wday(ci->when.tm_wday, ci->when.tm_hour, ci->when.tm_min);
-
- if (ci->when.tm_mday == -1) {
- later = otherlater;
- } else {
- later = later < otherlater ? later : otherlater;
- }
- }
-
- ci->when_next = later;
-
- LIST_FOREACH(ci_iter, &sorted_calendar_events, global_sle) {
- if (ci->when_next < ci_iter->when_next) {
- LIST_INSERT_BEFORE(ci_iter, ci, global_sle);
- break;
- }
-
- ci_prev = ci_iter;
- }
-
- if (ci_iter == NULL) {
- /* ci must want to fire after every other timer, or there are no timers */
-
- if (LIST_EMPTY(&sorted_calendar_events)) {
- LIST_INSERT_HEAD(&sorted_calendar_events, ci, global_sle);
- } else {
- LIST_INSERT_AFTER(ci_prev, ci, global_sle);
- }
- }
-
- head_later = LIST_FIRST(&sorted_calendar_events)->when_next;
-
- if (job_assumes(j, kevent_mod((uintptr_t)&sorted_calendar_events, EVFILT_TIMER, EV_ADD, NOTE_ABSOLUTE|NOTE_SECONDS, head_later, root_jobmgr) != -1)) {
- char time_string[100];
- size_t time_string_len;
-
- ctime_r(&later, time_string);
- time_string_len = strlen(time_string);
-
- if (likely(time_string_len && time_string[time_string_len - 1] == '\n')) {
- time_string[time_string_len - 1] = '\0';
- }
-
- job_log(j, LOG_INFO, "Scheduled to run again at %s", time_string);
- }
-}
-
-void
-extract_rcsid_substr(const char *i, char *o, size_t osz)
-{
- char *rcs_rev_tmp = strchr(i, ' ');
-
- if (!rcs_rev_tmp) {
- strlcpy(o, i, osz);
- } else {
- strlcpy(o, rcs_rev_tmp + 1, osz);
- rcs_rev_tmp = strchr(o, ' ');
- if (rcs_rev_tmp) {
- *rcs_rev_tmp = '\0';
- }
- }
-}
-
-void
-jobmgr_log_bug(jobmgr_t jm, unsigned int line)
-{
- static const char *file;
- int saved_errno = errno;
- char buf[100];
-
- runtime_ktrace1(RTKT_LAUNCHD_BUG);
-
- extract_rcsid_substr(__rcs_file_version__, buf, sizeof(buf));
-
- if (!file) {
- file = strrchr(__FILE__, '/');
- if (!file) {
- file = __FILE__;
- } else {
- file += 1;
- }
- }
-
- /* the only time 'jm' should not be set is if setting up the first bootstrap fails for some reason */
- if (likely(jm)) {
- jobmgr_log(jm, LOG_NOTICE, "Bug: %s:%u (%s):%u", file, line, buf, saved_errno);
- } else {
- runtime_syslog(LOG_NOTICE, "Bug: %s:%u (%s):%u", file, line, buf, saved_errno);
- }
-}
-
-void
-job_log_bug(job_t j, unsigned int line)
-{
- static const char *file;
- int saved_errno = errno;
- char buf[100];
-
- runtime_ktrace1(RTKT_LAUNCHD_BUG);
-
- extract_rcsid_substr(__rcs_file_version__, buf, sizeof(buf));
-
- if (!file) {
- file = strrchr(__FILE__, '/');
- if (!file) {
- file = __FILE__;
- } else {
- file += 1;
- }
- }
-
- if (likely(j)) {
- job_log(j, LOG_NOTICE, "Bug: %s:%u (%s):%u", file, line, buf, saved_errno);
- } else {
- runtime_syslog(LOG_NOTICE, "Bug: %s:%u (%s):%u", file, line, buf, saved_errno);
- }
-}
-
-void
-job_logv(job_t j, int pri, int err, const char *msg, va_list ap)
-{
- const char *label2use = j ? j->label : "com.apple.launchd.NULL";
- const char *mgr2use = j ? j->mgr->name : "NULL";
- struct runtime_syslog_attr attr = { g_my_label, label2use, mgr2use, pri, getuid(), getpid(), j ? j->p : 0 };
- char *newmsg;
- int oldmask = 0;
- size_t newmsgsz;
-
- /*
- * Hack: If bootstrap_port is set, we must be on the child side of a
- * fork(), but before the exec*(). Let's route the log message back to
- * launchd proper.
- */
- if (bootstrap_port) {
- return _vproc_logv(pri, err, msg, ap);
- }
-
- newmsgsz = strlen(msg) + 200;
- newmsg = alloca(newmsgsz);
-
- if (err) {
-#if !TARGET_OS_EMBEDDED
- snprintf(newmsg, newmsgsz, "%s: %s", msg, strerror(err));
-#else
- snprintf(newmsg, newmsgsz, "(%s) %s: %s", label2use, msg, strerror(err));
-#endif
- } else {
-#if !TARGET_OS_EMBEDDED
- snprintf(newmsg, newmsgsz, "%s", msg);
-#else
- snprintf(newmsg, newmsgsz, "(%s) %s", label2use, msg);
-#endif
- }
-
- if (j && unlikely(j->debug)) {
- oldmask = setlogmask(LOG_UPTO(LOG_DEBUG));
- }
-
- runtime_vsyslog(&attr, newmsg, ap);
-
- if (j && unlikely(j->debug)) {
- setlogmask(oldmask);
- }
-}
-
-void
-job_log_error(job_t j, int pri, const char *msg, ...)
-{
- va_list ap;
-
- va_start(ap, msg);
- job_logv(j, pri, errno, msg, ap);
- va_end(ap);
-}
-
-void
-job_log(job_t j, int pri, const char *msg, ...)
-{
- va_list ap;
-
- va_start(ap, msg);
- job_logv(j, pri, 0, msg, ap);
- va_end(ap);
-}
-
-#if 0
-void
-jobmgr_log_error(jobmgr_t jm, int pri, const char *msg, ...)
-{
- va_list ap;
-
- va_start(ap, msg);
- jobmgr_logv(jm, pri, errno, msg, ap);
- va_end(ap);
-}
-#endif
-
-void
-jobmgr_log(jobmgr_t jm, int pri, const char *msg, ...)
-{
- va_list ap;
-
- va_start(ap, msg);
- jobmgr_logv(jm, pri, 0, msg, ap);
- va_end(ap);
-}
-
-void
-jobmgr_logv(jobmgr_t jm, int pri, int err, const char *msg, va_list ap)
-{
- char *newmsg;
- char *newname;
- size_t i, o, jmname_len = strlen(jm->name), newmsgsz;
-
- newname = alloca((jmname_len + 1) * 2);
- newmsgsz = (jmname_len + 1) * 2 + strlen(msg) + 100;
- newmsg = alloca(newmsgsz);
-
- for (i = 0, o = 0; i < jmname_len; i++, o++) {
- if (jm->name[i] == '%') {
- newname[o] = '%';
- o++;
- }
- newname[o] = jm->name[i];
- }
- newname[o] = '\0';
-
- if (err) {
- snprintf(newmsg, newmsgsz, "%s: %s: %s", newname, msg, strerror(err));
- } else {
- snprintf(newmsg, newmsgsz, "%s: %s", newname, msg);
- }
-
- if (jm->parentmgr) {
- jobmgr_logv(jm->parentmgr, pri, 0, newmsg, ap);
- } else {
- struct runtime_syslog_attr attr = { g_my_label, g_my_label, jm->name, pri, getuid(), getpid(), getpid() };
-
- runtime_vsyslog(&attr, newmsg, ap);
- }
-}
-
-void
-semaphoreitem_ignore(job_t j, struct semaphoreitem *si)
-{
- if (si->fd != -1) {
- job_log(j, LOG_DEBUG, "Ignoring Vnode: %d", si->fd);
- (void)job_assumes(j, kevent_mod(si->fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL) != -1);
- }
-}
-
-void
-semaphoreitem_watch(job_t j, struct semaphoreitem *si)
-{
- char *parentdir, tmp_path[PATH_MAX];
- int saved_errno = 0;
- int fflags = NOTE_DELETE|NOTE_RENAME;
-
- switch (si->why) {
- case DIR_NOT_EMPTY:
- case PATH_CHANGES:
- fflags |= NOTE_ATTRIB|NOTE_LINK;
- /* fall through */
- case PATH_EXISTS:
- fflags |= NOTE_REVOKE|NOTE_EXTEND|NOTE_WRITE;
- /* fall through */
- case PATH_MISSING:
- break;
- default:
- return;
- }
-
- /* dirname() may modify tmp_path */
- strlcpy(tmp_path, si->what, sizeof(tmp_path));
-
- if (!job_assumes(j, (parentdir = dirname(tmp_path)))) {
- return;
- }
-
- /* See 5321044 for why we do the do-while loop and 5415523 for why ENOENT is checked */
- do {
- if (si->fd == -1) {
- struct stat sb;
- if (stat(si->what, &sb) == 0) {
- /* If we're watching a character or block device, only watch the parent directory.
- * See rdar://problem/6489900 for the gory details. Basically, holding an open file
- * descriptor to a devnode could end up (a) blocking us on open(2) until someone else
- * open(2)s the file (like a character device that waits for a carrier signal) or
- * (b) preventing other processes from obtaining an exclusive lock on the file, even
- * though we're opening it with O_EVTONLY.
- *
- * The main point of contention is that O_EVTONLY doesn't actually mean "event only".
- * It means "Don't prevent unmounts of this descriptor's volume". We work around this
- * for dev nodes by only watching the parent directory and stat(2)ing our desired file
- * each time the parent changes to see if it appeared or disappeared.
- */
- if (S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode)) {
- si->fd = _fd(open(si->what, O_EVTONLY | O_NOCTTY | O_NONBLOCK));
- }
- }
-
- if (si->fd == -1) {
- si->watching_parent = job_assumes(j, (si->fd = _fd(open(parentdir, O_EVTONLY | O_NOCTTY | O_NONBLOCK))) != -1);
- } else {
- si->watching_parent = false;
- }
- }
-
- if (si->fd == -1) {
- return job_log_error(j, LOG_ERR, "Path monitoring failed on \"%s\"", si->what);
- }
-
- job_log(j, LOG_DEBUG, "Watching %svnode (%s): %d", si->watching_parent ? "parent ": "", si->what, si->fd);
-
- if (kevent_mod(si->fd, EVFILT_VNODE, EV_ADD, fflags, 0, j) == -1) {
- saved_errno = errno;
- /*
- * The FD can be revoked between the open() and kevent().
- * This is similar to the inability for kevents to be
- * attached to short lived zombie processes after fork()
- * but before kevent().
- */
- (void)job_assumes(j, runtime_close(si->fd) == 0);
- si->fd = -1;
- }
- } while (unlikely((si->fd == -1) && (saved_errno == ENOENT)));
-
- if (saved_errno == ENOTSUP) {
- /*
- * 3524219 NFS needs kqueue support
- * 4124079 VFS needs generic kqueue support
- * 5226811 EVFILT: Launchd EVFILT_VNODE doesn't work on /dev
- */
- job_log(j, LOG_DEBUG, "Falling back to polling for path: %s", si->what);
-
- if (!j->poll_for_vfs_changes) {
- j->poll_for_vfs_changes = true;
- (void)job_assumes(j, kevent_mod((uintptr_t)&j->semaphores, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, 3, j) != -1);
- }
- }
-}
-
-void
-semaphoreitem_callback(job_t j, struct kevent *kev)
-{
- char invalidation_reason[100] = "";
- struct semaphoreitem *si;
-
- SLIST_FOREACH(si, &j->semaphores, sle) {
- switch (si->why) {
- case PATH_CHANGES:
- case PATH_EXISTS:
- case PATH_MISSING:
- case DIR_NOT_EMPTY:
- job_log(j, LOG_DEBUG, "P%s changed (%u): %s", si->watching_parent ? "arent path" : "ath", si->why, si->what);
- break;
- default:
- continue;
- }
-
- if (si->fd == (int)kev->ident) {
- break;
- }
- }
-
- if (!job_assumes(j, si != NULL)) {
- return;
- }
-
- if (NOTE_DELETE & kev->fflags) {
- strcat(invalidation_reason, "deleted");
- }
-
- if (NOTE_RENAME & kev->fflags) {
- if (invalidation_reason[0]) {
- strcat(invalidation_reason, "/renamed");
- } else {
- strcat(invalidation_reason, "renamed");
- }
- }
-
- if (NOTE_REVOKE & kev->fflags) {
- if (invalidation_reason[0]) {
- strcat(invalidation_reason, "/revoked");
- } else {
- strcat(invalidation_reason, "revoked");
- }
- }
-
- if (invalidation_reason[0]) {
- job_log(j, LOG_DEBUG, "Path %s: %s", invalidation_reason, si->what);
- (void)job_assumes(j, runtime_close(si->fd) == 0);
- si->fd = -1; /* this will get fixed in semaphoreitem_watch() */
- }
-
- if (!si->watching_parent) {
- if (si->why == PATH_CHANGES) {
- j->start_pending = true;
- } else {
- semaphoreitem_watch(j, si);
- }
- } else { /* Something happened to the parent directory. See if our target file appeared. */
- if (!invalidation_reason[0]) {
- (void)job_assumes(j, runtime_close(si->fd) == 0);
- si->fd = -1; /* this will get fixed in semaphoreitem_watch() */
- semaphoreitem_watch(j, si);
- }
- /* Need to think about what should happen if the parent directory goes invalid. */
- }
-
- job_dispatch(j, false);
-}
-
-struct cal_dict_walk {
- job_t j;
- struct tm tmptm;
-};
-
-void
-calendarinterval_new_from_obj_dict_walk(launch_data_t obj, const char *key, void *context)
-{
- struct cal_dict_walk *cdw = context;
- struct tm *tmptm = &cdw->tmptm;
- job_t j = cdw->j;
- int64_t val;
-
- if (unlikely(LAUNCH_DATA_INTEGER != launch_data_get_type(obj))) {
- /* hack to let caller know something went wrong */
- tmptm->tm_sec = -1;
- return;
- }
-
- val = launch_data_get_integer(obj);
-
- if (val < 0) {
- job_log(j, LOG_WARNING, "The interval for key \"%s\" is less than zero.", key);
- } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_MINUTE) == 0) {
- if (val > 59) {
- job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 0 and 59 (inclusive).", key);
- tmptm->tm_sec = -1;
- } else {
- tmptm->tm_min = (typeof(tmptm->tm_min)) val;
- }
- } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_HOUR) == 0) {
- if (val > 23) {
- job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 0 and 23 (inclusive).", key);
- tmptm->tm_sec = -1;
- } else {
- tmptm->tm_hour = (typeof(tmptm->tm_hour)) val;
- }
- } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_DAY) == 0) {
- if (val < 1 || val > 31) {
- job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 1 and 31 (inclusive).", key);
- tmptm->tm_sec = -1;
- } else {
- tmptm->tm_mday = (typeof(tmptm->tm_mday)) val;
- }
- } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_WEEKDAY) == 0) {
- if (val > 7) {
- job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 0 and 7 (inclusive).", key);
- tmptm->tm_sec = -1;
- } else {
- tmptm->tm_wday = (typeof(tmptm->tm_wday)) val;
- }
- } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_MONTH) == 0) {
- if (val > 12) {
- job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 0 and 12 (inclusive).", key);
- tmptm->tm_sec = -1;
- } else {
- tmptm->tm_mon = (typeof(tmptm->tm_mon)) val;
- tmptm->tm_mon -= 1; /* 4798263 cron compatibility */
- }
- }
-}
-
-bool
-calendarinterval_new_from_obj(job_t j, launch_data_t obj)
-{
- struct cal_dict_walk cdw;
-
- cdw.j = j;
- memset(&cdw.tmptm, 0, sizeof(0));
-
- cdw.tmptm.tm_min = -1;
- cdw.tmptm.tm_hour = -1;
- cdw.tmptm.tm_mday = -1;
- cdw.tmptm.tm_wday = -1;
- cdw.tmptm.tm_mon = -1;
-
- if (!job_assumes(j, obj != NULL)) {
- return false;
- }
-
- if (unlikely(LAUNCH_DATA_DICTIONARY != launch_data_get_type(obj))) {
- return false;
- }
-
- launch_data_dict_iterate(obj, calendarinterval_new_from_obj_dict_walk, &cdw);
-
- if (unlikely(cdw.tmptm.tm_sec == -1)) {
- return false;
- }
-
- return calendarinterval_new(j, &cdw.tmptm);
-}
-
-bool
-calendarinterval_new(job_t j, struct tm *w)
-{
- struct calendarinterval *ci = calloc(1, sizeof(struct calendarinterval));
-
- if (!job_assumes(j, ci != NULL)) {
- return false;
- }
-
- ci->when = *w;
- ci->job = j;
-
- SLIST_INSERT_HEAD(&j->cal_intervals, ci, sle);
-
- calendarinterval_setalarm(j, ci);
-
- runtime_add_weak_ref();
-
- return true;
-}
-
-void
-calendarinterval_delete(job_t j, struct calendarinterval *ci)
-{
- SLIST_REMOVE(&j->cal_intervals, ci, calendarinterval, sle);
- LIST_REMOVE(ci, global_sle);
-
- free(ci);
-
- runtime_del_weak_ref();
-}
-
-void
-calendarinterval_sanity_check(void)
-{
- struct calendarinterval *ci = LIST_FIRST(&sorted_calendar_events);
- time_t now = time(NULL);
-
- if (unlikely(ci && (ci->when_next < now))) {
- (void)jobmgr_assumes(root_jobmgr, raise(SIGUSR1) != -1);
- }
-}
-
-void
-calendarinterval_callback(void)
-{
- struct calendarinterval *ci, *ci_next;
- time_t now = time(NULL);
-
- LIST_FOREACH_SAFE(ci, &sorted_calendar_events, global_sle, ci_next) {
- job_t j = ci->job;
-
- if (ci->when_next > now) {
- break;
- }
-
- LIST_REMOVE(ci, global_sle);
- calendarinterval_setalarm(j, ci);
-
- j->start_pending = true;
- job_dispatch(j, false);
- }
-}
-
-bool
-socketgroup_new(job_t j, const char *name, int *fds, size_t fd_cnt, bool junkfds)
-{
- struct socketgroup *sg = calloc(1, sizeof(struct socketgroup) + strlen(name) + 1);
-
- if (!job_assumes(j, sg != NULL)) {
- return false;
- }
-
- sg->fds = calloc(1, fd_cnt * sizeof(int));
- sg->fd_cnt = fd_cnt;
- sg->junkfds = junkfds;
-
- if (!job_assumes(j, sg->fds != NULL)) {
- free(sg);
- return false;
- }
-
- memcpy(sg->fds, fds, fd_cnt * sizeof(int));
- strcpy(sg->name_init, name);
-
- SLIST_INSERT_HEAD(&j->sockets, sg, sle);
-
- runtime_add_weak_ref();
-
- return true;
-}
-
-void
-socketgroup_delete(job_t j, struct socketgroup *sg)
-{
- unsigned int i;
-
- for (i = 0; i < sg->fd_cnt; i++) {
-#if 0
- struct sockaddr_storage ss;
- struct sockaddr_un *sun = (struct sockaddr_un *)&ss;
- socklen_t ss_len = sizeof(ss);
-
- /* 5480306 */
- if (job_assumes(j, getsockname(sg->fds[i], (struct sockaddr *)&ss, &ss_len) != -1)
- && job_assumes(j, ss_len > 0) && (ss.ss_family == AF_UNIX)) {
- (void)job_assumes(j, unlink(sun->sun_path) != -1);
- /* We might conditionally need to delete a directory here */
- }
-#endif
- (void)job_assumes(j, runtime_close(sg->fds[i]) != -1);
- }
-
- SLIST_REMOVE(&j->sockets, sg, socketgroup, sle);
-
- free(sg->fds);
- free(sg);
-
- runtime_del_weak_ref();
-}
-
-void
-socketgroup_kevent_mod(job_t j, struct socketgroup *sg, bool do_add)
-{
- struct kevent kev[sg->fd_cnt];
- char buf[10000];
- unsigned int i, buf_off = 0;
-
- if (unlikely(sg->junkfds)) {
- return;
- }
-
- for (i = 0; i < sg->fd_cnt; i++) {
- EV_SET(&kev[i], sg->fds[i], EVFILT_READ, do_add ? EV_ADD : EV_DELETE, 0, 0, j);
- buf_off += snprintf(buf + buf_off, sizeof(buf) - buf_off, " %d", sg->fds[i]);
- }
-
- job_log(j, LOG_DEBUG, "%s Sockets:%s", do_add ? "Watching" : "Ignoring", buf);
-
- (void)job_assumes(j, kevent_bulk_mod(kev, sg->fd_cnt) != -1);
-
- for (i = 0; i < sg->fd_cnt; i++) {
- (void)job_assumes(j, kev[i].flags & EV_ERROR);
- errno = (typeof(errno)) kev[i].data;
- (void)job_assumes(j, kev[i].data == 0);
- }
-}
-
-void
-socketgroup_ignore(job_t j, struct socketgroup *sg)
-{
- socketgroup_kevent_mod(j, sg, false);
-}
-
-void
-socketgroup_watch(job_t j, struct socketgroup *sg)
-{
- socketgroup_kevent_mod(j, sg, true);
-}
-
-void
-socketgroup_callback(job_t j)
-{
- job_dispatch(j, true);
-}
-
-bool
-envitem_new(job_t j, const char *k, const char *v, bool global, bool one_shot)
-{
- struct envitem *ei = calloc(1, sizeof(struct envitem) + strlen(k) + 1 + strlen(v) + 1);
-
- if (!job_assumes(j, ei != NULL)) {
- return false;
- }
-
- strcpy(ei->key_init, k);
- ei->value = ei->key_init + strlen(k) + 1;
- strcpy(ei->value, v);
- ei->one_shot = one_shot;
-
- if (global) {
- if (SLIST_EMPTY(&j->global_env)) {
- LIST_INSERT_HEAD(&j->mgr->global_env_jobs, j, global_env_sle);
- }
- SLIST_INSERT_HEAD(&j->global_env, ei, sle);
- } else {
- SLIST_INSERT_HEAD(&j->env, ei, sle);
- }
-
- job_log(j, LOG_DEBUG, "Added environmental variable: %s=%s", k, v);
-
- return true;
-}
-
-void
-envitem_delete(job_t j, struct envitem *ei, bool global)
-{
- if (global) {
- SLIST_REMOVE(&j->global_env, ei, envitem, sle);
- if (SLIST_EMPTY(&j->global_env)) {
- LIST_REMOVE(j, global_env_sle);
- }
- } else {
- SLIST_REMOVE(&j->env, ei, envitem, sle);
- }
-
- free(ei);
-}
-
-void
-envitem_setup(launch_data_t obj, const char *key, void *context)
-{
- job_t j = context;
-
- if (launch_data_get_type(obj) != LAUNCH_DATA_STRING) {
- return;
- }
-
- if (strncmp(LAUNCHD_TRUSTED_FD_ENV, key, sizeof(LAUNCHD_TRUSTED_FD_ENV) - 1) != 0) {
- envitem_new(j, key, launch_data_get_string(obj), j->importing_global_env, false);
- } else {
- job_log(j, LOG_DEBUG, "Ignoring reserved environmental variable: %s", key);
- }
-}
-
-void
-envitem_setup_one_shot(launch_data_t obj, const char *key, void *context)
-{
- job_t j = context;
-
- if (launch_data_get_type(obj) != LAUNCH_DATA_STRING) {
- return;
- }
-
- if (strncmp(LAUNCHD_TRUSTED_FD_ENV, key, sizeof(LAUNCHD_TRUSTED_FD_ENV) - 1) != 0) {
- envitem_new(j, key, launch_data_get_string(obj), j->importing_global_env, true);
- } else {
- job_log(j, LOG_DEBUG, "Ignoring reserved environmental variable: %s", key);
- }
-}
-
-bool
-limititem_update(job_t j, int w, rlim_t r)
-{
- struct limititem *li;
-
- SLIST_FOREACH(li, &j->limits, sle) {
- if (li->which == w) {
- break;
- }
- }
-
- if (li == NULL) {
- li = calloc(1, sizeof(struct limititem));
-
- if (!job_assumes(j, li != NULL)) {
- return false;
- }
-
- SLIST_INSERT_HEAD(&j->limits, li, sle);
-
- li->which = w;
- }
-
- if (j->importing_hard_limits) {
- li->lim.rlim_max = r;
- li->sethard = true;
- } else {
- li->lim.rlim_cur = r;
- li->setsoft = true;
- }
-
- return true;
-}
-
-void
-limititem_delete(job_t j, struct limititem *li)
-{
- SLIST_REMOVE(&j->limits, li, limititem, sle);
-
- free(li);
-}
-
-#if HAVE_SANDBOX
-void
-seatbelt_setup_flags(launch_data_t obj, const char *key, void *context)
-{
- job_t j = context;
-
- if (launch_data_get_type(obj) != LAUNCH_DATA_BOOL) {
- job_log(j, LOG_WARNING, "Sandbox flag value must be boolean: %s", key);
- return;
- }
-
- if (launch_data_get_bool(obj) == false) {
- return;
- }
-
- if (strcasecmp(key, LAUNCH_JOBKEY_SANDBOX_NAMED) == 0) {
- j->seatbelt_flags |= SANDBOX_NAMED;
- }
-}
-#endif
-
-void
-limititem_setup(launch_data_t obj, const char *key, void *context)
-{
- job_t j = context;
- size_t i, limits_cnt = (sizeof(launchd_keys2limits) / sizeof(launchd_keys2limits[0]));
- rlim_t rl;
-
- if (launch_data_get_type(obj) != LAUNCH_DATA_INTEGER) {
- return;
- }
-
- rl = launch_data_get_integer(obj);
-
- for (i = 0; i < limits_cnt; i++) {
- if (strcasecmp(launchd_keys2limits[i].key, key) == 0) {
- break;
- }
- }
-
- if (i == limits_cnt) {
- return;
- }
-
- limititem_update(j, launchd_keys2limits[i].val, rl);
-}
-
-bool
-job_useless(job_t j)
-{
- if ((j->legacy_LS_job || j->only_once) && j->start_time != 0) {
- if (j->legacy_LS_job && j->j_port) {
- return false;
- }
- job_log(j, LOG_INFO, "Exited. Was only configured to run once.");
- return true;
- } else if (j->removal_pending) {
- job_log(j, LOG_DEBUG, "Exited while removal was pending.");
- return true;
- } else if (j->shutdown_monitor) {
- return false;
- } else if (j->mgr->shutting_down) {
- job_log(j, LOG_DEBUG, "Exited while shutdown in progress. Processes remaining: %lu/%lu", total_children, total_anon_children);
- if (total_children == 0 && !j->anonymous) {
- job_log(j, LOG_DEBUG | LOG_CONSOLE, "Job was last to exit during shutdown of: %s.", j->mgr->name);
- }
- return true;
- } else if (j->legacy_mach_job) {
- if (SLIST_EMPTY(&j->machservices)) {
- job_log(j, LOG_INFO, "Garbage collecting");
- return true;
- } else if (!j->checkedin) {
- job_log(j, LOG_WARNING, "Failed to check-in!");
- return true;
- }
- } else {
- /* If the job's executable does not have any valid architectures (for
- * example, if it's a PowerPC-only job), then we don't even bother
- * trying to relaunch it, as we have no reasonable expectation that
- * the situation will change.
- *
- * <rdar://problem/9106979>
- */
- if (!j->did_exec && WEXITSTATUS(j->last_exit_status) == EBADARCH) {
- job_log(j, LOG_ERR, "Job executable does not contain supported architectures. Unloading it. Its plist should be removed.");
- return true;
- }
- }
-
- return false;
-}
-
-bool
-job_keepalive(job_t j)
-{
- mach_msg_type_number_t statusCnt;
- mach_port_status_t status;
- struct semaphoreitem *si;
- struct machservice *ms;
- struct stat sb;
- bool good_exit = (WIFEXITED(j->last_exit_status) && WEXITSTATUS(j->last_exit_status) == 0);
- bool is_not_kextd = (do_apple_internal_logging || (strcmp(j->label, "com.apple.kextd") != 0));
-
- if (unlikely(j->mgr->shutting_down)) {
- return false;
- }
-
- /*
- * 5066316
- *
- * We definitely need to revisit this after Leopard ships. Please see
- * launchctl.c for the other half of this hack.
- */
- if (unlikely((j->mgr->global_on_demand_cnt > 0) && is_not_kextd)) {
- return false;
- }
-
- if (unlikely(j->needs_kickoff)) {
- job_log(j, LOG_DEBUG, "KeepAlive check: Job needs to be kicked off on-demand before KeepAlive sets in.");
- return false;
- }
-
- if (j->start_pending) {
- job_log(j, LOG_DEBUG, "KeepAlive check: Pent-up non-IPC launch criteria.");
- return true;
- }
-
- if (!j->ondemand) {
- job_log(j, LOG_DEBUG, "KeepAlive check: job configured to run continuously.");
- return true;
- }
-
- SLIST_FOREACH(ms, &j->machservices, sle) {
- statusCnt = MACH_PORT_RECEIVE_STATUS_COUNT;
- if (mach_port_get_attributes(mach_task_self(), ms->port, MACH_PORT_RECEIVE_STATUS,
- (mach_port_info_t)&status, &statusCnt) != KERN_SUCCESS) {
- continue;
- }
- if (status.mps_msgcount) {
- job_log(j, LOG_DEBUG, "KeepAlive check: %d queued Mach messages on service: %s",
- status.mps_msgcount, ms->name);
- return true;
- }
- }
-
- /* TODO: Coalesce external events and semaphore items, since they're basically
- * the same thing.
- */
- struct externalevent *ei = NULL;
- LIST_FOREACH(ei, &j->events, job_le) {
- if (ei->state == ei->wanted_state) {
- return true;
- }
- }
-
- SLIST_FOREACH(si, &j->semaphores, sle) {
- bool wanted_state = false;
- int qdir_file_cnt;
- job_t other_j;
-
- switch (si->why) {
- case NETWORK_UP:
- wanted_state = true;
- case NETWORK_DOWN:
- if (network_up == wanted_state) {
- job_log(j, LOG_DEBUG, "KeepAlive: The network is %s.", wanted_state ? "up" : "down");
- return true;
- }
- break;
- case SUCCESSFUL_EXIT:
- wanted_state = true;
- case FAILED_EXIT:
- if (good_exit == wanted_state) {
- job_log(j, LOG_DEBUG, "KeepAlive: The exit state was %s.", wanted_state ? "successful" : "failure");
- return true;
- }
- break;
- case CRASHED:
- wanted_state = true;
- case DID_NOT_CRASH:
- if (j->crashed == wanted_state) {
- return true;
- }
- break;
- case OTHER_JOB_ENABLED:
- wanted_state = true;
- case OTHER_JOB_DISABLED:
- if ((bool)job_find(NULL, si->what) == wanted_state) {
- job_log(j, LOG_DEBUG, "KeepAlive: The following job is %s: %s", wanted_state ? "enabled" : "disabled", si->what);
- return true;
- }
- break;
- case OTHER_JOB_ACTIVE:
- wanted_state = true;
- case OTHER_JOB_INACTIVE:
- if ((other_j = job_find(NULL, si->what))) {
- if ((bool)other_j->p == wanted_state) {
- job_log(j, LOG_DEBUG, "KeepAlive: The following job is %s: %s", wanted_state ? "active" : "inactive", si->what);
- return true;
- }
- }
- break;
- case PATH_EXISTS:
- wanted_state = true;
- case PATH_MISSING:
- if ((bool)(stat(si->what, &sb) == 0) == wanted_state) {
- job_log(j, LOG_DEBUG, "KeepAlive: The following path %s: %s", wanted_state ? "exists" : "is missing", si->what);
- return true;
- } else {
- if (wanted_state) { /* File is not there but we wish it was. */
- if (si->fd != -1 && !si->watching_parent) { /* Need to be watching the parent now. */
- (void)job_assumes(j, runtime_close(si->fd) == 0);
- si->fd = -1;
- semaphoreitem_watch(j, si);
- }
- } else { /* File is there but we wish it wasn't. */
- if (si->fd != -1 && si->watching_parent) { /* Need to watch the file now. */
- (void)job_assumes(j, runtime_close(si->fd) == 0);
- si->fd = -1;
- semaphoreitem_watch(j, si);
- }
- }
- }
- break;
- case PATH_CHANGES:
- break;
- case DIR_NOT_EMPTY:
- if (-1 == (qdir_file_cnt = dir_has_files(j, si->what))) {
- job_log_error(j, LOG_ERR, "Failed to count the number of files in \"%s\"", si->what);
- } else if (qdir_file_cnt > 0) {
- job_log(j, LOG_DEBUG, "KeepAlive: Directory is not empty: %s", si->what);
- return true;
- }
- break;
- }
- }
-
- return false;
-}
-
-const char *
-job_active(job_t j)
-{
- struct machservice *ms;
- if (j->p && j->shutdown_monitor) {
- return "Monitoring shutdown";
- }
- if (j->p) {
- return "PID is still valid";
- }
-
- if (j->mgr->shutting_down && j->log_redirect_fd) {
- (void)job_assumes(j, runtime_close(j->log_redirect_fd) != -1);
- j->log_redirect_fd = 0;
- }
-
- if (j->log_redirect_fd) {
- if (job_assumes(j, j->legacy_LS_job)) {
- return "Standard out/error is still valid";
- } else {
- (void)job_assumes(j, runtime_close(j->log_redirect_fd) != -1);
- j->log_redirect_fd = 0;
- }
- }
-
- if (j->priv_port_has_senders) {
- return "Privileged Port still has outstanding senders";
- }
-
- SLIST_FOREACH(ms, &j->machservices, sle) {
- if (ms->recv && machservice_active(ms)) {
- return "Mach service is still active";
- }
- }
-
- return NULL;
-}
-
-void
-machservice_watch(job_t j, struct machservice *ms)
-{
- if (ms->recv) {
- (void)job_assumes(j, runtime_add_mport(ms->port, NULL, 0) == KERN_SUCCESS);
- }
-}
-
-void
-machservice_ignore(job_t j, struct machservice *ms)
-{
- (void)job_assumes(j, runtime_remove_mport(ms->port) == KERN_SUCCESS);
-}
-
-void
-machservice_resetport(job_t j, struct machservice *ms)
-{
- LIST_REMOVE(ms, port_hash_sle);
- (void)job_assumes(j, launchd_mport_close_recv(ms->port) == KERN_SUCCESS);
- (void)job_assumes(j, launchd_mport_deallocate(ms->port) == KERN_SUCCESS);
- ms->gen_num++;
- (void)job_assumes(j, launchd_mport_create_recv(&ms->port) == KERN_SUCCESS);
- (void)job_assumes(j, launchd_mport_make_send(ms->port) == KERN_SUCCESS);
- LIST_INSERT_HEAD(&port_hash[HASH_PORT(ms->port)], ms, port_hash_sle);
-}
-
-struct machservice *
-machservice_new(job_t j, const char *name, mach_port_t *serviceport, bool pid_local)
-{
- struct machservice *ms = calloc(1, sizeof(struct machservice) + strlen(name) + 1);
-
- if (!job_assumes(j, ms != NULL)) {
- return NULL;
- }
-
- strcpy((char *)ms->name, name);
- ms->job = j;
- ms->gen_num = 1;
- ms->per_pid = pid_local;
-
- if (likely(*serviceport == MACH_PORT_NULL)) {
- if (!job_assumes(j, launchd_mport_create_recv(&ms->port) == KERN_SUCCESS)) {
- goto out_bad;
- }
-
- if (!job_assumes(j, launchd_mport_make_send(ms->port) == KERN_SUCCESS)) {
- goto out_bad2;
- }
- *serviceport = ms->port;
- ms->recv = true;
- } else {
- ms->port = *serviceport;
- ms->isActive = true;
- }
-
- SLIST_INSERT_HEAD(&j->machservices, ms, sle);
-
- jobmgr_t where2put = j->mgr;
- /* XPC domains are separate from Mach bootstraps. */
- if (!(j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
- if (g_flat_mach_namespace && !(j->mgr->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET)) {
- where2put = root_jobmgr;
- }
- }
-
- /* Don't allow MachServices added by multiple-instance jobs to be looked up by others.
- * We could just do this with a simple bit, but then we'd have to uniquify the
- * names ourselves to avoid collisions. This is just easier.
- */
- if (!j->dedicated_instance) {
- LIST_INSERT_HEAD(&where2put->ms_hash[hash_ms(ms->name)], ms, name_hash_sle);
- }
- LIST_INSERT_HEAD(&port_hash[HASH_PORT(ms->port)], ms, port_hash_sle);
-
- job_log(j, LOG_DEBUG, "Mach service added%s: %s", (j->mgr->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET) ? " to private namespace" : "", name);
-
- return ms;
-out_bad2:
- (void)job_assumes(j, launchd_mport_close_recv(ms->port) == KERN_SUCCESS);
-out_bad:
- free(ms);
- return NULL;
-}
-
-#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__
-struct machservice *
-machservice_new_alias(job_t j, struct machservice *orig)
-{
- struct machservice *ms = calloc(1, sizeof(struct machservice) + strlen(orig->name) + 1);
- if (job_assumes(j, ms != NULL)) {
- strcpy((char *)ms->name, orig->name);
- ms->alias = orig;
- ms->job = j;
-
- LIST_INSERT_HEAD(&j->mgr->ms_hash[hash_ms(ms->name)], ms, name_hash_sle);
- SLIST_INSERT_HEAD(&j->machservices, ms, sle);
- jobmgr_log(j->mgr, LOG_DEBUG, "Service aliased into job manager: %s", orig->name);
- }
-
- return ms;
-}
-#endif /* __LAUNCH_DISABLE_XPC_SUPPORT__ */
-
-bootstrap_status_t
-machservice_status(struct machservice *ms)
-{
- ms = ms->alias ? ms->alias : ms;
- if (ms->isActive) {
- return BOOTSTRAP_STATUS_ACTIVE;
- } else if (ms->job->ondemand) {
- return BOOTSTRAP_STATUS_ON_DEMAND;
- } else {
- return BOOTSTRAP_STATUS_INACTIVE;
- }
-}
-
-void
-job_setup_exception_port(job_t j, task_t target_task)
-{
- struct machservice *ms;
- thread_state_flavor_t f = 0;
- mach_port_t exc_port = the_exception_server;
-
- if (unlikely(j->alt_exc_handler)) {
- ms = jobmgr_lookup_service(j->mgr, j->alt_exc_handler, true, 0);
- if (likely(ms)) {
- exc_port = machservice_port(ms);
- } else {
- job_log(j, LOG_WARNING, "Falling back to default Mach exception handler. Could not find: %s", j->alt_exc_handler);
- }
- } else if (unlikely(j->internal_exc_handler)) {
- exc_port = runtime_get_kernel_port();
- } else if (unlikely(!exc_port)) {
- return;
- }
-
-#if defined (__ppc__) || defined(__ppc64__)
- f = PPC_THREAD_STATE64;
-#elif defined(__i386__) || defined(__x86_64__)
- f = x86_THREAD_STATE;
-#elif defined(__arm__)
- f = ARM_THREAD_STATE;
-#else
-#error "unknown architecture"
-#endif
-
- if (likely(target_task)) {
- (void)job_assumes(j, task_set_exception_ports(target_task, EXC_MASK_CRASH, exc_port, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, f) == KERN_SUCCESS);
- } else if (pid1_magic && the_exception_server) {
- mach_port_t mhp = mach_host_self();
- (void)job_assumes(j, host_set_exception_ports(mhp, EXC_MASK_CRASH, the_exception_server, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, f) == KERN_SUCCESS);
- job_assumes(j, launchd_mport_deallocate(mhp) == KERN_SUCCESS);
- }
-}
-
-void
-job_set_exception_port(job_t j, mach_port_t port)
-{
- if (unlikely(!the_exception_server)) {
- the_exception_server = port;
- job_setup_exception_port(j, 0);
- } else {
- job_log(j, LOG_WARNING, "The exception server is already claimed!");
- }
-}
-
-void
-machservice_setup_options(launch_data_t obj, const char *key, void *context)
-{
- struct machservice *ms = context;
- mach_port_t mhp = mach_host_self();
- int which_port;
- bool b;
-
- if (!job_assumes(ms->job, mhp != MACH_PORT_NULL)) {
- return;
- }
-
- switch (launch_data_get_type(obj)) {
- case LAUNCH_DATA_INTEGER:
- which_port = (int)launch_data_get_integer(obj); /* XXX we should bound check this... */
- if (strcasecmp(key, LAUNCH_JOBKEY_MACH_TASKSPECIALPORT) == 0) {
- switch (which_port) {
- case TASK_KERNEL_PORT:
- case TASK_HOST_PORT:
- case TASK_NAME_PORT:
- case TASK_BOOTSTRAP_PORT:
- /* I find it a little odd that zero isn't reserved in the header.
- * Normally Mach is fairly good about this convention... */
- case 0:
- job_log(ms->job, LOG_WARNING, "Tried to set a reserved task special port: %d", which_port);
- break;
- default:
- ms->special_port_num = which_port;
- SLIST_INSERT_HEAD(&special_ports, ms, special_port_sle);
- break;
- }
- } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_HOSTSPECIALPORT) == 0 && pid1_magic) {
- if (which_port > HOST_MAX_SPECIAL_KERNEL_PORT) {
- (void)job_assumes(ms->job, (errno = host_set_special_port(mhp, which_port, ms->port)) == KERN_SUCCESS);
- } else {
- job_log(ms->job, LOG_WARNING, "Tried to set a reserved host special port: %d", which_port);
- }
- }
- case LAUNCH_DATA_BOOL:
- b = launch_data_get_bool(obj);
- if (strcasecmp(key, LAUNCH_JOBKEY_MACH_ENTERKERNELDEBUGGERONCLOSE) == 0) {
- ms->debug_on_close = b;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_RESETATCLOSE) == 0) {
- ms->reset = b;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_HIDEUNTILCHECKIN) == 0) {
- ms->hide = b;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_EXCEPTIONSERVER) == 0) {
- job_set_exception_port(ms->job, ms->port);
- } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_KUNCSERVER) == 0) {
- ms->kUNCServer = b;
- (void)job_assumes(ms->job, host_set_UNDServer(mhp, ms->port) == KERN_SUCCESS);
- } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_PINGEVENTUPDATES) == 0) {
- ms->event_update_port = b;
- }
- break;
- case LAUNCH_DATA_STRING:
- if (strcasecmp(key, LAUNCH_JOBKEY_MACH_DRAINMESSAGESONCRASH) == 0) {
- const char *option = launch_data_get_string(obj);
- if (strcasecmp(option, "One") == 0) {
- ms->drain_one_on_crash = true;
- } else if (strcasecmp(option, "All") == 0) {
- ms->drain_all_on_crash = true;
- }
- }
- break;
- case LAUNCH_DATA_DICTIONARY:
- job_set_exception_port(ms->job, ms->port);
- break;
- default:
- break;
- }
-
- job_assumes(ms->job, launchd_mport_deallocate(mhp) == KERN_SUCCESS);
-}
-
-void
-machservice_setup(launch_data_t obj, const char *key, void *context)
-{
- job_t j = context;
- struct machservice *ms;
- mach_port_t p = MACH_PORT_NULL;
-
- if (unlikely(ms = jobmgr_lookup_service(j->mgr, key, false, 0))) {
- job_log(j, LOG_WARNING, "Conflict with job: %s over Mach service: %s", ms->job->label, key);
- return;
- }
-
- if (!job_assumes(j, (ms = machservice_new(j, key, &p, false)) != NULL)) {
- return;
- }
-
- ms->isActive = false;
- ms->upfront = true;
-
- if (launch_data_get_type(obj) == LAUNCH_DATA_DICTIONARY) {
- launch_data_dict_iterate(obj, machservice_setup_options, ms);
- }
-}
-
-jobmgr_t
-jobmgr_do_garbage_collection(jobmgr_t jm)
-{
- jobmgr_t jmi = NULL, jmn = NULL;
- SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) {
- jobmgr_do_garbage_collection(jmi);
- }
-
- if (!jm->shutting_down) {
- return jm;
- }
-
- if (SLIST_EMPTY(&jm->submgrs)) {
- jobmgr_log(jm, LOG_DEBUG, "No submanagers left.");
- } else {
- jobmgr_log(jm, LOG_DEBUG, "Still have submanagers.");
- SLIST_FOREACH(jmi, &jm->submgrs, sle) {
- jobmgr_log(jm, LOG_DEBUG, "Submanager: %s", jmi->name);
- }
- }
-
- size_t actives = 0;
- job_t ji = NULL, jn = NULL;
- LIST_FOREACH_SAFE(ji, &jm->jobs, sle, jn) {
- if (ji->anonymous) {
- continue;
- }
-
- /* Let the shutdown monitor be up until the very end. */
- if (ji->shutdown_monitor) {
- continue;
- }
-
- /* On our first pass through, open a transaction for all the jobs that
- * need to be dirty at shutdown. We'll close these transactions once the
- * jobs that do not need to be dirty at shutdown have all exited.
- */
- if (ji->dirty_at_shutdown && !jm->shutdown_jobs_dirtied) {
- job_open_shutdown_transaction(ji);
- }
-
- const char *active = job_active(ji);
- if (!active) {
- job_remove(ji);
- } else {
- job_log(ji, LOG_DEBUG, "Job is active: %s", active);
- job_stop(ji);
-
- if (ji->p && !ji->dirty_at_shutdown) {
- /* We really only care if the job has not yet been reaped.
- * There's no reason to delay shutdown if a Mach port has not
- * yet been sent back to us, for example. While we're shutting
- * all the "normal" jobs down, do not count the
- * dirty-at-shutdown jobs toward the total of actives.
- *
- * Note that there's a potential race here where we may not get
- * a port back in time, so that when we hit jobmgr_remove(), we
- * end up removing the job and then our attempt to close the
- * Mach port will fail. But at that point, the failure won't
- * even make it to the syslog, so not a big deal.
- */
- actives++;
- }
-
- if (ji->clean_kill) {
- job_log(ji, LOG_DEBUG, "Job was killed cleanly.");
- } else {
- job_log(ji, LOG_DEBUG, "Job was sent SIGTERM%s.", ji->sent_sigkill ? " and SIGKILL" : "");
- }
- }
- }
-
- jm->shutdown_jobs_dirtied = true;
- if (actives == 0) {
- if (!jm->shutdown_jobs_cleaned) {
- LIST_FOREACH(ji, &jm->jobs, sle) {
- if (!ji->anonymous) {
- job_close_shutdown_transaction(ji);
- actives++;
- }
- }
-
- jm->shutdown_jobs_cleaned = true;
- } else if (jm->monitor_shutdown && _s_shutdown_monitor) {
- /* The rest of shutdown has completed, so we can kill the shutdown
- * monitor now like it was any other job.
- */
- _s_shutdown_monitor->shutdown_monitor = false;
- actives = 1;
-
- job_log(_s_shutdown_monitor, LOG_NOTICE | LOG_CONSOLE, "Stopping shutdown monitor.");
- job_stop(_s_shutdown_monitor);
- _s_shutdown_monitor = NULL;
- }
- }
-
- jobmgr_t r = jm;
- if (SLIST_EMPTY(&jm->submgrs) && actives == 0) {
- jobmgr_log(jm, LOG_DEBUG, "Removing.");
- jobmgr_remove(jm);
- r = NULL;
- }
-
- return r;
-}
-
-void
-jobmgr_kill_stray_children(jobmgr_t jm, pid_t *p, size_t np)
-{
- /* I maintain that stray processes should be at the mercy of launchd during shutdown,
- * but nevertheless, things like diskimages-helper can stick around, and SIGKILLing
- * them can result in data loss. So we send SIGTERM to all the strays and don't wait
- * for them to exit before moving on.
- *
- * See rdar://problem/6562592
- */
- size_t i = 0;
- for (i = 0; i < np; i++) {
- if (p[i] != 0) {
- jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Sending SIGTERM to PID %u and continuing...", p[i]);
- (void)jobmgr_assumes(jm, runtime_kill(p[i], SIGTERM) != -1);
- }
- }
-}
-
-void
-jobmgr_log_stray_children(jobmgr_t jm, bool kill_strays)
-{
- size_t kp_skipped = 0, len = sizeof(pid_t) * get_kern_max_proc();
- pid_t *pids = NULL;
- int i = 0, kp_cnt = 0;
-
- if (likely(jm->parentmgr || !pid1_magic)) {
- return;
- }
-
- if (!jobmgr_assumes(jm, (pids = malloc(len)) != NULL)) {
- return;
- }
-
- runtime_ktrace0(RTKT_LAUNCHD_FINDING_ALL_STRAYS);
-
- if (!jobmgr_assumes(jm, (kp_cnt = proc_listallpids(pids, len)) != -1)) {
- goto out;
- }
-
- pid_t *ps = (pid_t *)calloc(sizeof(pid_t), kp_cnt);
- for (i = 0; i < kp_cnt; i++) {
- struct proc_bsdshortinfo proc;
- if (proc_pidinfo(pids[i], PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
- if (errno != ESRCH) {
- jobmgr_assumes(jm, errno == 0);
- }
-
- kp_skipped++;
- continue;
- }
-
- pid_t p_i = pids[i];
- pid_t pp_i = proc.pbsi_ppid;
- pid_t pg_i = proc.pbsi_pgid;
- const char *z = (proc.pbsi_status == SZOMB) ? "zombie " : "";
- const char *n = proc.pbsi_comm;
-
- if (unlikely(p_i == 0 || p_i == 1)) {
- kp_skipped++;
- continue;
- }
-
- if (_s_shutdown_monitor && pp_i == _s_shutdown_monitor->p) {
- kp_skipped++;
- continue;
- }
-
- /* We might have some jobs hanging around that we've decided to shut down in spite of. */
- job_t j = jobmgr_find_by_pid(jm, p_i, false);
- if (!j || (j && j->anonymous)) {
- jobmgr_log(jm, LOG_INFO | LOG_CONSOLE, "Stray %s%s at shutdown: PID %u PPID %u PGID %u %s", z, j ? "anonymous job" : "process", p_i, pp_i, pg_i, n);
-
- int status = 0;
- if (pp_i == getpid() && !jobmgr_assumes(jm, proc.pbsi_status != SZOMB)) {
- if (jobmgr_assumes(jm, waitpid(p_i, &status, WNOHANG) == 0)) {
- jobmgr_log(jm, LOG_INFO | LOG_CONSOLE, "Unreaped zombie stray exited with status %i.", WEXITSTATUS(status));
- }
- kp_skipped++;
- } else {
- job_t leader = jobmgr_find_by_pid(jm, pg_i, false);
- /* See rdar://problem/6745714. Some jobs have child processes that back kernel state,
- * so we don't want to terminate them. Long-term, I'd really like to provide shutdown
- * hints to the kernel along the way, so that it could shutdown certain subsystems when
- * their userspace emissaries go away, before the call to reboot(2).
- */
- if (leader && leader->ignore_pg_at_shutdown) {
- kp_skipped++;
- } else {
- ps[i] = p_i;
- }
- }
- } else {
- kp_skipped++;
- }
- }
-
- if ((kp_cnt - kp_skipped > 0) && kill_strays) {
- jobmgr_kill_stray_children(jm, ps, kp_cnt - kp_skipped);
- }
-
- free(ps);
-out:
- free(pids);
-}
-
-jobmgr_t
-jobmgr_parent(jobmgr_t jm)
-{
- return jm->parentmgr;
-}
-
-void
-job_uncork_fork(job_t j)
-{
- pid_t c = j->p;
-
- job_log(j, LOG_DEBUG, "Uncorking the fork().");
- /* this unblocks the child and avoids a race
- * between the above fork() and the kevent_mod() */
- (void)job_assumes(j, write(j->fork_fd, &c, sizeof(c)) == sizeof(c));
- (void)job_assumes(j, runtime_close(j->fork_fd) != -1);
- j->fork_fd = 0;
-}
-
-jobmgr_t
-jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bool sflag, const char *name, bool skip_init, mach_port_t asport)
-{
- mach_msg_size_t mxmsgsz;
- job_t bootstrapper = NULL;
- jobmgr_t jmr;
-
- launchd_assert(offsetof(struct jobmgr_s, kqjobmgr_callback) == 0);
-
- if (unlikely(jm && requestorport == MACH_PORT_NULL)) {
- jobmgr_log(jm, LOG_ERR, "Mach sub-bootstrap create request requires a requester port");
- return NULL;
- }
-
- jmr = calloc(1, sizeof(struct jobmgr_s) + (name ? (strlen(name) + 1) : NAME_MAX + 1));
-
- if (!jobmgr_assumes(jm, jmr != NULL)) {
- return NULL;
- }
-
- if (jm == NULL) {
- root_jobmgr = jmr;
- }
-
- jmr->kqjobmgr_callback = jobmgr_callback;
- strcpy(jmr->name_init, name ? name : "Under construction");
-
- jmr->req_port = requestorport;
-
- if ((jmr->parentmgr = jm)) {
- SLIST_INSERT_HEAD(&jm->submgrs, jmr, sle);
- }
-
- if (jm && !jobmgr_assumes(jmr, launchd_mport_notify_req(jmr->req_port, MACH_NOTIFY_DEAD_NAME) == KERN_SUCCESS)) {
- goto out_bad;
- }
-
- if (transfer_port != MACH_PORT_NULL) {
- (void)jobmgr_assumes(jmr, jm != NULL);
- jmr->jm_port = transfer_port;
- } else if (!jm && !pid1_magic) {
- char *trusted_fd = getenv(LAUNCHD_TRUSTED_FD_ENV);
- name_t service_buf;
-
- snprintf(service_buf, sizeof(service_buf), "com.apple.launchd.peruser.%u", getuid());
-
- if (!jobmgr_assumes(jmr, bootstrap_check_in(bootstrap_port, service_buf, &jmr->jm_port) == 0)) {
- goto out_bad;
- }
-
- if (trusted_fd) {
- int dfd, lfd = (int) strtol(trusted_fd, NULL, 10);
-
- if ((dfd = dup(lfd)) >= 0) {
- (void)jobmgr_assumes(jmr, runtime_close(dfd) != -1);
- (void)jobmgr_assumes(jmr, runtime_close(lfd) != -1);
- }
-
- unsetenv(LAUNCHD_TRUSTED_FD_ENV);
- }
-
- /* cut off the Libc cache, we don't want to deadlock against ourself */
- inherited_bootstrap_port = bootstrap_port;
- bootstrap_port = MACH_PORT_NULL;
- launchd_assert(launchd_mport_notify_req(inherited_bootstrap_port, MACH_NOTIFY_DEAD_NAME) == KERN_SUCCESS);
-
- /* We set this explicitly as we start each child */
- launchd_assert(launchd_set_bport(MACH_PORT_NULL) == KERN_SUCCESS);
- } else if (!jobmgr_assumes(jmr, launchd_mport_create_recv(&jmr->jm_port) == KERN_SUCCESS)) {
- goto out_bad;
- }
-
- if (!name) {
- sprintf(jmr->name_init, "%u", MACH_PORT_INDEX(jmr->jm_port));
- }
-
- /* Sigh... at the moment, MIG has maxsize == sizeof(reply union) */
- mxmsgsz = (typeof(mxmsgsz)) sizeof(union __RequestUnion__job_mig_protocol_vproc_subsystem);
- if (job_mig_protocol_vproc_subsystem.maxsize > mxmsgsz) {
- mxmsgsz = job_mig_protocol_vproc_subsystem.maxsize;
- }
-
- /* Total hacks. But the MIG server loop is too generic, and the more dynamic
- * parts of it haven't been tested, or if they have, it was a very long time
- * ago.
- */
- if (xpc_events_xpc_events_subsystem.maxsize > mxmsgsz) {
- mxmsgsz = xpc_events_xpc_events_subsystem.maxsize;
- }
- if (xpc_domain_xpc_domain_subsystem.maxsize > mxmsgsz) {
- mxmsgsz = xpc_domain_xpc_domain_subsystem.maxsize;
- }
-
- if (!jm) {
- (void)jobmgr_assumes(jmr, kevent_mod(SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, jmr) != -1);
- (void)jobmgr_assumes(jmr, kevent_mod(SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, jmr) != -1);
- (void)jobmgr_assumes(jmr, kevent_mod(SIGUSR2, EVFILT_SIGNAL, EV_ADD, 0, 0, jmr) != -1);
- (void)jobmgr_assumes(jmr, kevent_mod(0, EVFILT_FS, EV_ADD, VQ_MOUNT|VQ_UNMOUNT|VQ_UPDATE, 0, jmr) != -1);
- }
-
- if (name && !skip_init) {
- bootstrapper = jobmgr_init_session(jmr, name, sflag);
- }
-
- if (!bootstrapper || !bootstrapper->weird_bootstrap) {
- if (!jobmgr_assumes(jmr, runtime_add_mport(jmr->jm_port, protocol_vproc_server, mxmsgsz) == KERN_SUCCESS)) {
- goto out_bad;
- }
- }
-
- jobmgr_log(jmr, LOG_DEBUG, "Created job manager%s%s", jm ? " with parent: " : ".", jm ? jm->name : "");
-
- if (bootstrapper) {
- bootstrapper->asport = asport;
-
- jobmgr_log(jmr, LOG_DEBUG, "Bootstrapping new job manager with audit session %u", asport);
- (void)jobmgr_assumes(jmr, job_dispatch(bootstrapper, true) != NULL);
- } else {
- jmr->req_asport = asport;
- }
-
- if (asport != MACH_PORT_NULL) {
- (void)jobmgr_assumes(jmr, launchd_mport_copy_send(asport) == KERN_SUCCESS);
- }
-
- if (jmr->parentmgr) {
- runtime_add_weak_ref();
- }
-
- return jmr;
-
-out_bad:
- if (jmr) {
- jobmgr_remove(jmr);
- if (jm == NULL) {
- root_jobmgr = NULL;
- }
- }
- return NULL;
-}
-
-#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__
-jobmgr_t
-jobmgr_new_xpc_singleton_domain(jobmgr_t jm, name_t name)
-{
- jobmgr_t new = NULL;
-
- /* These job managers are basically singletons, so we use the root Mach
- * bootstrap port as their requestor ports so they'll never go away.
- */
- mach_port_t req_port = root_jobmgr->jm_port;
- if (jobmgr_assumes(jm, launchd_mport_make_send(req_port) == KERN_SUCCESS)) {
- new = jobmgr_new(root_jobmgr, req_port, MACH_PORT_NULL, false, name, true, MACH_PORT_NULL);
- if (new) {
- new->properties |= BOOTSTRAP_PROPERTY_XPC_SINGLETON;
- new->properties |= BOOTSTRAP_PROPERTY_XPC_DOMAIN;
- new->xpc_singleton = true;
- }
- }
-
- return new;
-}
-
-jobmgr_t
-jobmgr_find_xpc_per_user_domain(jobmgr_t jm, uid_t uid)
-{
- jobmgr_t jmi = NULL;
- LIST_FOREACH(jmi, &_s_xpc_user_domains, xpc_le) {
- if (jmi->req_euid == uid) {
- return jmi;
- }
- }
-
- name_t name;
- (void)snprintf(name, sizeof(name), "com.apple.xpc.domain.peruser.%u", uid);
- jmi = jobmgr_new_xpc_singleton_domain(jm, name);
- if (jobmgr_assumes(jm, jmi != NULL)) {
- /* We need to create a per-user launchd for this UID if there isn't one
- * already so we can grab the bootstrap port.
- */
- job_t puj = jobmgr_lookup_per_user_context_internal(NULL, uid, &jmi->req_bsport);
- if (jobmgr_assumes(jmi, puj != NULL)) {
- (void)jobmgr_assumes(jmi, launchd_mport_copy_send(puj->asport) == KERN_SUCCESS);
- (void)jobmgr_assumes(jmi, launchd_mport_copy_send(jmi->req_bsport) == KERN_SUCCESS);
- jmi->shortdesc = "per-user";
- jmi->req_asport = puj->asport;
- jmi->req_asid = puj->asid;
- jmi->req_euid = uid;
- jmi->req_egid = -1;
-
- LIST_INSERT_HEAD(&_s_xpc_user_domains, jmi, xpc_le);
- } else {
- jobmgr_remove(jmi);
- }
- }
-
- return jmi;
-}
-
-jobmgr_t
-jobmgr_find_xpc_per_session_domain(jobmgr_t jm, au_asid_t asid)
-{
- jobmgr_t jmi = NULL;
- LIST_FOREACH(jmi, &_s_xpc_session_domains, xpc_le) {
- if (jmi->req_asid == asid) {
- return jmi;
- }
- }
-
- name_t name;
- (void)snprintf(name, sizeof(name), "com.apple.xpc.domain.persession.%i", asid);
- jmi = jobmgr_new_xpc_singleton_domain(jm, name);
- if (jobmgr_assumes(jm, jmi != NULL)) {
- (void)jobmgr_assumes(jmi, launchd_mport_make_send(root_jobmgr->jm_port) == KERN_SUCCESS);
- jmi->shortdesc = "per-session";
- jmi->req_bsport = root_jobmgr->jm_port;
- (void)jobmgr_assumes(jmi, audit_session_port(asid, &jmi->req_asport) == 0);
- jmi->req_asid = asid;
- jmi->req_euid = -1;
- jmi->req_egid = -1;
-
- LIST_INSERT_HEAD(&_s_xpc_session_domains, jmi, xpc_le);
- } else {
- jobmgr_remove(jmi);
- }
-
- return jmi;
-}
-#endif /* __LAUNCH_DISABLE_XPC_SUPPORT__ */
-
-job_t
-jobmgr_init_session(jobmgr_t jm, const char *session_type, bool sflag)
-{
- const char *bootstrap_tool[] = { "/bin/launchctl", "bootstrap", "-S", session_type, sflag ? "-s" : NULL, NULL };
- char thelabel[1000];
- job_t bootstrapper;
-
- snprintf(thelabel, sizeof(thelabel), "com.apple.launchctl.%s", session_type);
- bootstrapper = job_new(jm, thelabel, NULL, bootstrap_tool);
-
- if (jobmgr_assumes(jm, bootstrapper != NULL) && (jm->parentmgr || !pid1_magic)) {
- bootstrapper->is_bootstrapper = true;
- char buf[100];
-
- /* <rdar://problem/5042202> launchd-201: can't ssh in with AFP OD account (hangs) */
- snprintf(buf, sizeof(buf), "0x%X:0:0", getuid());
- envitem_new(bootstrapper, "__CF_USER_TEXT_ENCODING", buf, false, false);
- bootstrapper->weird_bootstrap = true;
- (void)jobmgr_assumes(jm, job_setup_machport(bootstrapper));
- } else if (bootstrapper && strncmp(session_type, VPROCMGR_SESSION_SYSTEM, sizeof(VPROCMGR_SESSION_SYSTEM)) == 0) {
- bootstrapper->is_bootstrapper = true;
- if (jobmgr_assumes(jm, pid1_magic)) {
- /* Have our system bootstrapper print out to the console. */
- bootstrapper->stdoutpath = strdup(_PATH_CONSOLE);
- bootstrapper->stderrpath = strdup(_PATH_CONSOLE);
-
- if (g_console) {
- (void)jobmgr_assumes(jm, kevent_mod((uintptr_t)fileno(g_console), EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_REVOKE, 0, jm) != -1);
- }
- }
- }
-
- jm->session_initialized = true;
- return bootstrapper;
-}
-
-jobmgr_t
-jobmgr_delete_anything_with_port(jobmgr_t jm, mach_port_t port)
-{
- struct machservice *ms, *next_ms;
- jobmgr_t jmi, jmn;
-
- /* Mach ports, unlike Unix descriptors, are reference counted. In other
- * words, when some program hands us a second or subsequent send right
- * to a port we already have open, the Mach kernel gives us the same
- * port number back and increments an reference count associated with
- * the port. This forces us, when discovering that a receive right at
- * the other end has been deleted, to wander all of our objects to see
- * what weird places clients might have handed us the same send right
- * to use.
- */
-
- if (jm == root_jobmgr) {
- if (port == inherited_bootstrap_port) {
- (void)jobmgr_assumes(jm, launchd_mport_deallocate(port) == KERN_SUCCESS);
- inherited_bootstrap_port = MACH_PORT_NULL;
-
- return jobmgr_shutdown(jm);
- }
-
- LIST_FOREACH_SAFE(ms, &port_hash[HASH_PORT(port)], port_hash_sle, next_ms) {
- if (ms->port == port && !ms->recv) {
- machservice_delete(ms->job, ms, true);
- }
- }
- }
-
- SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) {
- jobmgr_delete_anything_with_port(jmi, port);
- }
-
- if (jm->req_port == port) {
- jobmgr_log(jm, LOG_DEBUG, "Request port died: %i", MACH_PORT_INDEX(port));
- return jobmgr_shutdown(jm);
- }
-
- return jm;
-}
-
-struct machservice *
-jobmgr_lookup_service(jobmgr_t jm, const char *name, bool check_parent, pid_t target_pid)
-{
- struct machservice *ms;
- job_t target_j;
-
- jobmgr_log(jm, LOG_DEBUG, "Looking up %sservice %s", target_pid ? "per-PID " : "", name);
-
- if (target_pid) {
- /* This is a hack to let FileSyncAgent look up per-PID Mach services from the Background
- * bootstrap in other bootstraps.
- */
-
- /* Start in the given bootstrap. */
- if (unlikely((target_j = jobmgr_find_by_pid(jm, target_pid, false)) == NULL)) {
- /* If we fail, do a deep traversal. */
- if (unlikely((target_j = jobmgr_find_by_pid_deep(root_jobmgr, target_pid, true)) == NULL)) {
- jobmgr_log(jm, LOG_DEBUG, "Didn't find PID %i", target_pid);
- return NULL;
- }
- }
-
- SLIST_FOREACH(ms, &target_j->machservices, sle) {
- if (ms->per_pid && strcmp(name, ms->name) == 0) {
- return ms;
- }
- }
-
- job_log(target_j, LOG_DEBUG, "Didn't find per-PID Mach service: %s", name);
- return NULL;
- }
-
- jobmgr_t where2look = jm;
- /* XPC domains are separate from Mach bootstraps. */
- if (!(jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
- if (g_flat_mach_namespace && !(jm->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET)) {
- where2look = root_jobmgr;
- }
- }
-
- LIST_FOREACH(ms, &where2look->ms_hash[hash_ms(name)], name_hash_sle) {
- if (!ms->per_pid && strcmp(name, ms->name) == 0) {
- return ms;
- }
- }
-
- if (jm->parentmgr == NULL || !check_parent) {
- return NULL;
- }
-
- return jobmgr_lookup_service(jm->parentmgr, name, true, 0);
-}
-
-mach_port_t
-machservice_port(struct machservice *ms)
-{
- return ms->port;
-}
-
-job_t
-machservice_job(struct machservice *ms)
-{
- return ms->job;
-}
-
-bool
-machservice_hidden(struct machservice *ms)
-{
- return ms->hide;
-}
-
-bool
-machservice_active(struct machservice *ms)
-{
- return ms->isActive;
-}
-
-const char *
-machservice_name(struct machservice *ms)
-{
- return ms->name;
-}
-
-void
-machservice_drain_port(struct machservice *ms)
-{
- bool drain_one = ms->drain_one_on_crash;
- bool drain_all = ms->drain_all_on_crash;
-
- if (!job_assumes(ms->job, (drain_one || drain_all) == true)) {
- return;
- }
-
- job_log(ms->job, LOG_INFO, "Draining %s...", ms->name);
-
- char req_buff[sizeof(union __RequestUnion__catch_mach_exc_subsystem) * 2];
- char rep_buff[sizeof(union __ReplyUnion__catch_mach_exc_subsystem)];
- mig_reply_error_t *req_hdr = (mig_reply_error_t *)&req_buff;
- mig_reply_error_t *rep_hdr = (mig_reply_error_t *)&rep_buff;
-
- mach_msg_return_t mr = ~MACH_MSG_SUCCESS;
-
- do {
- /* This should be a direct check on the Mach service to see if it's an exception-handling
- * port, and it will break things if ReportCrash or SafetyNet start advertising other
- * Mach services. But for now, it should be okay.
- */
- if (ms->job->alt_exc_handler || ms->job->internal_exc_handler) {
- mr = launchd_exc_runtime_once(ms->port, sizeof(req_buff), sizeof(rep_buff), req_hdr, rep_hdr, 0);
- } else {
- mach_msg_options_t options = MACH_RCV_MSG |
- MACH_RCV_TIMEOUT ;
-
- mr = mach_msg((mach_msg_header_t *)req_hdr, options, 0, sizeof(req_buff), ms->port, 0, MACH_PORT_NULL);
- switch (mr) {
- case MACH_MSG_SUCCESS:
- mach_msg_destroy((mach_msg_header_t *)req_hdr);
- break;
- case MACH_RCV_TIMED_OUT:
- break;
- case MACH_RCV_TOO_LARGE:
- runtime_syslog(LOG_WARNING, "Tried to receive message that was larger than %lu bytes", sizeof(req_buff));
- break;
- default:
- break;
- }
- }
- } while (drain_all && mr != MACH_RCV_TIMED_OUT);
-}
-
-void
-machservice_delete(job_t j, struct machservice *ms, bool port_died)
-{
- if (ms->alias) {
- /* HACK: Egregious code duplication. But dealing with aliases is a
- * pretty simple affair since they can't and shouldn't have any complex
- * behaviors associated with them.
- */
- LIST_REMOVE(ms, name_hash_sle);
- SLIST_REMOVE(&j->machservices, ms, machservice, sle);
- free(ms);
- return;
- }
-
- if (unlikely(ms->debug_on_close)) {
- job_log(j, LOG_NOTICE, "About to enter kernel debugger because of Mach port: 0x%x", ms->port);
- (void)job_assumes(j, host_reboot(mach_host_self(), HOST_REBOOT_DEBUGGER) == KERN_SUCCESS);
- }
-
- if (ms->recv && job_assumes(j, !machservice_active(ms))) {
- job_log(j, LOG_DEBUG, "Closing receive right for %s", ms->name);
- (void)job_assumes(j, launchd_mport_close_recv(ms->port) == KERN_SUCCESS);
- }
-
- (void)job_assumes(j, launchd_mport_deallocate(ms->port) == KERN_SUCCESS);
-
- if (unlikely(ms->port == the_exception_server)) {
- the_exception_server = 0;
- }
-
- job_log(j, LOG_DEBUG, "Mach service deleted%s: %s", port_died ? " (port died)" : "", ms->name);
-
- if (ms->special_port_num) {
- SLIST_REMOVE(&special_ports, ms, machservice, special_port_sle);
- }
- SLIST_REMOVE(&j->machservices, ms, machservice, sle);
-
- if (!(j->dedicated_instance || ms->event_channel)) {
- LIST_REMOVE(ms, name_hash_sle);
- }
- LIST_REMOVE(ms, port_hash_sle);
-
- free(ms);
-}
-
-void
-machservice_request_notifications(struct machservice *ms)
-{
- mach_msg_id_t which = MACH_NOTIFY_DEAD_NAME;
-
- ms->isActive = true;
-
- if (ms->recv) {
- which = MACH_NOTIFY_PORT_DESTROYED;
- job_checkin(ms->job);
- }
-
- (void)job_assumes(ms->job, launchd_mport_notify_req(ms->port, which) == KERN_SUCCESS);
-}
-
-#define NELEM(x) (sizeof(x)/sizeof(x[0]))
-#define END_OF(x) (&(x)[NELEM(x)])
-
-char **
-mach_cmd2argv(const char *string)
-{
- char *argv[100], args[1000];
- const char *cp;
- char *argp = args, term, **argv_ret, *co;
- unsigned int nargs = 0, i;
-
- for (cp = string; *cp;) {
- while (isspace(*cp))
- cp++;
- term = (*cp == '"') ? *cp++ : '\0';
- if (nargs < NELEM(argv)) {
- argv[nargs++] = argp;
- }
- while (*cp && (term ? *cp != term : !isspace(*cp)) && argp < END_OF(args)) {
- if (*cp == '\\') {
- cp++;
- }
- *argp++ = *cp;
- if (*cp) {
- cp++;
- }
- }
- *argp++ = '\0';
- }
- argv[nargs] = NULL;
-
- if (nargs == 0) {
- return NULL;
- }
-
- argv_ret = malloc((nargs + 1) * sizeof(char *) + strlen(string) + 1);
-
- if (!launchd_assumes(argv_ret != NULL)) {
- return NULL;
- }
-
- co = (char *)argv_ret + (nargs + 1) * sizeof(char *);
-
- for (i = 0; i < nargs; i++) {
- strcpy(co, argv[i]);
- argv_ret[i] = co;
- co += strlen(argv[i]) + 1;
- }
- argv_ret[i] = NULL;
-
- return argv_ret;
-}
-
-void
-job_checkin(job_t j)
-{
- j->checkedin = true;
-}
-
-bool job_is_god(job_t j)
-{
- return j->embedded_special_privileges;
-}
-
-bool
-job_ack_port_destruction(mach_port_t p)
-{
- struct machservice *ms;
- job_t j;
-
- LIST_FOREACH(ms, &port_hash[HASH_PORT(p)], port_hash_sle) {
- if (ms->recv && (ms->port == p)) {
- break;
- }
- }
-
- if (!jobmgr_assumes(root_jobmgr, ms != NULL)) {
- return false;
- }
-
- j = ms->job;
-
- jobmgr_log(root_jobmgr, LOG_DEBUG, "Receive right returned to us: %s", ms->name);
-
- /* Without being the exception handler, NOTE_EXIT is our only way to tell if the job
- * crashed, and we can't rely on NOTE_EXIT always being processed after all the job's
- * receive rights have been returned.
- *
- * So when we get receive rights back, check to see if the job has been reaped yet. If
- * not, then we add this service to a list of services to be drained on crash if it's
- * requested that behavior. So, for a job with N receive rights all requesting that they
- * be drained on crash, we can safely handle the following sequence of events.
- *
- * ReceiveRight0Returned
- * ReceiveRight1Returned
- * ReceiveRight2Returned
- * NOTE_EXIT (reap, get exit status)
- * ReceiveRight3Returned
- * .
- * .
- * .
- * ReceiveRight(N - 1)Returned
- */
-
- if (ms->drain_one_on_crash || ms->drain_all_on_crash) {
- if (j->crashed && j->reaped) {
- job_log(j, LOG_DEBUG, "Job has crashed. Draining port...");
- machservice_drain_port(ms);
- } else if (!(j->crashed || j->reaped)) {
- job_log(j, LOG_DEBUG, "Job's exit status is still unknown. Deferring drain.");
- }
- }
-
- /* If we get this notification after the job has been reaped, then we want to ping
- * the event port to keep things going.
- */
- if (ms->event_update_port && !j->p && job_assumes(j, j->event_monitor)) {
- if (_s_event_update_port == MACH_PORT_NULL) {
- (void)job_assumes(j, launchd_mport_make_send_once(ms->port, &_s_event_update_port) == KERN_SUCCESS);
- }
- eventsystem_ping();
- }
-
- ms->isActive = false;
- if (ms->delete_on_destruction) {
- machservice_delete(j, ms, false);
- } else if (ms->reset) {
- machservice_resetport(j, ms);
- }
-
- job_dispatch(j, false);
-
- root_jobmgr = jobmgr_do_garbage_collection(root_jobmgr);
-
- return true;
-}
-
-void
-job_ack_no_senders(job_t j)
-{
- j->priv_port_has_senders = false;
-
- (void)job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS);
- j->j_port = 0;
-
- job_log(j, LOG_DEBUG, "No more senders on privileged Mach bootstrap port");
-
- job_dispatch(j, false);
-}
-
-bool
-semaphoreitem_new(job_t j, semaphore_reason_t why, const char *what)
-{
- struct semaphoreitem *si;
- size_t alloc_sz = sizeof(struct semaphoreitem);
-
- if (what) {
- alloc_sz += strlen(what) + 1;
- }
-
- if (!job_assumes(j, si = calloc(1, alloc_sz))) {
- return false;
- }
-
- si->fd = -1;
- si->why = why;
-
- if (what) {
- strcpy(si->what_init, what);
- }
-
- SLIST_INSERT_HEAD(&j->semaphores, si, sle);
-
- if ((why == OTHER_JOB_ENABLED || why == OTHER_JOB_DISABLED) && !j->nosy) {
- job_log(j, LOG_DEBUG, "Job is interested in \"%s\".", what);
- SLIST_INSERT_HEAD(&s_curious_jobs, j, curious_jobs_sle);
- j->nosy = true;
- }
-
- semaphoreitem_runtime_mod_ref(si, true);
-
- return true;
-}
-
-void
-semaphoreitem_runtime_mod_ref(struct semaphoreitem *si, bool add)
-{
- /*
- * External events need to be tracked.
- * Internal events do NOT need to be tracked.
- */
-
- switch (si->why) {
- case SUCCESSFUL_EXIT:
- case FAILED_EXIT:
- case OTHER_JOB_ENABLED:
- case OTHER_JOB_DISABLED:
- case OTHER_JOB_ACTIVE:
- case OTHER_JOB_INACTIVE:
- return;
- default:
- break;
- }
-
- if (add) {
- runtime_add_weak_ref();
- } else {
- runtime_del_weak_ref();
- }
-}
-
-void
-semaphoreitem_delete(job_t j, struct semaphoreitem *si)
-{
- semaphoreitem_runtime_mod_ref(si, false);
-
- SLIST_REMOVE(&j->semaphores, si, semaphoreitem, sle);
-
- if (si->fd != -1) {
- (void)job_assumes(j, runtime_close(si->fd) != -1);
- }
-
- /* We'll need to rethink this if it ever becomes possible to dynamically add or remove semaphores. */
- if ((si->why == OTHER_JOB_ENABLED || si->why == OTHER_JOB_DISABLED) && j->nosy) {
- j->nosy = false;
- SLIST_REMOVE(&s_curious_jobs, j, job_s, curious_jobs_sle);
- }
-
- free(si);
-}
-
-void
-semaphoreitem_setup_dict_iter(launch_data_t obj, const char *key, void *context)
-{
- struct semaphoreitem_dict_iter_context *sdic = context;
- semaphore_reason_t why;
-
- why = launch_data_get_bool(obj) ? sdic->why_true : sdic->why_false;
-
- semaphoreitem_new(sdic->j, why, key);
-}
-
-void
-semaphoreitem_setup(launch_data_t obj, const char *key, void *context)
-{
- struct semaphoreitem_dict_iter_context sdic = { context, 0, 0 };
- job_t j = context;
- semaphore_reason_t why;
-
- switch (launch_data_get_type(obj)) {
- case LAUNCH_DATA_BOOL:
- if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_NETWORKSTATE) == 0) {
- why = launch_data_get_bool(obj) ? NETWORK_UP : NETWORK_DOWN;
- semaphoreitem_new(j, why, NULL);
- } else if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT) == 0) {
- why = launch_data_get_bool(obj) ? SUCCESSFUL_EXIT : FAILED_EXIT;
- semaphoreitem_new(j, why, NULL);
- j->start_pending = true;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_AFTERINITIALDEMAND) == 0) {
- j->needs_kickoff = launch_data_get_bool(obj);
- } else if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_CRASHED) == 0) {
- why = launch_data_get_bool(obj) ? CRASHED : DID_NOT_CRASH;
- semaphoreitem_new(j, why, NULL);
- j->start_pending = true;
- } else {
- (void)job_assumes(j, false);
- }
- break;
- case LAUNCH_DATA_DICTIONARY:
- if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_PATHSTATE) == 0) {
- sdic.why_true = PATH_EXISTS;
- sdic.why_false = PATH_MISSING;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBACTIVE) == 0) {
- sdic.why_true = OTHER_JOB_ACTIVE;
- sdic.why_false = OTHER_JOB_INACTIVE;
- } else if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBENABLED) == 0) {
- sdic.why_true = OTHER_JOB_ENABLED;
- sdic.why_false = OTHER_JOB_DISABLED;
- } else {
- (void)job_assumes(j, false);
- break;
- }
-
- launch_data_dict_iterate(obj, semaphoreitem_setup_dict_iter, &sdic);
- break;
- default:
- (void)job_assumes(j, false);
- break;
- }
-}
-
-bool
-externalevent_new(job_t j, struct eventsystem *sys, char *evname, launch_data_t event)
-{
- struct externalevent *ee = (struct externalevent *)calloc(1, sizeof(struct externalevent) + strlen(evname) + 1);
- if (job_assumes(j, ee != NULL)) {
- ee->event = launch_data_copy(event);
- if (job_assumes(j, ee->event != NULL)) {
- strcpy(ee->name, evname);
- ee->job = j;
- ee->id = sys->curid;
- ee->sys = sys;
- ee->state = false;
- ee->wanted_state = true;
- sys->curid++;
-
- LIST_INSERT_HEAD(&j->events, ee, job_le);
- LIST_INSERT_HEAD(&sys->events, ee, sys_le);
-
- job_log(j, LOG_DEBUG, "New event: %s:%s", sys->name, evname);
- } else {
- free(ee);
- ee = NULL;
- }
- }
-
- eventsystem_ping();
- return ee;
-}
-
-void
-externalevent_delete(struct externalevent *ee)
-{
- launch_data_free(ee->event);
- LIST_REMOVE(ee, job_le);
- LIST_REMOVE(ee, sys_le);
-
- free(ee);
-
- eventsystem_ping();
-}
-
-void
-externalevent_setup(launch_data_t obj, const char *key, void *context)
-{
- struct externalevent_iter_ctx *ctx = (struct externalevent_iter_ctx *)context;
- (void)job_assumes(ctx->j, externalevent_new(ctx->j, ctx->sys, (char *)key, obj));
-}
-
-struct externalevent *
-externalevent_find(const char *sysname, uint64_t id)
-{
- struct externalevent *ei = NULL;
-
- struct eventsystem *es = eventsystem_find(sysname);
- if (launchd_assumes(es != NULL)) {
- LIST_FOREACH(ei, &es->events, sys_le) {
- if (ei->id == id) {
- break;
- }
- }
- }
-
- return ei;
-}
-
-struct eventsystem *
-eventsystem_new(const char *name)
-{
- struct eventsystem *es = (struct eventsystem *)calloc(1, sizeof(struct eventsystem) + strlen(name) + 1);
- if (launchd_assumes(es != NULL)) {
- strcpy(es->name, name);
- LIST_INSERT_HEAD(&_s_event_systems, es, global_le);
- }
-
- return es;
-}
-
-void
-eventsystem_delete(struct eventsystem *es)
-{
- struct externalevent *ei = NULL;
- while ((ei = LIST_FIRST(&es->events))) {
- externalevent_delete(ei);
- }
-
- LIST_REMOVE(es, global_le);
-
- free(es);
-}
-
-void
-eventsystem_setup(launch_data_t obj, const char *key, void *context)
-{
- job_t j = (job_t)context;
- if (!job_assumes(j, launch_data_get_type(obj) == LAUNCH_DATA_DICTIONARY)) {
- return;
- }
-
- struct eventsystem *sys = eventsystem_find(key);
- if (unlikely(sys == NULL)) {
- sys = eventsystem_new(key);
- job_log(j, LOG_DEBUG, "New event system: %s", key);
- }
-
- if (job_assumes(j, sys != NULL)) {
- struct externalevent_iter_ctx ctx = {
- .j = j,
- .sys = sys,
- };
- launch_data_dict_iterate(obj, externalevent_setup, &ctx);
- sys->has_updates = true;
- }
-}
-
-struct eventsystem *
-eventsystem_find(const char *name)
-{
- struct eventsystem *esi = NULL;
- LIST_FOREACH(esi, &_s_event_systems, global_le) {
- if (strcmp(name, esi->name) == 0) {
- break;
- }
- }
-
- return esi;
-}
-
-void
-eventsystem_ping(void)
-{
- /* We don't wrap this in an assumes() macro because we could potentially
- * call this function many times before the helper job gets back to us
- * and gives us another send-once right. So if it's MACH_PORT_NULL, that
- * means that we've sent a ping, but the helper hasn't yet checked in to
- * get the new set of notifications.
- */
- if (_s_event_update_port != MACH_PORT_NULL) {
- kern_return_t kr = helper_downcall_ping(_s_event_update_port);
- if (kr != KERN_SUCCESS) {
- runtime_syslog(LOG_NOTICE, "helper_downcall_ping(): kr = 0x%x", kr);
- }
- _s_event_update_port = MACH_PORT_NULL;
- }
-}
-
-void
-jobmgr_dispatch_all_semaphores(jobmgr_t jm)
-{
- jobmgr_t jmi, jmn;
- job_t ji, jn;
-
-
- SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) {
- jobmgr_dispatch_all_semaphores(jmi);
- }
-
- LIST_FOREACH_SAFE(ji, &jm->jobs, sle, jn) {
- if (!SLIST_EMPTY(&ji->semaphores)) {
- job_dispatch(ji, false);
- }
- }
-}
-
-time_t
-cronemu(int mon, int mday, int hour, int min)
-{
- struct tm workingtm;
- time_t now;
-
- now = time(NULL);
- workingtm = *localtime(&now);
-
- workingtm.tm_isdst = -1;
- workingtm.tm_sec = 0;
- workingtm.tm_min++;
-
- while (!cronemu_mon(&workingtm, mon, mday, hour, min)) {
- workingtm.tm_year++;
- workingtm.tm_mon = 0;
- workingtm.tm_mday = 1;
- workingtm.tm_hour = 0;
- workingtm.tm_min = 0;
- mktime(&workingtm);
- }
-
- return mktime(&workingtm);
-}
-
-time_t
-cronemu_wday(int wday, int hour, int min)
-{
- struct tm workingtm;
- time_t now;
-
- now = time(NULL);
- workingtm = *localtime(&now);
-
- workingtm.tm_isdst = -1;
- workingtm.tm_sec = 0;
- workingtm.tm_min++;
-
- if (wday == 7) {
- wday = 0;
- }
-
- while (!(workingtm.tm_wday == wday && cronemu_hour(&workingtm, hour, min))) {
- workingtm.tm_mday++;
- workingtm.tm_hour = 0;
- workingtm.tm_min = 0;
- mktime(&workingtm);
- }
-
- return mktime(&workingtm);
-}
-
-bool
-cronemu_mon(struct tm *wtm, int mon, int mday, int hour, int min)
-{
- if (mon == -1) {
- struct tm workingtm = *wtm;
- int carrytest;
-
- while (!cronemu_mday(&workingtm, mday, hour, min)) {
- workingtm.tm_mon++;
- workingtm.tm_mday = 1;
- workingtm.tm_hour = 0;
- workingtm.tm_min = 0;
- carrytest = workingtm.tm_mon;
- mktime(&workingtm);
- if (carrytest != workingtm.tm_mon) {
- return false;
- }
- }
- *wtm = workingtm;
- return true;
- }
-
- if (mon < wtm->tm_mon) {
- return false;
- }
-
- if (mon > wtm->tm_mon) {
- wtm->tm_mon = mon;
- wtm->tm_mday = 1;
- wtm->tm_hour = 0;
- wtm->tm_min = 0;
- }
-
- return cronemu_mday(wtm, mday, hour, min);
-}
-
-bool
-cronemu_mday(struct tm *wtm, int mday, int hour, int min)
-{
- if (mday == -1) {
- struct tm workingtm = *wtm;
- int carrytest;
-
- while (!cronemu_hour(&workingtm, hour, min)) {
- workingtm.tm_mday++;
- workingtm.tm_hour = 0;
- workingtm.tm_min = 0;
- carrytest = workingtm.tm_mday;
- mktime(&workingtm);
- if (carrytest != workingtm.tm_mday) {
- return false;
- }
- }
- *wtm = workingtm;
- return true;
- }
-
- if (mday < wtm->tm_mday) {
- return false;
- }
-
- if (mday > wtm->tm_mday) {
- wtm->tm_mday = mday;
- wtm->tm_hour = 0;
- wtm->tm_min = 0;
- }
-
- return cronemu_hour(wtm, hour, min);
-}
-
-bool
-cronemu_hour(struct tm *wtm, int hour, int min)
-{
- if (hour == -1) {
- struct tm workingtm = *wtm;
- int carrytest;
-
- while (!cronemu_min(&workingtm, min)) {
- workingtm.tm_hour++;
- workingtm.tm_min = 0;
- carrytest = workingtm.tm_hour;
- mktime(&workingtm);
- if (carrytest != workingtm.tm_hour) {
- return false;
- }
- }
- *wtm = workingtm;
- return true;
- }
-
- if (hour < wtm->tm_hour) {
- return false;
- }
-
- if (hour > wtm->tm_hour) {
- wtm->tm_hour = hour;
- wtm->tm_min = 0;
- }
-
- return cronemu_min(wtm, min);
-}
-
-bool
-cronemu_min(struct tm *wtm, int min)
-{
- if (min == -1) {
- return true;
- }
-
- if (min < wtm->tm_min) {
- return false;
- }
-
- if (min > wtm->tm_min) {
- wtm->tm_min = min;
- }
-
- return true;
-}
-
-kern_return_t
-job_mig_setup_shmem(job_t j, mach_port_t *shmem_port)
-{
- memory_object_size_t size_of_page, size_of_page_orig;
- vm_address_t vm_addr;
- kern_return_t kr;
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (unlikely(j->anonymous)) {
- job_log(j, LOG_DEBUG, "Anonymous job tried to setup shared memory");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- if (unlikely(j->shmem)) {
- job_log(j, LOG_ERR, "Tried to setup shared memory more than once");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- size_of_page_orig = size_of_page = getpagesize();
-
- kr = vm_allocate(mach_task_self(), &vm_addr, size_of_page, true);
-
- if (!job_assumes(j, kr == 0)) {
- return kr;
- }
-
- j->shmem = (typeof(j->shmem))vm_addr;
- j->shmem->vp_shmem_standby_timeout = j->timeout;
-
- kr = mach_make_memory_entry_64(mach_task_self(), &size_of_page,
- (memory_object_offset_t)vm_addr, VM_PROT_READ|VM_PROT_WRITE, shmem_port, 0);
-
- if (job_assumes(j, kr == 0)) {
- (void)job_assumes(j, size_of_page == size_of_page_orig);
- }
-
- /* no need to inherit this in child processes */
- (void)job_assumes(j, vm_inherit(mach_task_self(), (vm_address_t)j->shmem, size_of_page_orig, VM_INHERIT_NONE) == 0);
-
- return kr;
-}
-
-kern_return_t
-job_mig_create_server(job_t j, cmd_t server_cmd, uid_t server_uid, boolean_t on_demand, mach_port_t *server_portp)
-{
- struct ldcred *ldc = runtime_get_caller_creds();
- job_t js;
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (unlikely(j->deny_job_creation)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
-#if HAVE_SANDBOX
- const char **argv = (const char **)mach_cmd2argv(server_cmd);
- if (unlikely(argv == NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
- if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_PATH, argv[0]) > 0)) {
- free(argv);
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- free(argv);
-#endif
-
- job_log(j, LOG_DEBUG, "Server create attempt: %s", server_cmd);
-
- if (pid1_magic) {
- if (ldc->euid || ldc->uid) {
- job_log(j, LOG_WARNING, "Server create attempt moved to per-user launchd: %s", server_cmd);
- return VPROC_ERR_TRY_PER_USER;
- }
- } else {
- if (unlikely(server_uid != getuid())) {
- job_log(j, LOG_WARNING, "Server create: \"%s\": As UID %d, we will not be able to switch to UID %d",
- server_cmd, getuid(), server_uid);
- }
- server_uid = 0; /* zero means "do nothing" */
- }
-
- js = job_new_via_mach_init(j, server_cmd, server_uid, on_demand);
-
- if (unlikely(js == NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- *server_portp = js->j_port;
- return BOOTSTRAP_SUCCESS;
-}
-
-kern_return_t
-job_mig_send_signal(job_t j, mach_port_t srp, name_t targetlabel, int sig)
-{
- struct ldcred *ldc = runtime_get_caller_creds();
- job_t otherj;
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (unlikely(ldc->euid != 0 && ldc->euid != getuid()) || j->deny_job_creation) {
- #if TARGET_OS_EMBEDDED
- if (!j->embedded_special_privileges) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- #else
- return BOOTSTRAP_NOT_PRIVILEGED;
- #endif
- }
-
-#if HAVE_SANDBOX
- if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-#endif
-
- if (unlikely(!(otherj = job_find(NULL, targetlabel)))) {
- return BOOTSTRAP_UNKNOWN_SERVICE;
- }
-
-#if TARGET_OS_EMBEDDED
- if (j->embedded_special_privileges && strcmp(j->username, otherj->username) != 0) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-#endif
-
- if (sig == VPROC_MAGIC_UNLOAD_SIGNAL) {
- bool do_block = otherj->p;
-
- if (otherj->anonymous) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- job_remove(otherj);
-
- if (do_block) {
- job_log(j, LOG_DEBUG, "Blocking MIG return of job_remove(): %s", otherj->label);
- /* this is messy. We shouldn't access 'otherj' after job_remove(), but we check otherj->p first... */
- (void)job_assumes(otherj, waiting4removal_new(otherj, srp));
- return MIG_NO_REPLY;
- } else {
- return 0;
- }
- } else if (sig == VPROC_MAGIC_TRYKILL_SIGNAL) {
- if (!j->kill_via_shmem) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- if (!j->shmem) {
- j->sent_kill_via_shmem = true;
- (void)job_assumes(j, runtime_kill(otherj->p, SIGKILL) != -1);
- return 0;
- }
-
-#if !TARGET_OS_EMBEDDED
- if (__sync_bool_compare_and_swap(&j->shmem->vp_shmem_transaction_cnt, 0, -1)) {
- j->shmem->vp_shmem_flags |= VPROC_SHMEM_EXITING;
- j->sent_kill_via_shmem = true;
- (void)job_assumes(j, runtime_kill(otherj->p, SIGKILL) != -1);
- return 0;
- }
-#endif
- return BOOTSTRAP_NOT_PRIVILEGED;
- } else if (otherj->p) {
- (void)job_assumes(j, runtime_kill(otherj->p, sig) != -1);
- }
-
- return 0;
-}
-
-kern_return_t
-job_mig_log_forward(job_t j, vm_offset_t inval, mach_msg_type_number_t invalCnt)
-{
- struct ldcred *ldc = runtime_get_caller_creds();
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (!job_assumes(j, j->per_user)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- return runtime_log_forward(ldc->euid, ldc->egid, inval, invalCnt);
-}
-
-kern_return_t
-job_mig_log_drain(job_t j, mach_port_t srp, vm_offset_t *outval, mach_msg_type_number_t *outvalCnt)
-{
- struct ldcred *ldc = runtime_get_caller_creds();
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (unlikely(ldc->euid)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- return runtime_log_drain(srp, outval, outvalCnt);
-}
-
-kern_return_t
-job_mig_swap_complex(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, vm_offset_t inval, mach_msg_type_number_t invalCnt, vm_offset_t *outval, mach_msg_type_number_t *outvalCnt)
-{
- const char *action;
- launch_data_t input_obj = NULL, output_obj = NULL;
- size_t data_offset = 0;
- size_t packed_size;
- struct ldcred *ldc = runtime_get_caller_creds();
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
- if (unlikely(inkey && ldc->euid && ldc->euid != getuid())) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- if (unlikely(inkey && outkey && !job_assumes(j, inkey == outkey))) {
- return 1;
- }
-
- if (inkey && outkey) {
- action = "Swapping";
- } else if (inkey) {
- action = "Setting";
- } else {
- action = "Getting";
- }
-
- job_log(j, LOG_DEBUG, "%s key: %u", action, inkey ? inkey : outkey);
-
- *outvalCnt = 20 * 1024 * 1024;
- mig_allocate(outval, *outvalCnt);
- if (!job_assumes(j, *outval != 0)) {
- return 1;
- }
-
- /* Note to future maintainers: launch_data_unpack() does NOT return a heap object. The data
- * is decoded in-place. So do not call launch_data_free() on input_obj.
- */
- runtime_ktrace0(RTKT_LAUNCHD_DATA_UNPACK);
- if (unlikely(invalCnt && !job_assumes(j, (input_obj = launch_data_unpack((void *)inval, invalCnt, NULL, 0, &data_offset, NULL)) != NULL))) {
- goto out_bad;
- }
-
- switch (outkey) {
- case VPROC_GSK_ENVIRONMENT:
- if (!job_assumes(j, (output_obj = launch_data_alloc(LAUNCH_DATA_DICTIONARY)))) {
- goto out_bad;
- }
- jobmgr_export_env_from_other_jobs(j->mgr, output_obj);
- runtime_ktrace0(RTKT_LAUNCHD_DATA_PACK);
- if (!job_assumes(j, launch_data_pack(output_obj, (void *)*outval, *outvalCnt, NULL, NULL) != 0)) {
- goto out_bad;
- }
- launch_data_free(output_obj);
- break;
- case VPROC_GSK_ALLJOBS:
- if (!job_assumes(j, (output_obj = job_export_all()) != NULL)) {
- goto out_bad;
- }
- ipc_revoke_fds(output_obj);
- runtime_ktrace0(RTKT_LAUNCHD_DATA_PACK);
- packed_size = launch_data_pack(output_obj, (void *)*outval, *outvalCnt, NULL, NULL);
- if (!job_assumes(j, packed_size != 0)) {
- goto out_bad;
- }
- launch_data_free(output_obj);
- break;
- case VPROC_GSK_MGR_NAME:
- if (!job_assumes(j, (output_obj = launch_data_new_string(j->mgr->name)) != NULL)) {
- goto out_bad;
- }
- packed_size = launch_data_pack(output_obj, (void *)*outval, *outvalCnt, NULL, NULL);
- if (!job_assumes(j, packed_size != 0)) {
- goto out_bad;
- }
-
- launch_data_free(output_obj);
- break;
- case VPROC_GSK_JOB_OVERRIDES_DB:
- if (!job_assumes(j, (output_obj = launch_data_new_string(launchd_data_base_path(LAUNCHD_DB_TYPE_OVERRIDES))) != NULL)) {
- goto out_bad;
- }
- packed_size = launch_data_pack(output_obj, (void *)*outval, *outvalCnt, NULL, NULL);
- if (!job_assumes(j, packed_size != 0)) {
- goto out_bad;
- }
-
- launch_data_free(output_obj);
- break;
- case VPROC_GSK_JOB_CACHE_DB:
- if (!job_assumes(j, (output_obj = launch_data_new_string(launchd_data_base_path(LAUNCHD_DB_TYPE_JOBCACHE))) != NULL)) {
- goto out_bad;
- }
- packed_size = launch_data_pack(output_obj, (void *)*outval, *outvalCnt, NULL, NULL);
- if (!job_assumes(j, packed_size != 0)) {
- goto out_bad;
- }
-
- job_log(j, LOG_DEBUG, "Location of job cache database: %s", launch_data_get_string(output_obj));
-
- launch_data_free(output_obj);
- break;
- case 0:
- mig_deallocate(*outval, *outvalCnt);
- *outval = 0;
- *outvalCnt = 0;
- break;
- default:
- goto out_bad;
- }
-
- if (invalCnt) switch (inkey) {
- case VPROC_GSK_ENVIRONMENT:
- if (launch_data_get_type(input_obj) == LAUNCH_DATA_DICTIONARY) {
- if (j->p) {
- job_log(j, LOG_INFO, "Setting environment for a currently active job. This environment will take effect on the next invocation of the job.");
- }
- launch_data_dict_iterate(input_obj, envitem_setup_one_shot, j);
- }
- break;
- case 0:
- break;
- default:
- goto out_bad;
- }
-
- mig_deallocate(inval, invalCnt);
- return 0;
-
-out_bad:
- mig_deallocate(inval, invalCnt);
- if (*outval) {
- mig_deallocate(*outval, *outvalCnt);
- }
- if (output_obj) {
- launch_data_free(output_obj);
- }
-
- return 1;
-}
-
-kern_return_t
-job_mig_swap_integer(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, int64_t inval, int64_t *outval)
-{
- const char *action;
- kern_return_t kr = 0;
- struct ldcred *ldc = runtime_get_caller_creds();
- int oldmask;
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (unlikely(inkey && ldc->euid && ldc->euid != getuid())) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- if (unlikely(inkey && outkey && !job_assumes(j, inkey == outkey))) {
- return 1;
- }
-
- if (inkey && outkey) {
- action = "Swapping";
- } else if (inkey) {
- action = "Setting";
- } else {
- action = "Getting";
- }
-
- job_log(j, LOG_DEBUG, "%s key: %u", action, inkey ? inkey : outkey);
-
- switch (outkey) {
- case VPROC_GSK_ABANDON_PROCESS_GROUP:
- *outval = j->abandon_pg;
- break;
- case VPROC_GSK_LAST_EXIT_STATUS:
- *outval = j->last_exit_status;
- break;
- case VPROC_GSK_MGR_UID:
- *outval = getuid();
- break;
- case VPROC_GSK_MGR_PID:
- *outval = getpid();
- break;
- case VPROC_GSK_IS_MANAGED:
- *outval = j->anonymous ? 0 : 1;
- break;
- case VPROC_GSK_BASIC_KEEPALIVE:
- *outval = !j->ondemand;
- break;
- case VPROC_GSK_START_INTERVAL:
- *outval = j->start_interval;
- break;
- case VPROC_GSK_IDLE_TIMEOUT:
- *outval = j->timeout;
- break;
- case VPROC_GSK_EXIT_TIMEOUT:
- *outval = j->exit_timeout;
- break;
- case VPROC_GSK_GLOBAL_LOG_MASK:
- oldmask = runtime_setlogmask(LOG_UPTO(LOG_DEBUG));
- *outval = oldmask;
- runtime_setlogmask(oldmask);
- break;
- case VPROC_GSK_GLOBAL_UMASK:
- oldmask = umask(0);
- *outval = oldmask;
- umask(oldmask);
- break;
- case VPROC_GSK_TRANSACTIONS_ENABLED:
- job_log(j, LOG_DEBUG, "Reading transaction model status.");
- *outval = j->kill_via_shmem;
- break;
- case VPROC_GSK_WAITFORDEBUGGER:
- *outval = j->wait4debugger;
- break;
- case VPROC_GSK_EMBEDDEDROOTEQUIVALENT:
- *outval = j->embedded_special_privileges;
- break;
- case 0:
- *outval = 0;
- break;
- default:
- kr = 1;
- break;
- }
-
- switch (inkey) {
- case VPROC_GSK_ABANDON_PROCESS_GROUP:
- j->abandon_pg = (bool)inval;
- break;
- case VPROC_GSK_GLOBAL_ON_DEMAND:
- job_log(j, LOG_DEBUG, "Job is setting global on-demand mode to %s (j->forced_peers_to_demand_mode = %s)", (bool)inval ? "true" : "false", j->forced_peers_to_demand_mode ? "true" : "false");
- kr = job_set_global_on_demand(j, (bool)inval) ? 0 : 1;
- break;
- case VPROC_GSK_BASIC_KEEPALIVE:
- j->ondemand = !inval;
- break;
- case VPROC_GSK_START_INTERVAL:
- if (inval > UINT32_MAX || inval < 0) {
- kr = 1;
- } else if (inval) {
- if (j->start_interval == 0) {
- runtime_add_weak_ref();
- }
- j->start_interval = (typeof(j->start_interval)) inval;
- (void)job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, j->start_interval, j) != -1);
- } else if (j->start_interval) {
- (void)job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_DELETE, 0, 0, NULL) != -1);
- if (j->start_interval != 0) {
- runtime_del_weak_ref();
- }
- j->start_interval = 0;
- }
- break;
- case VPROC_GSK_IDLE_TIMEOUT:
- if (inval < 0 || inval > UINT32_MAX) {
- kr = 1;
- } else {
- j->timeout = (typeof(j->timeout)) inval;
- }
- break;
- case VPROC_GSK_EXIT_TIMEOUT:
- if (inval < 0 || inval > UINT32_MAX) {
- kr = 1;
- } else {
- j->exit_timeout = (typeof(j->exit_timeout)) inval;
- }
- break;
- case VPROC_GSK_GLOBAL_LOG_MASK:
- if (inval < 0 || inval > UINT32_MAX) {
- kr = 1;
- } else {
- runtime_setlogmask((int) inval);
- }
- break;
- case VPROC_GSK_GLOBAL_UMASK:
- launchd_assert(sizeof (mode_t) == 2);
- if (inval < 0 || inval > UINT16_MAX) {
- kr = 1;
- } else {
-#if HAVE_SANDBOX
- if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) {
- kr = 1;
- } else {
- umask((mode_t) inval);
- }
-#endif
- }
- break;
- case VPROC_GSK_TRANSACTIONS_ENABLED:
- if (!job_assumes(j, inval != 0)) {
- job_log(j, LOG_WARNING, "Attempt to unregister from transaction model. This is not supported.");
- kr = 1;
- } else {
- j->kill_via_shmem = (bool)inval;
- }
- break;
- case VPROC_GSK_WEIRD_BOOTSTRAP:
- if (job_assumes(j, j->weird_bootstrap)) {
- job_log(j, LOG_DEBUG, "Unsetting weird bootstrap.");
-
- mach_msg_size_t mxmsgsz = (typeof(mxmsgsz)) sizeof(union __RequestUnion__job_mig_protocol_vproc_subsystem);
-
- if (job_mig_protocol_vproc_subsystem.maxsize > mxmsgsz) {
- mxmsgsz = job_mig_protocol_vproc_subsystem.maxsize;
- }
-
- (void)job_assumes(j, runtime_add_mport(j->mgr->jm_port, protocol_vproc_server, mxmsgsz) == KERN_SUCCESS);
- j->weird_bootstrap = false;
- }
- break;
- case VPROC_GSK_WAITFORDEBUGGER:
- j->wait4debugger_oneshot = inval;
- break;
- case VPROC_GSK_PERUSER_SUSPEND:
- if (job_assumes(j, pid1_magic && ldc->euid == 0)) {
- mach_port_t junk = MACH_PORT_NULL;
- job_t jpu = jobmgr_lookup_per_user_context_internal(j, (uid_t)inval, &junk);
- if (job_assumes(j, jpu != NULL)) {
- struct suspended_peruser *spi = NULL;
- LIST_FOREACH(spi, &j->suspended_perusers, sle) {
- if ((int64_t)(spi->j->mach_uid) == inval) {
- job_log(j, LOG_WARNING, "Job tried to suspend per-user launchd for UID %lli twice.", inval);
- break;
- }
- }
-
- if (spi == NULL) {
- job_log(j, LOG_INFO, "Job is suspending the per-user launchd for UID %lli.", inval);
- spi = (struct suspended_peruser *)calloc(sizeof(struct suspended_peruser), 1);
- if (job_assumes(j, spi != NULL)) {
- /* Stop listening for events.
- *
- * See <rdar://problem/9014146>.
- */
- if (jpu->peruser_suspend_count == 0) {
- job_ignore(jpu);
- }
-
- spi->j = jpu;
- spi->j->peruser_suspend_count++;
- LIST_INSERT_HEAD(&j->suspended_perusers, spi, sle);
- job_stop(spi->j);
- *outval = jpu->p;
- } else {
- kr = BOOTSTRAP_NO_MEMORY;
- }
- }
- }
- } else {
- kr = 1;
- }
- break;
- case VPROC_GSK_PERUSER_RESUME:
- if (job_assumes(j, pid1_magic == true)) {
- struct suspended_peruser *spi = NULL, *spt = NULL;
- LIST_FOREACH_SAFE(spi, &j->suspended_perusers, sle, spt) {
- if ((int64_t)(spi->j->mach_uid) == inval) {
- spi->j->peruser_suspend_count--;
- LIST_REMOVE(spi, sle);
- job_log(j, LOG_INFO, "Job is resuming the per-user launchd for UID %lli.", inval);
- break;
- }
- }
-
- if (!job_assumes(j, spi != NULL)) {
- job_log(j, LOG_WARNING, "Job tried to resume per-user launchd for UID %lli that it did not suspend.", inval);
- kr = BOOTSTRAP_NOT_PRIVILEGED;
- } else if (spi->j->peruser_suspend_count == 0) {
- job_watch(spi->j);
- job_dispatch(spi->j, false);
- free(spi);
- }
- } else {
- kr = 1;
- }
- break;
- case 0:
- break;
- default:
- kr = 1;
- break;
- }
-
- return kr;
-}
-
-kern_return_t
-job_mig_post_fork_ping(job_t j, task_t child_task, mach_port_t *asport)
-{
- struct machservice *ms;
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- job_log(j, LOG_DEBUG, "Post fork ping.");
-
- job_setup_exception_port(j, child_task);
-
- SLIST_FOREACH(ms, &special_ports, special_port_sle) {
- if (j->per_user && (ms->special_port_num != TASK_ACCESS_PORT)) {
- /* The TASK_ACCESS_PORT funny business is to workaround 5325399. */
- continue;
- }
-
- errno = task_set_special_port(child_task, ms->special_port_num, ms->port);
-
- if (unlikely(errno)) {
- int desired_log_level = LOG_ERR;
-
- if (j->anonymous) {
- /* 5338127 */
-
- desired_log_level = LOG_WARNING;
-
- if (ms->special_port_num == TASK_SEATBELT_PORT) {
- desired_log_level = LOG_DEBUG;
- }
- }
-
- job_log(j, desired_log_level, "Could not setup Mach task special port %u: %s", ms->special_port_num, mach_error_string(errno));
- }
- }
-
- /* MIG will not zero-initialize this pointer, so we must always do so. See
- * <rdar://problem/8562593>.
- */
- *asport = MACH_PORT_NULL;
-#if !TARGET_OS_EMBEDDED
- if (!j->anonymous) {
- /* XPC services will spawn into the root security session by default.
- * xpcproxy will switch them away if needed.
- */
- if (!(j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
- job_log(j, LOG_DEBUG, "Returning j->asport: %u", j->asport);
- *asport = j->asport;
- }
- }
-#endif
- (void)job_assumes(j, launchd_mport_deallocate(child_task) == KERN_SUCCESS);
-
- return 0;
-}
-
-kern_return_t
-job_mig_reboot2(job_t j, uint64_t flags)
-{
- char who_started_the_reboot[2048] = "";
- struct proc_bsdshortinfo proc;
- struct ldcred *ldc = runtime_get_caller_creds();
- pid_t pid_to_log;
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (unlikely(!pid1_magic)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
-#if !TARGET_OS_EMBEDDED
- if (unlikely(ldc->euid)) {
-#else
- if (unlikely(ldc->euid) && !j->embedded_special_privileges) {
-#endif
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- for (pid_to_log = ldc->pid; pid_to_log; pid_to_log = proc.pbsi_ppid) {
- size_t who_offset;
- if (proc_pidinfo(pid_to_log, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
- if (errno != ESRCH) {
- job_assumes(j, errno == 0);
- }
- return 1;
- }
-
- if (!job_assumes(j, pid_to_log != (pid_t)proc.pbsi_ppid)) {
- job_log(j, LOG_WARNING, "Job which is its own parent started reboot.");
- snprintf(who_started_the_reboot, sizeof(who_started_the_reboot), "%s[%u]->%s[%u]->%s[%u]->...", proc.pbsi_comm, pid_to_log, proc.pbsi_comm, pid_to_log, proc.pbsi_comm, pid_to_log);
- break;
- }
-
- who_offset = strlen(who_started_the_reboot);
- snprintf(who_started_the_reboot + who_offset, sizeof(who_started_the_reboot) - who_offset,
- " %s[%u]%s", proc.pbsi_comm, pid_to_log, proc.pbsi_ppid ? " ->" : "");
- }
-
- root_jobmgr->reboot_flags = (int)flags;
- job_log(j, LOG_DEBUG, "reboot2() initiated by:%s", who_started_the_reboot);
- launchd_shutdown();
-
- return 0;
-}
-
-kern_return_t
-job_mig_getsocket(job_t j, name_t spr)
-{
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (j->deny_job_creation) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
-#if HAVE_SANDBOX
- struct ldcred *ldc = runtime_get_caller_creds();
- if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-#endif
-
- ipc_server_init();
-
- if (unlikely(!sockpath)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- strncpy(spr, sockpath, sizeof(name_t));
-
- return BOOTSTRAP_SUCCESS;
-}
-
-kern_return_t
-job_mig_log(job_t j, int pri, int err, logmsg_t msg)
-{
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if ((errno = err)) {
- job_log_error(j, pri, "%s", msg);
- } else {
- job_log(j, pri, "%s", msg);
- }
-
- return 0;
-}
-
-job_t
-jobmgr_lookup_per_user_context_internal(job_t j, uid_t which_user, mach_port_t *mp)
-{
- job_t ji = NULL;
- LIST_FOREACH(ji, &root_jobmgr->jobs, sle) {
- if (!ji->per_user) {
- continue;
- }
- if (ji->mach_uid != which_user) {
- continue;
- }
- if (SLIST_EMPTY(&ji->machservices)) {
- continue;
- }
- if (!SLIST_FIRST(&ji->machservices)->per_user_hack) {
- continue;
- }
- break;
- }
-
- if (unlikely(ji == NULL)) {
- struct machservice *ms;
- char lbuf[1024];
-
- job_log(j, LOG_DEBUG, "Creating per user launchd job for UID: %u", which_user);
-
- sprintf(lbuf, "com.apple.launchd.peruser.%u", which_user);
-
- ji = job_new(root_jobmgr, lbuf, "/sbin/launchd", NULL);
-
- if (ji != NULL) {
- auditinfo_addr_t auinfo = {
- .ai_termid = { .at_type = AU_IPv4 },
- .ai_auid = which_user,
- .ai_asid = AU_ASSIGN_ASID,
- };
-
- if (setaudit_addr(&auinfo, sizeof(auinfo)) == 0) {
- job_log(ji, LOG_DEBUG, "Created new security session for per-user launchd: %u", auinfo.ai_asid);
- (void)job_assumes(ji, (ji->asport = audit_session_self()) != MACH_PORT_NULL);
-
- /* Kinda lame that we have to do this, but we can't create an
- * audit session without joining it.
- */
- (void)job_assumes(ji, audit_session_join(g_audit_session_port));
- ji->asid = auinfo.ai_asid;
- } else {
- job_log(ji, LOG_WARNING, "Could not set audit session!");
- job_remove(ji);
- return NULL;
- }
-
- ji->mach_uid = which_user;
- ji->per_user = true;
- ji->kill_via_shmem = true;
-
- struct stat sb;
- char pu_db[PATH_MAX];
- snprintf(pu_db, sizeof(pu_db), LAUNCHD_DB_PREFIX "/%s", lbuf);
-
- bool created = false;
- int err = stat(pu_db, &sb);
- if ((err == -1 && errno == ENOENT) || (err == 0 && !S_ISDIR(sb.st_mode))) {
- if (err == 0) {
- char move_aside[PATH_MAX];
- snprintf(move_aside, sizeof(move_aside), LAUNCHD_DB_PREFIX "/%s.movedaside", lbuf);
-
- (void)job_assumes(ji, rename(pu_db, move_aside) != -1);
- }
-
- (void)job_assumes(ji, mkdir(pu_db, S_IRWXU) != -1);
- (void)job_assumes(ji, chown(pu_db, which_user, 0) != -1);
- created = true;
- }
-
- if (!created) {
- if (!job_assumes(ji, sb.st_uid == which_user)) {
- (void)job_assumes(ji, chown(pu_db, which_user, 0) != -1);
- }
- if (!job_assumes(ji, sb.st_gid == 0)) {
- (void)job_assumes(ji, chown(pu_db, which_user, 0) != -1);
- }
- if (!job_assumes(ji, sb.st_mode == (S_IRWXU | S_IFDIR))) {
- (void)job_assumes(ji, chmod(pu_db, S_IRWXU) != -1);
- }
- }
-
- if ((ms = machservice_new(ji, lbuf, mp, false)) == NULL) {
- job_remove(ji);
- ji = NULL;
- } else {
- ms->per_user_hack = true;
- ms->hide = true;
-
- ji = job_dispatch(ji, false);
- }
- }
- } else {
- *mp = machservice_port(SLIST_FIRST(&ji->machservices));
- job_log(j, LOG_DEBUG, "Per user launchd job found for UID: %u", which_user);
- }
-
- return ji;
-}
-
-kern_return_t
-job_mig_lookup_per_user_context(job_t j, uid_t which_user, mach_port_t *up_cont)
-{
- struct ldcred *ldc = runtime_get_caller_creds();
- job_t jpu;
-
-#if TARGET_OS_EMBEDDED
- /* There is no need for per-user launchd's on embedded. */
- job_log(j, LOG_ERR, "Per-user launchds are not supported on this platform.");
- return BOOTSTRAP_NOT_PRIVILEGED;
-#endif
-
-#if HAVE_SANDBOX
- if (unlikely(sandbox_check(ldc->pid, "mach-per-user-lookup", SANDBOX_FILTER_NONE) > 0)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-#endif
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- job_log(j, LOG_INFO, "Looking up per user launchd for UID: %u", which_user);
-
- if (unlikely(!pid1_magic)) {
- job_log(j, LOG_ERR, "Only PID 1 supports per user launchd lookups.");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- if (ldc->euid || ldc->uid) {
- which_user = ldc->euid ?: ldc->uid;
- }
-
- *up_cont = MACH_PORT_NULL;
-
- jpu = jobmgr_lookup_per_user_context_internal(j, which_user, up_cont);
-
- return 0;
-}
-
-kern_return_t
-job_mig_check_in2(job_t j, name_t servicename, mach_port_t *serviceportp, uuid_t instance_id, uint64_t flags)
-{
- bool per_pid_service = flags & BOOTSTRAP_PER_PID_SERVICE;
- bool strict = flags & BOOTSTRAP_STRICT_CHECKIN;
- struct ldcred *ldc = runtime_get_caller_creds();
- struct machservice *ms = NULL;
- job_t jo;
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (j->dedicated_instance) {
- struct machservice *msi = NULL;
- SLIST_FOREACH(msi, &j->machservices, sle) {
- if (strncmp(servicename, msi->name, sizeof(name_t) - 1) == 0) {
- uuid_copy(instance_id, j->instance_id);
- ms = msi;
- break;
- }
- }
- } else {
- ms = jobmgr_lookup_service(j->mgr, servicename, false, per_pid_service ? ldc->pid : 0);
- }
-
- if (strict) {
- if (likely(ms != NULL)) {
- if (ms->job != j) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- } else if (ms->isActive) {
- return BOOTSTRAP_SERVICE_ACTIVE;
- }
- } else {
- return BOOTSTRAP_UNKNOWN_SERVICE;
- }
- } else if (ms == NULL) {
- if (job_assumes(j, !j->dedicated_instance)) {
- *serviceportp = MACH_PORT_NULL;
-
- if (unlikely((ms = machservice_new(j, servicename, serviceportp, per_pid_service)) == NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- /* Treat this like a legacy job. */
- if (!j->legacy_mach_job) {
- ms->isActive = true;
- ms->recv = false;
- }
-
- if (!(j->anonymous || j->legacy_LS_job || j->legacy_mach_job)) {
- job_log(j, LOG_SCOLDING, "Please add the following service to the configuration file for this job: %s", servicename);
- }
- } else {
- return BOOTSTRAP_UNKNOWN_SERVICE;
- }
- } else {
- if (unlikely((jo = machservice_job(ms)) != j)) {
- static pid_t last_warned_pid;
-
- if (last_warned_pid != ldc->pid) {
- job_log(jo, LOG_WARNING, "The following job tried to hijack the service \"%s\" from this job: %s", servicename, j->label);
- last_warned_pid = ldc->pid;
- }
-
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- if (unlikely(machservice_active(ms))) {
- job_log(j, LOG_WARNING, "Check-in of Mach service failed. Already active: %s", servicename);
- return BOOTSTRAP_SERVICE_ACTIVE;
- }
- }
-
- job_checkin(j);
- machservice_request_notifications(ms);
-
- job_log(j, LOG_INFO, "Check-in of service: %s", servicename);
-
- *serviceportp = machservice_port(ms);
- return BOOTSTRAP_SUCCESS;
-}
-
-kern_return_t
-job_mig_register2(job_t j, name_t servicename, mach_port_t serviceport, uint64_t flags)
-{
- struct machservice *ms;
- struct ldcred *ldc = runtime_get_caller_creds();
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (!(flags & BOOTSTRAP_PER_PID_SERVICE) && !j->legacy_LS_job) {
- job_log(j, LOG_SCOLDING, "Performance: bootstrap_register() is deprecated. Service: %s", servicename);
- }
-
- job_log(j, LOG_DEBUG, "%sMach service registration attempt: %s", flags & BOOTSTRAP_PER_PID_SERVICE ? "Per PID " : "", servicename);
-
- /* 5641783 for the embedded hack */
-#if !TARGET_OS_EMBEDDED
- /*
- * From a per-user/session launchd's perspective, SecurityAgent (UID
- * 92) is a rogue application (not our UID, not root and not a child of
- * us). We'll have to reconcile this design friction at a later date.
- */
- if (unlikely(j->anonymous && j->mgr->parentmgr == NULL && ldc->uid != 0 && ldc->uid != getuid() && ldc->uid != 92)) {
- if (pid1_magic) {
- return VPROC_ERR_TRY_PER_USER;
- } else {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- }
-#endif
-
- ms = jobmgr_lookup_service(j->mgr, servicename, false, flags & BOOTSTRAP_PER_PID_SERVICE ? ldc->pid : 0);
-
- if (unlikely(ms)) {
- if (machservice_job(ms) != j) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- if (machservice_active(ms)) {
- job_log(j, LOG_DEBUG, "Mach service registration failed. Already active: %s", servicename);
- return BOOTSTRAP_SERVICE_ACTIVE;
- }
- if (ms->recv && (serviceport != MACH_PORT_NULL)) {
- job_log(j, LOG_ERR, "bootstrap_register() erroneously called instead of bootstrap_check_in(). Mach service: %s", servicename);
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- job_checkin(j);
- machservice_delete(j, ms, false);
- }
-
- if (likely(serviceport != MACH_PORT_NULL)) {
- if (likely(ms = machservice_new(j, servicename, &serviceport, flags & BOOTSTRAP_PER_PID_SERVICE ? true : false))) {
- machservice_request_notifications(ms);
- } else {
- return BOOTSTRAP_NO_MEMORY;
- }
- }
-
-
- return BOOTSTRAP_SUCCESS;
-}
-
-kern_return_t
-job_mig_look_up2(job_t j, mach_port_t srp, name_t servicename, mach_port_t *serviceportp, pid_t target_pid, uuid_t instance_id, uint64_t flags)
-{
- struct machservice *ms = NULL;
- struct ldcred *ldc = runtime_get_caller_creds();
- kern_return_t kr;
- bool per_pid_lookup = flags & BOOTSTRAP_PER_PID_SERVICE;
- bool specific_instance = flags & BOOTSTRAP_SPECIFIC_INSTANCE;
- bool strict_lookup = flags & BOOTSTRAP_STRICT_LOOKUP;
- bool privileged = flags & BOOTSTRAP_PRIVILEGED_SERVER;
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- bool xpc_req = j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN;
-
- /* 5641783 for the embedded hack */
-#if !TARGET_OS_EMBEDDED
- if (unlikely(pid1_magic && j->anonymous && j->mgr->parentmgr == NULL && ldc->uid != 0 && ldc->euid != 0)) {
- return VPROC_ERR_TRY_PER_USER;
- }
-#endif
-
-#if HAVE_SANDBOX
- /* We don't do sandbox checking for XPC domains because, by definition, all
- * the services within your domain should be accessibly to you.
- */
- if (!xpc_req && unlikely(sandbox_check(ldc->pid, "mach-lookup", per_pid_lookup ? SANDBOX_FILTER_LOCAL_NAME : SANDBOX_FILTER_GLOBAL_NAME, servicename) > 0)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-#endif
-
- if (per_pid_lookup) {
- ms = jobmgr_lookup_service(j->mgr, servicename, false, target_pid);
- } else {
- if (xpc_req) {
- /* Requests from XPC domains stay local. */
- ms = jobmgr_lookup_service(j->mgr, servicename, false, 0);
- } else {
- /* A strict lookup which is privileged won't even bother trying to
- * find a service if we're not hosting the root Mach bootstrap.
- */
- if (strict_lookup && privileged) {
- if (inherited_bootstrap_port == MACH_PORT_NULL) {
- ms = jobmgr_lookup_service(j->mgr, servicename, true, 0);
- }
- } else {
- ms = jobmgr_lookup_service(j->mgr, servicename, true, 0);
- }
- }
- }
-
- if (likely(ms)) {
- ms = ms->alias ? ms->alias : ms;
- if (unlikely(specific_instance && ms->job->multiple_instances)) {
- job_t ji = NULL;
- job_t instance = NULL;
- LIST_FOREACH(ji, &ms->job->subjobs, subjob_sle) {
- if (uuid_compare(instance_id, ji->instance_id) == 0) {
- instance = ji;
- break;
- }
- }
-
- if (unlikely(instance == NULL)) {
- job_log(ms->job, LOG_DEBUG, "Creating new instance of job based on lookup of service %s", ms->name);
- instance = job_new_subjob(ms->job, instance_id);
- if (job_assumes(j, instance != NULL)) {
- /* Disable this support for now. We only support having
- * multi-instance jobs within private XPC domains.
- */
-#if 0
- /* If the job is multi-instance, in a singleton XPC domain
- * and the request is not coming from within that singleton
- * domain, we need to alias the new job into the requesting
- * domain.
- */
- if (!j->mgr->xpc_singleton && xpc_req) {
- (void)job_assumes(instance, job_new_alias(j->mgr, instance));
- }
-#endif
- job_dispatch(instance, false);
- }
- }
-
- ms = NULL;
- if (job_assumes(j, instance != NULL)) {
- struct machservice *msi = NULL;
- SLIST_FOREACH(msi, &instance->machservices, sle) {
- /* sizeof(servicename) will return the size of a pointer, even though it's
- * an array type, because when passing arrays as parameters in C, they
- * implicitly degrade to pointers.
- */
- if (strncmp(servicename, msi->name, sizeof(name_t) - 1) == 0) {
- ms = msi;
- break;
- }
- }
- }
- } else {
- if (machservice_hidden(ms) && !machservice_active(ms)) {
- ms = NULL;
- } else if (unlikely(ms->per_user_hack)) {
- ms = NULL;
- }
- }
- }
-
- if (likely(ms)) {
- (void)job_assumes(j, machservice_port(ms) != MACH_PORT_NULL);
- job_log(j, LOG_DEBUG, "%sMach service lookup: %s", per_pid_lookup ? "Per PID " : "", servicename);
-
- if (unlikely(!per_pid_lookup && j->lastlookup == ms && j->lastlookup_gennum == ms->gen_num && !j->per_user)) {
- /* we need to think more about the per_pid_lookup logic before logging about repeated lookups */
- job_log(j, LOG_DEBUG, "Performance: Please fix the framework that talks to \"%s\" to cache the Mach port for service: %s", ms->job->label, servicename);
- }
-
- j->lastlookup = ms;
- j->lastlookup_gennum = ms->gen_num;
-
- *serviceportp = machservice_port(ms);
-
- kr = BOOTSTRAP_SUCCESS;
- } else if (strict_lookup && !privileged) {
- /* Hack: We need to simulate XPC's desire not to establish a hierarchy. So if
- * XPC is doing the lookup, and it's not a privileged lookup, we won't forward.
- * But if it is a privileged lookup (that is, was looked up in XPC_DOMAIN_LOCAL_SYSTEM)
- * then we must forward.
- */
- return BOOTSTRAP_UNKNOWN_SERVICE;
- } else if (inherited_bootstrap_port != MACH_PORT_NULL) {
- /* Requests from within an XPC domain don't get forwarded. */
- job_log(j, LOG_DEBUG, "Mach service lookup forwarded: %s", servicename);
- /* Clients potentially check the audit token of the reply to verify that the returned send right is trustworthy. */
- (void)job_assumes(j, vproc_mig_look_up2_forward(inherited_bootstrap_port, srp, servicename, target_pid, instance_id, flags) == 0);
- /* The previous routine moved the reply port, we're forced to return MIG_NO_REPLY now */
- return MIG_NO_REPLY;
- } else if (pid1_magic && j->anonymous && ldc->euid >= 500 && strcasecmp(j->mgr->name, VPROCMGR_SESSION_LOGINWINDOW) == 0) {
- /*
- * 5240036 Should start background session when a lookup of CCacheServer occurs
- *
- * This is a total hack. We sniff out loginwindow session, and attempt to guess what it is up to.
- * If we find a EUID that isn't root, we force it over to the per-user context.
- */
- return VPROC_ERR_TRY_PER_USER;
- } else {
- job_log(j, LOG_DEBUG, "%sMach service lookup failed: %s", per_pid_lookup ? "Per PID " : "", servicename);
- kr = BOOTSTRAP_UNKNOWN_SERVICE;
- }
-
- return kr;
-}
-
-kern_return_t
-job_mig_parent(job_t j, mach_port_t srp, mach_port_t *parentport)
-{
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- job_log(j, LOG_DEBUG, "Requested parent bootstrap port");
- jobmgr_t jm = j->mgr;
-
- if (jobmgr_parent(jm)) {
- *parentport = jobmgr_parent(jm)->jm_port;
- } else if (MACH_PORT_NULL == inherited_bootstrap_port) {
- *parentport = jm->jm_port;
- } else {
- (void)job_assumes(j, vproc_mig_parent_forward(inherited_bootstrap_port, srp) == 0);
- /* The previous routine moved the reply port, we're forced to return MIG_NO_REPLY now */
- return MIG_NO_REPLY;
- }
- return BOOTSTRAP_SUCCESS;
-}
-
-kern_return_t
-job_mig_get_root_bootstrap(job_t j, mach_port_t *rootbsp)
-{
- if (!j) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (inherited_bootstrap_port == MACH_PORT_NULL) {
- *rootbsp = root_jobmgr->jm_port;
- (void)job_assumes(j, launchd_mport_make_send(root_jobmgr->jm_port) == KERN_SUCCESS);
- } else {
- *rootbsp = inherited_bootstrap_port;
- (void)job_assumes(j, launchd_mport_copy_send(inherited_bootstrap_port) == KERN_SUCCESS);
- }
-
- return BOOTSTRAP_SUCCESS;
-}
-
-kern_return_t
-job_mig_info(job_t j, name_array_t *servicenamesp, unsigned int *servicenames_cnt, name_array_t *servicejobsp, unsigned int *servicejobs_cnt, bootstrap_status_array_t *serviceactivesp, unsigned int *serviceactives_cnt, uint64_t flags)
-{
- name_array_t service_names = NULL;
- name_array_t service_jobs = NULL;
- bootstrap_status_array_t service_actives = NULL;
- unsigned int cnt = 0, cnt2 = 0;
- jobmgr_t jm;
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (g_flat_mach_namespace) {
- if ((j->mgr->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET) || (flags & BOOTSTRAP_FORCE_LOCAL)) {
- jm = j->mgr;
- } else {
- jm = root_jobmgr;
- }
- } else {
- jm = j->mgr;
- }
-
- unsigned int i = 0;
- struct machservice *msi = NULL;
- for (i = 0; i < MACHSERVICE_HASH_SIZE; i++) {
- LIST_FOREACH(msi, &jm->ms_hash[i], name_hash_sle) {
- cnt += !msi->per_pid ? 1 : 0;
- }
- }
-
- if (cnt == 0) {
- goto out;
- }
-
- mig_allocate((vm_address_t *)&service_names, cnt * sizeof(service_names[0]));
- if (!job_assumes(j, service_names != NULL)) {
- goto out_bad;
- }
-
- mig_allocate((vm_address_t *)&service_jobs, cnt * sizeof(service_jobs[0]));
- if (!job_assumes(j, service_jobs != NULL)) {
- goto out_bad;
- }
-
- mig_allocate((vm_address_t *)&service_actives, cnt * sizeof(service_actives[0]));
- if (!job_assumes(j, service_actives != NULL)) {
- goto out_bad;
- }
-
- for (i = 0; i < MACHSERVICE_HASH_SIZE; i++) {
- LIST_FOREACH(msi, &jm->ms_hash[i], name_hash_sle) {
- if (!msi->per_pid) {
- strlcpy(service_names[cnt2], machservice_name(msi), sizeof(service_names[0]));
- msi = msi->alias ? msi->alias : msi;
- if (msi->job->mgr->shortdesc) {
- strlcpy(service_jobs[cnt2], msi->job->mgr->shortdesc, sizeof(service_jobs[0]));
- } else {
- strlcpy(service_jobs[cnt2], msi->job->label, sizeof(service_jobs[0]));
- }
- service_actives[cnt2] = machservice_status(msi);
- cnt2++;
- }
- }
- }
-
- (void)job_assumes(j, cnt == cnt2);
-
-out:
- *servicenamesp = service_names;
- *servicejobsp = service_jobs;
- *serviceactivesp = service_actives;
- *servicenames_cnt = *servicejobs_cnt = *serviceactives_cnt = cnt;
-
- return BOOTSTRAP_SUCCESS;
-
-out_bad:
- if (service_names) {
- mig_deallocate((vm_address_t)service_names, cnt * sizeof(service_names[0]));
- }
- if (service_jobs) {
- mig_deallocate((vm_address_t)service_jobs, cnt * sizeof(service_jobs[0]));
- }
- if (service_actives) {
- mig_deallocate((vm_address_t)service_actives, cnt * sizeof(service_actives[0]));
- }
-
- return BOOTSTRAP_NO_MEMORY;
-}
-
-kern_return_t
-job_mig_lookup_children(job_t j, mach_port_array_t *child_ports, mach_msg_type_number_t *child_ports_cnt, name_array_t *child_names, mach_msg_type_number_t *child_names_cnt, bootstrap_property_array_t *child_properties,mach_msg_type_number_t *child_properties_cnt)
-{
- kern_return_t kr = BOOTSTRAP_NO_MEMORY;
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- struct ldcred *ldc = runtime_get_caller_creds();
-
- /* Only allow root processes to look up children, even if we're in the per-user launchd.
- * Otherwise, this could be used to cross sessions, which counts as a security vulnerability
- * in a non-flat namespace.
- */
- if (ldc->euid != 0) {
- job_log(j, LOG_WARNING, "Attempt to look up children of bootstrap by unprivileged job.");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- unsigned int cnt = 0;
-
- jobmgr_t jmr = j->mgr;
- jobmgr_t jmi = NULL;
- SLIST_FOREACH(jmi, &jmr->submgrs, sle) {
- cnt++;
- }
-
- /* Find our per-user launchds if we're PID 1. */
- job_t ji = NULL;
- if (pid1_magic) {
- LIST_FOREACH(ji, &jmr->jobs, sle) {
- cnt += ji->per_user ? 1 : 0;
- }
- }
-
- if (cnt == 0) {
- return BOOTSTRAP_NO_CHILDREN;
- }
-
- mach_port_array_t _child_ports = NULL;
- mig_allocate((vm_address_t *)&_child_ports, cnt * sizeof(_child_ports[0]));
- if (!job_assumes(j, _child_ports != NULL)) {
- kr = BOOTSTRAP_NO_MEMORY;
- goto out_bad;
- }
-
- name_array_t _child_names = NULL;
- mig_allocate((vm_address_t *)&_child_names, cnt * sizeof(_child_names[0]));
- if (!job_assumes(j, _child_names != NULL)) {
- kr = BOOTSTRAP_NO_MEMORY;
- goto out_bad;
- }
-
- bootstrap_property_array_t _child_properties = NULL;
- mig_allocate((vm_address_t *)&_child_properties, cnt * sizeof(_child_properties[0]));
- if (!job_assumes(j, _child_properties != NULL)) {
- kr = BOOTSTRAP_NO_MEMORY;
- goto out_bad;
- }
-
- unsigned int cnt2 = 0;
- SLIST_FOREACH(jmi, &jmr->submgrs, sle) {
- if (jobmgr_assumes(jmi, launchd_mport_make_send(jmi->jm_port) == KERN_SUCCESS)) {
- _child_ports[cnt2] = jmi->jm_port;
- } else {
- _child_ports[cnt2] = MACH_PORT_NULL;
- }
-
- strlcpy(_child_names[cnt2], jmi->name, sizeof(_child_names[0]));
- _child_properties[cnt2] = jmi->properties;
-
- cnt2++;
- }
-
- if (pid1_magic) LIST_FOREACH( ji, &jmr->jobs, sle) {
- if (ji->per_user) {
- if (job_assumes(ji, SLIST_FIRST(&ji->machservices)->per_user_hack == true)) {
- mach_port_t port = machservice_port(SLIST_FIRST(&ji->machservices));
-
- if (job_assumes(ji, launchd_mport_copy_send(port) == KERN_SUCCESS)) {
- _child_ports[cnt2] = port;
- } else {
- _child_ports[cnt2] = MACH_PORT_NULL;
- }
- } else {
- _child_ports[cnt2] = MACH_PORT_NULL;
- }
-
- strlcpy(_child_names[cnt2], ji->label, sizeof(_child_names[0]));
- _child_properties[cnt2] |= BOOTSTRAP_PROPERTY_PERUSER;
-
- cnt2++;
- }
- }
-
- *child_names_cnt = cnt;
- *child_ports_cnt = cnt;
- *child_properties_cnt = cnt;
-
- *child_names = _child_names;
- *child_ports = _child_ports;
- *child_properties = _child_properties;
-
- unsigned int i = 0;
- for (i = 0; i < cnt; i++) {
- job_log(j, LOG_DEBUG, "child_names[%u] = %s", i, (char *)_child_names[i]);
- }
-
- return BOOTSTRAP_SUCCESS;
-out_bad:
- if (_child_ports) {
- mig_deallocate((vm_address_t)_child_ports, cnt * sizeof(_child_ports[0]));
- }
-
- if (_child_names) {
- mig_deallocate((vm_address_t)_child_names, cnt * sizeof(_child_ports[0]));
- }
-
- if (_child_properties) {
- mig_deallocate((vm_address_t)_child_properties, cnt * sizeof(_child_properties[0]));
- }
-
- return kr;
-}
-
-kern_return_t
-job_mig_transaction_count_for_pid(job_t j, pid_t p, int32_t *cnt, boolean_t *condemned)
-{
- if (!j) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- kern_return_t kr = KERN_FAILURE;
- struct ldcred *ldc = runtime_get_caller_creds();
- if ((ldc->euid != geteuid()) && (ldc->euid != 0)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- job_t j_for_pid = jobmgr_find_by_pid_deep(j->mgr, p, false);
- if (j_for_pid) {
- if (j_for_pid->kill_via_shmem) {
- if (j_for_pid->shmem) {
- *cnt = j_for_pid->shmem->vp_shmem_transaction_cnt;
- *condemned = j_for_pid->shmem->vp_shmem_flags & VPROC_SHMEM_EXITING;
- *cnt += *condemned ? 1 : 0;
- } else {
- *cnt = 0;
- *condemned = false;
- }
-
- kr = BOOTSTRAP_SUCCESS;
- } else {
- kr = BOOTSTRAP_NO_MEMORY;
- }
- } else {
- kr = BOOTSTRAP_UNKNOWN_SERVICE;
- }
-
- return kr;
-}
-
-kern_return_t
-job_mig_pid_is_managed(job_t j __attribute__((unused)), pid_t p, boolean_t *managed)
-{
- struct ldcred *ldc = runtime_get_caller_creds();
- if ((ldc->euid != geteuid()) && (ldc->euid != 0)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- /* This is so loginwindow doesn't try to quit GUI apps that have been launched
- * directly by launchd as agents.
- */
- job_t j_for_pid = jobmgr_find_by_pid_deep(root_jobmgr, p, false);
- if (j_for_pid && !j_for_pid->anonymous && !j_for_pid->legacy_LS_job) {
- *managed = true;
- }
-
- return BOOTSTRAP_SUCCESS;
-}
-
-kern_return_t
-job_mig_port_for_label(job_t j __attribute__((unused)), name_t label, mach_port_t *mp)
-{
- if (!j) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- struct ldcred *ldc = runtime_get_caller_creds();
- kern_return_t kr = BOOTSTRAP_NOT_PRIVILEGED;
-
-#if HAVE_SANDBOX
- if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-#endif
-
- mach_port_t _mp = MACH_PORT_NULL;
- if (!j->deny_job_creation && (ldc->euid == 0 || ldc->euid == geteuid())) {
- job_t target_j = job_find(NULL, label);
- if (jobmgr_assumes(root_jobmgr, target_j != NULL)) {
- if (target_j->j_port == MACH_PORT_NULL) {
- (void)job_assumes(target_j, job_setup_machport(target_j) == true);
- }
-
- _mp = target_j->j_port;
- kr = _mp != MACH_PORT_NULL ? BOOTSTRAP_SUCCESS : BOOTSTRAP_NO_MEMORY;
- } else {
- kr = BOOTSTRAP_NO_MEMORY;
- }
- }
-
- *mp = _mp;
- return kr;
-}
-
-#if !TARGET_OS_EMBEDDED
-kern_return_t
-job_mig_set_security_session(job_t j, uuid_t uuid, mach_port_t asport)
-{
- if (!j) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- uuid_string_t uuid_str;
- uuid_unparse(uuid, uuid_str);
- job_log(j, LOG_DEBUG, "Setting session %u for UUID %s...", asport, uuid_str);
-
- job_t ji = NULL, jt = NULL;
- LIST_FOREACH_SAFE(ji, &s_needing_sessions, sle, jt) {
- uuid_string_t uuid_str2;
- uuid_unparse(ji->expected_audit_uuid, uuid_str2);
-
- if (uuid_compare(uuid, ji->expected_audit_uuid) == 0) {
- uuid_clear(ji->expected_audit_uuid);
- if (asport != MACH_PORT_NULL ) {
- job_log(ji, LOG_DEBUG, "Job should join session with port %u", asport);
- (void)job_assumes(j, launchd_mport_copy_send(asport) == KERN_SUCCESS);
- } else {
- job_log(ji, LOG_DEBUG, "No session to set for job. Using our session.");
- }
-
- ji->asport = asport;
- LIST_REMOVE(ji, needing_session_sle);
- job_dispatch(ji, false);
- }
- }
-
- /* Each job that the session port was set for holds a reference. At the end of
- * the loop, there will be one extra reference belonging to this MiG protocol.
- * We need to release it so that the session goes away when all the jobs
- * referencing it are unloaded.
- */
- (void)job_assumes(j, launchd_mport_deallocate(asport) == KERN_SUCCESS);
-
- return KERN_SUCCESS;
-}
-#else
-kern_return_t
-job_mig_set_security_session(job_t j __attribute__((unused)), uuid_t uuid __attribute__((unused)), mach_port_t session __attribute__((unused)))
-{
- return KERN_SUCCESS;
-}
-#endif
-
-jobmgr_t
-jobmgr_find_by_name(jobmgr_t jm, const char *where)
-{
- jobmgr_t jmi, jmi2;
-
- /* NULL is only passed for our custom API for LaunchServices. If that is the case, we do magic. */
- if (where == NULL) {
- if (strcasecmp(jm->name, VPROCMGR_SESSION_LOGINWINDOW) == 0) {
- where = VPROCMGR_SESSION_LOGINWINDOW;
- } else {
- where = VPROCMGR_SESSION_AQUA;
- }
- }
-
- if (strcasecmp(jm->name, where) == 0) {
- return jm;
- }
-
- if (strcasecmp(where, VPROCMGR_SESSION_BACKGROUND) == 0 && !pid1_magic) {
- jmi = root_jobmgr;
- goto jm_found;
- }
-
- SLIST_FOREACH(jmi, &root_jobmgr->submgrs, sle) {
- if (unlikely(jmi->shutting_down)) {
- continue;
- } else if (jmi->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) {
- continue;
- } else if (strcasecmp(jmi->name, where) == 0) {
- goto jm_found;
- } else if (strcasecmp(jmi->name, VPROCMGR_SESSION_BACKGROUND) == 0 && pid1_magic) {
- SLIST_FOREACH(jmi2, &jmi->submgrs, sle) {
- if (strcasecmp(jmi2->name, where) == 0) {
- jmi = jmi2;
- goto jm_found;
- }
- }
- }
- }
-
-jm_found:
- return jmi;
-}
-
-kern_return_t
-job_mig_move_subset(job_t j, mach_port_t target_subset, name_t session_type, mach_port_t asport, uint64_t flags)
-{
- mach_msg_type_number_t l2l_i, l2l_port_cnt = 0;
- mach_port_array_t l2l_ports = NULL;
- mach_port_t reqport, rcvright;
- kern_return_t kr = 1;
- launch_data_t out_obj_array = NULL;
- struct ldcred *ldc = runtime_get_caller_creds();
- jobmgr_t jmr = NULL;
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (job_mig_intran2(root_jobmgr, target_subset, ldc->pid)) {
- job_log(j, LOG_ERR, "Moving a session to ourself is bogus.");
-
- kr = BOOTSTRAP_NOT_PRIVILEGED;
- goto out;
- }
-
- job_log(j, LOG_DEBUG, "Move subset attempt: 0x%x", target_subset);
-
- kr = _vproc_grab_subset(target_subset, &reqport, &rcvright, &out_obj_array, &l2l_ports, &l2l_port_cnt);
-
- if (!job_assumes(j, kr == 0)) {
- goto out;
- }
-
- launchd_assert(launch_data_array_get_count(out_obj_array) == l2l_port_cnt);
-
- if (!job_assumes(j, (jmr = jobmgr_new(j->mgr, reqport, rcvright, false, session_type, false, asport)) != NULL)) {
- kr = BOOTSTRAP_NO_MEMORY;
- goto out;
- }
-
- jmr->properties |= BOOTSTRAP_PROPERTY_MOVEDSUBSET;
-
- /* This is a hack. We should be doing this in jobmgr_new(), but since we're in the middle of
- * processing an IPC request, we'll do this action before the new job manager can get any IPC
- * requests. This serialization is guaranteed since we are single-threaded in that respect.
- */
- if (flags & LAUNCH_GLOBAL_ON_DEMAND) {
- /* This is so awful. */
- /* Remove the job from its current job manager. */
- LIST_REMOVE(j, sle);
- LIST_REMOVE(j, pid_hash_sle);
-
- /* Put the job into the target job manager. */
- LIST_INSERT_HEAD(&jmr->jobs, j, sle);
- LIST_INSERT_HEAD(&jmr->active_jobs[ACTIVE_JOB_HASH(j->p)], j, pid_hash_sle);
-
- j->mgr = jmr;
- job_set_global_on_demand(j, true);
-
- if (!j->holds_ref) {
- j->holds_ref = true;
- runtime_add_ref();
- }
- }
-
- for (l2l_i = 0; l2l_i < l2l_port_cnt; l2l_i++) {
- launch_data_t tmp, obj_at_idx;
- struct machservice *ms;
- job_t j_for_service;
- const char *serv_name;
- pid_t target_pid;
- bool serv_perpid;
-
- (void)job_assumes(j, obj_at_idx = launch_data_array_get_index(out_obj_array, l2l_i));
- (void)job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_PID));
- target_pid = (pid_t)launch_data_get_integer(tmp);
- (void)job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_PERPID));
- serv_perpid = launch_data_get_bool(tmp);
- (void)job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_NAME));
- serv_name = launch_data_get_string(tmp);
-
- j_for_service = jobmgr_find_by_pid(jmr, target_pid, true);
-
- if (unlikely(!j_for_service)) {
- /* The PID probably exited */
- (void)job_assumes(j, launchd_mport_deallocate(l2l_ports[l2l_i]) == KERN_SUCCESS);
- continue;
- }
-
- if (likely(ms = machservice_new(j_for_service, serv_name, &l2l_ports[l2l_i], serv_perpid))) {
- job_log(j, LOG_DEBUG, "Importing %s into new bootstrap.", serv_name);
- machservice_request_notifications(ms);
- }
- }
-
- kr = 0;
-
-out:
- if (out_obj_array) {
- launch_data_free(out_obj_array);
- }
-
- if (l2l_ports) {
- mig_deallocate((vm_address_t)l2l_ports, l2l_port_cnt * sizeof(l2l_ports[0]));
- }
-
- if (kr == 0) {
- if (target_subset) {
- (void)job_assumes(j, launchd_mport_deallocate(target_subset) == KERN_SUCCESS);
- }
- if (asport) {
- (void)job_assumes(j, launchd_mport_deallocate(asport) == KERN_SUCCESS);
- }
- } else if (jmr) {
- jobmgr_shutdown(jmr);
- }
-
- return kr;
-}
-
-kern_return_t
-job_mig_init_session(job_t j, name_t session_type, mach_port_t asport)
-{
- if (!j) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- job_t j2;
-
- kern_return_t kr = BOOTSTRAP_NO_MEMORY;
- if (j->mgr->session_initialized) {
- job_log(j, LOG_ERR, "Tried to initialize an already setup session!");
- kr = BOOTSTRAP_NOT_PRIVILEGED;
- } else if (strcmp(session_type, VPROCMGR_SESSION_LOGINWINDOW) == 0) {
- jobmgr_t jmi;
-
- /*
- * 5330262
- *
- * We're working around LoginWindow and the WindowServer.
- *
- * In practice, there is only one LoginWindow session. Unfortunately, for certain
- * scenarios, the WindowServer spawns loginwindow, and in those cases, it frequently
- * spawns a replacement loginwindow session before cleaning up the previous one.
- *
- * We're going to use the creation of a new LoginWindow context as a clue that the
- * previous LoginWindow context is on the way out and therefore we should just
- * kick-start the shutdown of it.
- */
-
- SLIST_FOREACH(jmi, &root_jobmgr->submgrs, sle) {
- if (unlikely(jmi->shutting_down)) {
- continue;
- } else if (strcasecmp(jmi->name, session_type) == 0) {
- jobmgr_shutdown(jmi);
- break;
- }
- }
- }
-
- jobmgr_log(j->mgr, LOG_DEBUG, "Initializing as %s", session_type);
- strcpy(j->mgr->name_init, session_type);
-
- if (job_assumes(j, (j2 = jobmgr_init_session(j->mgr, session_type, false)))) {
- j2->asport = asport;
- (void)job_assumes(j, job_dispatch(j2, true));
- kr = BOOTSTRAP_SUCCESS;
- }
-
- return kr;
-}
-
-kern_return_t
-job_mig_switch_to_session(job_t j, mach_port_t requestor_port, name_t session_name, mach_port_t asport, mach_port_t *new_bsport)
-{
- struct ldcred *ldc = runtime_get_caller_creds();
- if (!jobmgr_assumes(root_jobmgr, j != NULL)) {
- jobmgr_log(root_jobmgr, LOG_ERR, "%s() called with NULL job: PID %d", __func__, ldc->pid);
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (j->mgr->shutting_down) {
- return BOOTSTRAP_UNKNOWN_SERVICE;
- }
-
- job_log(j, LOG_DEBUG, "Job wants to move to %s session.", session_name);
-
- if (!job_assumes(j, pid1_magic == false)) {
- job_log(j, LOG_WARNING, "Switching sessions is not allowed in the system Mach bootstrap.");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- if (!j->anonymous) {
- job_log(j, LOG_NOTICE, "Non-anonymous job tried to switch sessions. Please use LimitLoadToSessionType instead.");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- jobmgr_t target_jm = jobmgr_find_by_name(root_jobmgr, session_name);
- if (target_jm == j->mgr) {
- job_log(j, LOG_DEBUG, "Job is already in its desired session (%s).", session_name);
- *new_bsport = target_jm->jm_port;
- return BOOTSTRAP_SUCCESS;
- }
-
- if (!target_jm) {
- target_jm = jobmgr_new(j->mgr, requestor_port, MACH_PORT_NULL, false, session_name, false, asport);
- if (target_jm) {
- target_jm->properties |= BOOTSTRAP_PROPERTY_IMPLICITSUBSET;
- (void)job_assumes(j, launchd_mport_deallocate(asport) == KERN_SUCCESS);
- }
- }
-
- if (!job_assumes(j, target_jm != NULL)) {
- job_log(j, LOG_WARNING, "Could not find %s session!", session_name);
- return BOOTSTRAP_NO_MEMORY;
- }
-
- /* Remove the job from it's current job manager. */
- LIST_REMOVE(j, sle);
- LIST_REMOVE(j, pid_hash_sle);
-
- job_t ji = NULL, jit = NULL;
- LIST_FOREACH_SAFE(ji, &j->mgr->global_env_jobs, global_env_sle, jit) {
- if (ji == j) {
- LIST_REMOVE(ji, global_env_sle);
- break;
- }
- }
-
- /* Put the job into the target job manager. */
- LIST_INSERT_HEAD(&target_jm->jobs, j, sle);
- LIST_INSERT_HEAD(&target_jm->active_jobs[ACTIVE_JOB_HASH(j->p)], j, pid_hash_sle);
-
- if (ji) {
- LIST_INSERT_HEAD(&target_jm->global_env_jobs, j, global_env_sle);
- }
-
- /* Move our Mach services over if we're not in a flat namespace. */
- if (!g_flat_mach_namespace && !SLIST_EMPTY(&j->machservices)) {
- struct machservice *msi = NULL, *msit = NULL;
- SLIST_FOREACH_SAFE(msi, &j->machservices, sle, msit) {
- LIST_REMOVE(msi, name_hash_sle);
- LIST_INSERT_HEAD(&target_jm->ms_hash[hash_ms(msi->name)], msi, name_hash_sle);
- }
- }
-
- j->mgr = target_jm;
-
- if (!j->holds_ref) {
- /* Anonymous jobs which move around are particularly interesting to us, so we want to
- * stick around while they're still around.
- * For example, login calls into the PAM launchd module, which moves the process into
- * the StandardIO session by default. So we'll hold a reference on that job to prevent
- * ourselves from going away.
- */
- j->holds_ref = true;
- runtime_add_ref();
- }
-
- *new_bsport = target_jm->jm_port;
-
- return KERN_SUCCESS;
-}
-
-kern_return_t
-job_mig_take_subset(job_t j, mach_port_t *reqport, mach_port_t *rcvright,
- vm_offset_t *outdata, mach_msg_type_number_t *outdataCnt,
- mach_port_array_t *portsp, unsigned int *ports_cnt)
-{
- launch_data_t tmp_obj, tmp_dict, outdata_obj_array = NULL;
- mach_port_array_t ports = NULL;
- unsigned int cnt = 0, cnt2 = 0;
- size_t packed_size;
- struct machservice *ms;
- jobmgr_t jm;
- job_t ji;
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- jm = j->mgr;
-
- if (unlikely(!pid1_magic)) {
- job_log(j, LOG_ERR, "Only the system launchd will transfer Mach sub-bootstraps.");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- if (unlikely(jobmgr_parent(jm) == NULL)) {
- job_log(j, LOG_ERR, "Root Mach bootstrap cannot be transferred.");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- if (unlikely(strcasecmp(jm->name, VPROCMGR_SESSION_AQUA) == 0)) {
- job_log(j, LOG_ERR, "Cannot transfer a setup GUI session.");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- if (unlikely(!j->anonymous)) {
- job_log(j, LOG_ERR, "Only the anonymous job can transfer Mach sub-bootstraps.");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- job_log(j, LOG_DEBUG, "Transferring sub-bootstrap to the per session launchd.");
-
- outdata_obj_array = launch_data_alloc(LAUNCH_DATA_ARRAY);
- if (!job_assumes(j, outdata_obj_array)) {
- goto out_bad;
- }
-
- *outdataCnt = 20 * 1024 * 1024;
- mig_allocate(outdata, *outdataCnt);
- if (!job_assumes(j, *outdata != 0)) {
- return 1;
- }
-
- LIST_FOREACH(ji, &j->mgr->jobs, sle) {
- if (!ji->anonymous) {
- continue;
- }
- SLIST_FOREACH(ms, &ji->machservices, sle) {
- cnt++;
- }
- }
-
- mig_allocate((vm_address_t *)&ports, cnt * sizeof(ports[0]));
- if (!job_assumes(j, ports != NULL)) {
- goto out_bad;
- }
-
- LIST_FOREACH(ji, &j->mgr->jobs, sle) {
- if (!ji->anonymous) {
- continue;
- }
-
- SLIST_FOREACH(ms, &ji->machservices, sle) {
- if (job_assumes(j, (tmp_dict = launch_data_alloc(LAUNCH_DATA_DICTIONARY)))) {
- (void)job_assumes(j, launch_data_array_set_index(outdata_obj_array, tmp_dict, cnt2));
- } else {
- goto out_bad;
- }
-
- if (job_assumes(j, (tmp_obj = launch_data_new_string(machservice_name(ms))))) {
- (void)job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_NAME));
- } else {
- goto out_bad;
- }
-
- if (job_assumes(j, (tmp_obj = launch_data_new_integer((ms->job->p))))) {
- (void)job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_PID));
- } else {
- goto out_bad;
- }
-
- if (job_assumes(j, (tmp_obj = launch_data_new_bool((ms->per_pid))))) {
- (void)job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_PERPID));
- } else {
- goto out_bad;
- }
-
- ports[cnt2] = machservice_port(ms);
-
- /* Increment the send right by one so we can shutdown the jobmgr cleanly */
- (void)jobmgr_assumes(jm, (errno = launchd_mport_copy_send(ports[cnt2])) == KERN_SUCCESS);
- cnt2++;
- }
- }
-
- (void)job_assumes(j, cnt == cnt2);
-
- runtime_ktrace0(RTKT_LAUNCHD_DATA_PACK);
- packed_size = launch_data_pack(outdata_obj_array, (void *)*outdata, *outdataCnt, NULL, NULL);
- if (!job_assumes(j, packed_size != 0)) {
- goto out_bad;
- }
-
- launch_data_free(outdata_obj_array);
-
- *portsp = ports;
- *ports_cnt = cnt;
-
- *reqport = jm->req_port;
- *rcvright = jm->jm_port;
-
- jm->req_port = 0;
- jm->jm_port = 0;
-
- workaround_5477111 = j;
-
- jobmgr_shutdown(jm);
-
- return BOOTSTRAP_SUCCESS;
-
-out_bad:
- if (outdata_obj_array) {
- launch_data_free(outdata_obj_array);
- }
- if (*outdata) {
- mig_deallocate(*outdata, *outdataCnt);
- }
- if (ports) {
- mig_deallocate((vm_address_t)ports, cnt * sizeof(ports[0]));
- }
-
- return BOOTSTRAP_NO_MEMORY;
-}
-
-kern_return_t
-job_mig_subset(job_t j, mach_port_t requestorport, mach_port_t *subsetportp)
-{
- int bsdepth = 0;
- jobmgr_t jmr;
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
- if (j->mgr->shutting_down) {
- return BOOTSTRAP_UNKNOWN_SERVICE;
- }
-
- jmr = j->mgr;
-
- while ((jmr = jobmgr_parent(jmr)) != NULL) {
- bsdepth++;
- }
-
- /* Since we use recursion, we need an artificial depth for subsets */
- if (unlikely(bsdepth > 100)) {
- job_log(j, LOG_ERR, "Mach sub-bootstrap create request failed. Depth greater than: %d", bsdepth);
- return BOOTSTRAP_NO_MEMORY;
- }
-
- char name[NAME_MAX];
- snprintf(name, sizeof(name), "%s[%i].subset.%i", j->anonymous ? j->prog : j->label, j->p, MACH_PORT_INDEX(requestorport));
-
- if (!job_assumes(j, (jmr = jobmgr_new(j->mgr, requestorport, MACH_PORT_NULL, false, name, true, j->asport)) != NULL)) {
- if (unlikely(requestorport == MACH_PORT_NULL)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- return BOOTSTRAP_NO_MEMORY;
- }
-
- *subsetportp = jmr->jm_port;
- jmr->properties |= BOOTSTRAP_PROPERTY_EXPLICITSUBSET;
-
- /* A job could create multiple subsets, so only add a reference the first time
- * it does so we don't have to keep a count.
- */
- if (j->anonymous && !j->holds_ref) {
- j->holds_ref = true;
- runtime_add_ref();
- }
-
- job_log(j, LOG_DEBUG, "Job created a subset named \"%s\"", jmr->name);
- return BOOTSTRAP_SUCCESS;
-}
-
-#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__
-job_t
-xpc_domain_import_service(jobmgr_t jm, launch_data_t pload)
-{
- jobmgr_t where2put = NULL;
-
- launch_data_t destname = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_XPCDOMAIN);
- if (destname) {
- if (launch_data_get_type(destname) == LAUNCH_DATA_STRING) {
- const char *str = launch_data_get_string(destname);
- if (strcmp(str, XPC_DOMAIN_TYPE_SYSTEM) == 0) {
- where2put = _s_xpc_system_domain;
- } else if (strcmp(str, XPC_DOMAIN_TYPE_PERUSER) == 0) {
- where2put = jobmgr_find_xpc_per_user_domain(jm, jm->req_euid);
- } else if (strcmp(str, XPC_DOMAIN_TYPE_PERSESSION) == 0) {
- where2put = jobmgr_find_xpc_per_session_domain(jm, jm->req_asid);
- } else {
- jobmgr_log(jm, LOG_ERR, "Invalid XPC domain type: %s", str);
- errno = EINVAL;
- }
- } else {
- jobmgr_log(jm, LOG_ERR, "XPC domain type is not a string.");
- errno = EINVAL;
- }
-
- if (where2put) {
- launch_data_t mi = NULL;
- if ((mi = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_MULTIPLEINSTANCES))) {
- if (launch_data_get_type(mi) == LAUNCH_DATA_BOOL && launch_data_get_bool(mi)) {
- jobmgr_log(where2put, LOG_ERR, "Multiple-instance services are not supported in this domain.");
- where2put = NULL;
- errno = EINVAL;
- }
- }
- }
- } else {
- where2put = jm;
- }
-
- job_t j = NULL;
- if (where2put) {
- jobmgr_log(where2put, LOG_DEBUG, "Importing service...");
- j = jobmgr_import2(where2put, pload);
- if (j) {
- j->xpc_service = true;
- if (where2put->xpc_singleton) {
- /* If the service was destined for one of the global domains,
- * then we have to alias it into our local domain to reserve the
- * name.
- */
- job_t ja = job_new_alias(jm, j);
- if (!ja) {
- /* If we failed to alias the job because of a conflict over
- * the label, then we remove it from the global domain. We
- * don't want to risk having imported a malicious job into
- * one of the global domains.
- */
- if (errno != EEXIST) {
- job_assumes(j, errno == 0);
- } else {
- job_log(j, LOG_ERR, "Failed to alias job into: %s", where2put->name);
- }
-
- job_remove(j);
- } else {
- ja->xpc_service = true;
- j = ja;
- }
- }
- }
- }
-
- return j;
-}
-
-kern_return_t
-xpc_domain_import2(job_t j, mach_port_t reqport, mach_port_t dport)
-{
- if (unlikely(!pid1_magic)) {
- job_log(j, LOG_ERR, "XPC domains may only reside in PID 1.");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- if (!j || !MACH_PORT_VALID(reqport)) {
- return BOOTSTRAP_UNKNOWN_SERVICE;
- }
- if (root_jobmgr->shutting_down) {
- jobmgr_log(root_jobmgr, LOG_ERR, "Attempt to create new domain while shutting down.");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- if (!j->xpc_bootstrapper) {
- job_log(j, LOG_ERR, "Attempt to create new XPC domain by unprivileged job.");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- kern_return_t kr = BOOTSTRAP_NO_MEMORY;
- /* All XPC domains are children of the root job manager. What we're creating
- * here is really just a skeleton. By creating it, we're adding reqp to our
- * port set. It will have two messages on it. The first specifies the
- * environment of the originator. This is so we can cache it and hand it to
- * xpcproxy to bootstrap our services. The second is the set of jobs that is
- * to be bootstrapped in.
- */
- jobmgr_t jm = jobmgr_new(root_jobmgr, reqport, dport, false, NULL, true, MACH_PORT_NULL);
- if (job_assumes(j, jm != NULL)) {
- jm->properties |= BOOTSTRAP_PROPERTY_XPC_DOMAIN;
- jm->shortdesc = "private";
- kr = BOOTSTRAP_SUCCESS;
- }
-
- return kr;
-}
-
-kern_return_t
-xpc_domain_set_environment(job_t j, mach_port_t rp, mach_port_t bsport, mach_port_t excport, vm_offset_t ctx, mach_msg_type_number_t ctx_sz)
-{
- if (!j) {
- /* Due to the whacky nature of XPC service bootstrapping, we can end up
- * getting this message long after the requesting process has gone away.
- * See <rdar://problem/8593143>.
- */
- return BOOTSTRAP_UNKNOWN_SERVICE;
- }
-
- jobmgr_t jm = j->mgr;
- if (!(jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- if (jm->req_asport != MACH_PORT_NULL) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- struct ldcred *ldc = runtime_get_caller_creds();
- struct proc_bsdshortinfo proc;
- if (proc_pidinfo(ldc->pid, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
- if (errno != ESRCH) {
- jobmgr_assumes(jm, errno == 0);
- }
-
- jm->error = errno;
- jobmgr_remove(jm);
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (!jobmgr_assumes(jm, audit_session_port(ldc->asid, &jm->req_asport) == 0)) {
- jm->error = EPERM;
- jobmgr_remove(jm);
- job_log(j, LOG_ERR, "Failed to get port for ASID: %u", ldc->asid);
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- (void)snprintf(jm->name_init, NAME_MAX, "com.apple.xpc.domain.%s[%i]", proc.pbsi_comm, ldc->pid);
- strlcpy(jm->owner, proc.pbsi_comm, sizeof(jm->owner));
- jm->req_bsport = bsport;
- jm->req_excport = excport;
- jm->req_rport = rp;
- jm->req_ctx = ctx;
- jm->req_ctx_sz = ctx_sz;
- jm->req_pid = ldc->pid;
- jm->req_euid = ldc->euid;
- jm->req_egid = ldc->egid;
- jm->req_asid = ldc->asid;
-
- return KERN_SUCCESS;
-}
-
-kern_return_t
-xpc_domain_load_services(job_t j, vm_offset_t services_buff, mach_msg_type_number_t services_sz)
-{
- if (!j) {
- return BOOTSTRAP_UNKNOWN_SERVICE;
- }
-
- job_t rootj = jobmgr_find_by_pid(root_jobmgr, j->p, false);
- if (!(rootj && rootj->xpc_bootstrapper)) {
- job_log(j, LOG_ERR, "Attempt to load services into XPC domain by unprivileged job.");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- /* This is just for XPC domains (for now). */
- if (!(j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- if (j->mgr->session_initialized) {
- jobmgr_log(j->mgr, LOG_ERR, "Attempt to initialize an already-initialized XPC domain.");
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- size_t offset = 0;
- launch_data_t services = launch_data_unpack((void *)services_buff, services_sz, NULL, 0, &offset, NULL);
- if (!jobmgr_assumes(j->mgr, services != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- size_t i = 0;
- size_t c = launch_data_array_get_count(services);
- for (i = 0; i < c; i++) {
- job_t nj = NULL;
- launch_data_t ploadi = launch_data_array_get_index(services, i);
- if (!(nj = xpc_domain_import_service(j->mgr, ploadi))) {
- /* If loading one job fails, just fail the whole thing. At this
- * point, xpchelper should receive the failure and then just refuse
- * to launch the application, since its XPC services could not be
- * fully bootstrapped.
- *
- * Take care to not reference the job or its manager after this
- * point.
- */
- if (errno == EINVAL) {
- jobmgr_log(j->mgr, LOG_ERR, "Service at index is not valid: %lu", i);
- } else if (errno == EEXIST) {
- /* If we get back EEXIST, we know that the payload was a
- * dictionary with a label. But, well, I guess it never hurts to
- * check.
- */
- char *label = "(bogus)";
- if (launch_data_get_type(ploadi) == LAUNCH_DATA_DICTIONARY) {
- launch_data_t llabel = launch_data_dict_lookup(ploadi, LAUNCH_JOBKEY_LABEL);
- if (launch_data_get_type(llabel) == LAUNCH_DATA_STRING) {
- label = (char *)launch_data_get_string(llabel);
- }
- }
- jobmgr_log(j->mgr, LOG_ERR, "Service name conflict: %s", label);
- }
-
- j->mgr->error = errno;
- jobmgr_log(j->mgr, LOG_ERR, "Obliterating domain.");
- jobmgr_remove(j->mgr);
- break;
- } else {
- jobmgr_log(j->mgr, LOG_DEBUG, "Imported service %s", nj->label);
- job_dispatch(nj, false);
- }
- }
-
- kern_return_t result = BOOTSTRAP_NO_MEMORY;
- if (i == c) {
- j->mgr->session_initialized = true;
- (void)jobmgr_assumes(j->mgr, xpc_call_wakeup(j->mgr->req_rport, BOOTSTRAP_SUCCESS) == KERN_SUCCESS);
- j->mgr->req_rport = MACH_PORT_NULL;
-
- /* Returning a failure code will destroy the message, whereas returning
- * success will not, so we need to clean up here.
- */
- mig_deallocate(services_buff, services_sz);
- result = BOOTSTRAP_SUCCESS;
- }
-
- return result;
-}
-
-kern_return_t
-xpc_domain_check_in(job_t j, mach_port_t *bsport, mach_port_t *sbsport, mach_port_t *excport, mach_port_t *asport, uint32_t *uid, uint32_t *gid, int32_t *asid, vm_offset_t *ctx, mach_msg_type_number_t *ctx_sz)
-{
- if (!jobmgr_assumes(root_jobmgr, j != NULL)) {
- return BOOTSTRAP_UNKNOWN_SERVICE;
- }
- jobmgr_t jm = j->mgr;
- if (!(jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- if (jm->req_asport == MACH_PORT_NULL) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- *bsport = jm->req_bsport;
- *sbsport = root_jobmgr->jm_port;
- *excport = jm->req_excport;
- *asport = jm->req_asport;
- *uid = jm->req_euid;
- *gid = jm->req_egid;
- *asid = jm->req_asid;
-
- *ctx = jm->req_ctx;
- *ctx_sz = jm->req_ctx_sz;
-
- return KERN_SUCCESS;
-}
-
-kern_return_t
-xpc_domain_get_service_name(job_t j, event_name_t name)
-{
- if (!j) {
- return BOOTSTRAP_NO_MEMORY;
- }
- if (!j->xpc_service) {
- jobmgr_log(j->mgr, LOG_ERR, "Attempt to get service name by non-XPC service: %s", j->label);
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- struct machservice * ms = SLIST_FIRST(&j->machservices);
- if (!ms) {
- jobmgr_log(j->mgr, LOG_ERR, "Attempt to get service name of job with no machservices: %s", j->label);
- return BOOTSTRAP_UNKNOWN_SERVICE;
- }
-
- (void)strlcpy(name, ms->name, sizeof(event_name_t));
- return BOOTSTRAP_SUCCESS;
-}
-#endif /* __LAUNCH_DISABLE_XPC_SUPPORT__ */
-
-kern_return_t
-xpc_events_get_channel_name(job_t j __attribute__((unused)), event_name_t stream __attribute__((unused)), uint64_t token __attribute__((unused)), event_name_t name __attribute__((unused)))
-{
- return KERN_FAILURE;
-}
-
-kern_return_t
-xpc_events_get_event_name(job_t j, event_name_t stream, uint64_t token, event_name_t name)
-{
- struct externalevent *event = externalevent_find(stream, token);
- if (event && j->event_monitor) {
- (void)strcpy(name, event->name);
- } else {
- event = NULL;
- }
-
- return event ? BOOTSTRAP_SUCCESS : BOOTSTRAP_UNKNOWN_SERVICE;
-}
-
-kern_return_t
-xpc_events_set_event(job_t j, event_name_t stream, event_name_t key, vm_offset_t event, mach_msg_type_number_t eventCnt)
-{
- if (j->anonymous) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- struct externalevent *eei = NULL;
- LIST_FOREACH(eei, &j->events, job_le) {
- if (strcmp(eei->name, key) == 0 && strcmp(eei->sys->name, stream) == 0) {
- externalevent_delete(eei);
- eventsystem_ping();
- break;
- }
- }
-
- bool success = false;
- struct eventsystem *es = eventsystem_find(stream);
- if (!es) {
- es = eventsystem_new(stream);
- (void)job_assumes(j, es != NULL);
- }
-
- if (es) {
- size_t offset = 0;
- launch_data_t unpacked = launch_data_unpack((void *)event, eventCnt, NULL, 0, &offset, 0);
- if (unpacked && launch_data_get_type(unpacked) == LAUNCH_DATA_DICTIONARY) {
- success = externalevent_new(j, es, key, unpacked);
- }
- }
-
- if (!success) {
- mig_deallocate(event, eventCnt);
- }
-
- return KERN_SUCCESS;
-}
-
-kern_return_t
-xpc_events_get_event(job_t j, event_name_t stream, event_name_t key, vm_offset_t *event, mach_msg_type_number_t *eventCnt)
-{
- struct externalevent *eei = NULL;
- LIST_FOREACH(eei, &j->events, job_le) {
- if (strcmp(eei->name, key) == 0 && strcmp(eei->sys->name, stream) == 0) {
- /* Big enough. */
- *eventCnt = 10 * 1024;
- mig_allocate(event, *eventCnt);
-
- size_t sz = launch_data_pack(eei->event, (void *)*event, *eventCnt, NULL, NULL);
- if (!job_assumes(j, sz != 0)) {
- mig_deallocate(*event, *eventCnt);
- return BOOTSTRAP_NO_MEMORY;
- }
-
- return BOOTSTRAP_SUCCESS;
- }
- }
-
- return BOOTSTRAP_UNKNOWN_SERVICE;
-}
-
-struct machservice *
-xpc_events_find_channel(job_t j, event_name_t stream, mach_port_t *p)
-{
- struct machservice *msi = NULL;
- SLIST_FOREACH(msi, &j->machservices, sle) {
- if (strcmp(stream, msi->name) == 0) {
- break;
- }
- }
-
- if (!msi) {
- mach_port_t sp = MACH_PORT_NULL;
- msi = machservice_new(j, stream, &sp, false);
- if (job_assumes(j, msi)) {
- /* Hack to keep this from being publicly accessible through
- * bootstrap_look_up().
- */
- LIST_REMOVE(msi, name_hash_sle);
- msi->event_channel = true;
- *p = sp;
-
- /* If we call job_dispatch() here before the audit session for the
- * job has been set, we'll end up not watching this service. But we
- * also have to take care not to watch the port if the job is
- * active.
- *
- * See <rdar://problem/10357855>.
- */
- if (!j->currently_ignored) {
- machservice_watch(j, msi);
- }
- } else {
- errno = BOOTSTRAP_NO_MEMORY;
- }
- } else {
- if (!msi->event_channel) {
- job_log(j, LOG_ERR, "This job registered a MachService name identical to the requested event channel name: %s", stream);
- msi = NULL;
- errno = BOOTSTRAP_NAME_IN_USE;
- } else {
- *p = msi->port;
- }
- }
-
- return msi;
-}
-
-kern_return_t
-xpc_events_channel_check_in(job_t j, event_name_t stream, uint64_t flags __attribute__((unused)), mach_port_t *p)
-{
- struct machservice *ms = xpc_events_find_channel(j, stream, p);
- if (ms) {
- if (ms->isActive) {
- job_log(j, LOG_ERR, "Attempt to check in on event channel multiple times: %s", stream);
- *p = MACH_PORT_NULL;
- errno = BOOTSTRAP_SERVICE_ACTIVE;
- } else {
- job_checkin(j);
- machservice_request_notifications(ms);
- errno = BOOTSTRAP_SUCCESS;
- }
- }
-
- return errno;
-}
-
-kern_return_t
-xpc_events_channel_look_up(job_t j, event_name_t stream, event_token_t token, uint64_t flags __attribute__((unused)), mach_port_t *p)
-{
- if (!j->event_monitor) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- struct externalevent *ee = externalevent_find(stream, token);
- if (!ee) {
- return BOOTSTRAP_UNKNOWN_SERVICE;
- }
-
- struct machservice *ms = xpc_events_find_channel(ee->job, stream, p);
- if (ms) {
- errno = BOOTSTRAP_SUCCESS;
- }
-
- return errno;
-}
-
-kern_return_t
-job_mig_kickstart(job_t j, name_t targetlabel, pid_t *out_pid, unsigned int flags)
-{
- struct ldcred *ldc = runtime_get_caller_creds();
- job_t otherj;
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (unlikely(!(otherj = job_find(NULL, targetlabel)))) {
- return BOOTSTRAP_UNKNOWN_SERVICE;
- }
-
-#if TARGET_OS_EMBEDDED
- bool allow_non_root_kickstart = j->username && otherj->username && (strcmp(j->username, otherj->username) == 0);
-#else
- bool allow_non_root_kickstart = false;
-#endif
-
- if (ldc->euid != 0 && ldc->euid != geteuid() && !allow_non_root_kickstart) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
-#if HAVE_SANDBOX
- if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-#endif
-
- if (otherj->p && (flags & VPROCFLAG_STALL_JOB_EXEC)) {
- return BOOTSTRAP_SERVICE_ACTIVE;
- }
-
- otherj->stall_before_exec = (flags & VPROCFLAG_STALL_JOB_EXEC);
- otherj = job_dispatch(otherj, true);
-
- if (!job_assumes(j, otherj && otherj->p)) {
- /* <rdar://problem/6787083> Clear this flag if we failed to start the job. */
- otherj->stall_before_exec = false;
- return BOOTSTRAP_NO_MEMORY;
- }
-
- *out_pid = otherj->p;
-
- return 0;
-}
-
-kern_return_t
-job_mig_spawn_internal(job_t j, vm_offset_t indata, mach_msg_type_number_t indataCnt, mach_port_t asport, job_t *outj)
-{
- launch_data_t jobdata = NULL;
- size_t data_offset = 0;
- struct ldcred *ldc = runtime_get_caller_creds();
- job_t jr;
-
- if (!launchd_assumes(j != NULL)) {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (unlikely(j->deny_job_creation)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
-#if HAVE_SANDBOX
- if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-#endif
-
- if (unlikely(pid1_magic && ldc->euid && ldc->uid)) {
- job_log(j, LOG_DEBUG, "Punting spawn to per-user-context");
- return VPROC_ERR_TRY_PER_USER;
- }
-
- if (!job_assumes(j, indataCnt != 0)) {
- return 1;
- }
-
- runtime_ktrace0(RTKT_LAUNCHD_DATA_UNPACK);
- if (!job_assumes(j, (jobdata = launch_data_unpack((void *)indata, indataCnt, NULL, 0, &data_offset, NULL)) != NULL)) {
- return 1;
- }
-
- jobmgr_t target_jm = jobmgr_find_by_name(j->mgr, NULL);
- if (!jobmgr_assumes(j->mgr, target_jm != NULL)) {
- jobmgr_log(j->mgr, LOG_ERR, "This API can only be used by a process running within an Aqua session.");
- return 1;
- }
-
- jr = jobmgr_import2(target_jm ?: j->mgr, jobdata);
-
- launch_data_t label = NULL;
- launch_data_t wait4debugger = NULL;
- if (!jr) {
- switch (errno) {
- case EEXIST:
- /* If EEXIST was returned, we know that there is a label string in
- * the dictionary. So we don't need to check the types here; that
- * has already been done.
- */
- label = launch_data_dict_lookup(jobdata, LAUNCH_JOBKEY_LABEL);
- jr = job_find(NULL, launch_data_get_string(label));
- if (job_assumes(j, jr != NULL) && !jr->p) {
- wait4debugger = launch_data_dict_lookup(jobdata, LAUNCH_JOBKEY_WAITFORDEBUGGER);
- if (wait4debugger && launch_data_get_type(wait4debugger) == LAUNCH_DATA_BOOL) {
- if (launch_data_get_bool(wait4debugger)) {
- /* If the job exists, we're going to kick-start it, but
- * we need to give the caller the opportunity to start
- * it suspended if it so desires. But this will only
- * take effect if the job isn't running.
- */
- jr->wait4debugger_oneshot = true;
- }
- }
- }
-
- *outj = jr;
- return BOOTSTRAP_NAME_IN_USE;
- default:
- return BOOTSTRAP_NO_MEMORY;
- }
- }
-
- if (pid1_magic) {
- jr->mach_uid = ldc->uid;
- }
-
- jr->legacy_LS_job = true;
- jr->abandon_pg = true;
- jr->asport = asport;
- uuid_clear(jr->expected_audit_uuid);
- jr = job_dispatch(jr, true);
-
- if (!job_assumes(j, jr != NULL)) {
- job_remove(jr);
- return BOOTSTRAP_NO_MEMORY;
- }
-
- if (!job_assumes(jr, jr->p)) {
- job_remove(jr);
- return BOOTSTRAP_NO_MEMORY;
- }
-
- job_log(jr, LOG_DEBUG, "Spawned by PID %u: %s", j->p, j->label);
- *outj = jr;
-
- return BOOTSTRAP_SUCCESS;
-}
-
-kern_return_t
-job_mig_spawn2(job_t j, mach_port_t rp, vm_offset_t indata, mach_msg_type_number_t indataCnt, mach_port_t asport, pid_t *child_pid, mach_port_t *obsvr_port)
-{
- job_t nj = NULL;
- kern_return_t kr = job_mig_spawn_internal(j, indata, indataCnt, asport, &nj);
- if (likely(kr == KERN_SUCCESS)) {
- if (job_setup_exit_port(nj) != KERN_SUCCESS) {
- job_remove(nj);
- kr = BOOTSTRAP_NO_MEMORY;
- } else {
- /* Do not return until the job has called exec(3), thereby making it
- * safe for the caller to send it SIGCONT.
- *
- * <rdar://problem/9042798>
- */
- nj->spawn_reply_port = rp;
- kr = MIG_NO_REPLY;
- }
- } else if (kr == BOOTSTRAP_NAME_IN_USE) {
- bool was_running = nj->p;
- if (job_dispatch(nj, true)) {
- if (!was_running) {
- job_log(nj, LOG_DEBUG, "Job exists but is not running. Kick-starting.");
-
- if (job_setup_exit_port(nj) == KERN_SUCCESS) {
- nj->spawn_reply_port = rp;
- kr = MIG_NO_REPLY;
- } else {
- kr = BOOTSTRAP_NO_MEMORY;
- }
- } else {
- *obsvr_port = MACH_PORT_NULL;
- *child_pid = nj->p;
- kr = KERN_SUCCESS;
- }
- } else {
- job_log(nj, LOG_ERR, "Failed to dispatch job, requestor: %s", j->label);
- kr = BOOTSTRAP_UNKNOWN_SERVICE;
- }
- }
-
- mig_deallocate(indata, indataCnt);
- return kr;
-}
-
-kern_return_t
-job_mig_event_source_check_in(job_t j, name_t name, mach_port_t ping_port, vm_offset_t *outval, mach_msg_type_number_t *outvalCnt, uint64_t *tokens)
-{
- if (!j || !j->event_monitor) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- /* Update our ping-port. One ping will force all the notification systems
- * to check in, so they'll all give us send-once rights. It doesn't really
- * matter which one we keep around. It's not the most efficient thing ever,
- * but keep in mind that, by doing this over one channel, we can do it over
- * the job's MachService. This means that we'll get it back when the job dies,
- * and we can create ourselves a send-once right if we didn't have one already,
- * and we can just keep the helper alive without it needing to bootstrap
- * communication.
- *
- * So we're trading efficiency for robustness. In this case, the checkins
- * should happen pretty infrequently, so it's pretty worth it.
- */
- if (_s_event_update_port != MACH_PORT_NULL) {
- (void)job_assumes(j, launchd_mport_deallocate(_s_event_update_port) == KERN_SUCCESS);
- }
- _s_event_update_port = ping_port;
-
- kern_return_t result = BOOTSTRAP_NO_MEMORY;
- launch_data_t arr = launch_data_alloc(LAUNCH_DATA_ARRAY);
- if (job_assumes(j, arr != NULL)) {
- struct eventsystem *es = eventsystem_find(name);
- if (unlikely(es == NULL)) {
- es = eventsystem_new(name);
- }
-
- if (job_assumes(j, es != NULL)) {
- struct externalevent *ei = NULL;
- size_t i = 0;
- LIST_FOREACH(ei, &es->events, sys_le) {
- (void)job_assumes(j, launch_data_array_set_index(arr, ei->event, i));
- if (job_assumes(j, i < 1024)) {
- tokens[i] = ei->id;
- } else {
- break;
- }
- i++;
- }
-
- /* Big enough. */
- *outvalCnt = 10 * 1024;
- mig_allocate(outval, *outvalCnt);
-
- size_t sz = launch_data_pack(arr, (void *)*outval, *outvalCnt, NULL, NULL);
- if (job_assumes(j, sz != 0)) {
- result = BOOTSTRAP_SUCCESS;
- } else {
- mig_deallocate(*outval, *outvalCnt);
- }
- }
-
- /* Total hack, but launch_data doesn't do ref-counting. */
- struct _launch_data *hack = (struct _launch_data *)arr;
- free(hack->_array);
- free(arr);
- }
-
- return result;
-}
-
-kern_return_t
-job_mig_event_set_state(job_t j, name_t name, uint64_t token, boolean_t state)
-{
- if (!j || !j->event_monitor) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
-
- struct externalevent *ei = externalevent_find(name, token);
- if (job_assumes(j, ei != NULL)) {
- ei->state = state;
- if(job_dispatch(ei->job, false) == NULL) {
- if (errno == EPERM) {
- return BOOTSTRAP_NOT_PRIVILEGED;
- }
- return BOOTSTRAP_NO_MEMORY;
- }
- } else {
- return BOOTSTRAP_NO_MEMORY;
- }
-
- return BOOTSTRAP_SUCCESS;
-}
-
-void
-jobmgr_init(bool sflag)
-{
- const char *root_session_type = pid1_magic ? VPROCMGR_SESSION_SYSTEM : VPROCMGR_SESSION_BACKGROUND;
- SLIST_INIT(&s_curious_jobs);
- LIST_INIT(&s_needing_sessions);
-
- launchd_assert((root_jobmgr = jobmgr_new(NULL, MACH_PORT_NULL, MACH_PORT_NULL, sflag, root_session_type, false, MACH_PORT_NULL)) != NULL);
-#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__
- launchd_assert((_s_xpc_system_domain = jobmgr_new_xpc_singleton_domain(root_jobmgr, "com.apple.xpc.system")) != NULL);
- _s_xpc_system_domain->req_asid = g_audit_session;
- _s_xpc_system_domain->req_asport = g_audit_session_port;
- _s_xpc_system_domain->shortdesc = "system";
-#endif /* __LAUNCH_DISABLE_XPC_SUPPORT__ */
- if (pid1_magic) {
- root_jobmgr->monitor_shutdown = true;
- }
-
- uint32_t fflags = NOTE_ATTRIB | NOTE_LINK | NOTE_REVOKE | NOTE_EXTEND | NOTE_WRITE;
- s_no_hang_fd = open("/dev/autofs_nowait", O_EVTONLY | O_NONBLOCK);
- if (likely(s_no_hang_fd == -1)) {
- if (jobmgr_assumes(root_jobmgr, (s_no_hang_fd = open("/dev", O_EVTONLY | O_NONBLOCK)) != -1)) {
- (void)jobmgr_assumes(root_jobmgr, kevent_mod((uintptr_t)s_no_hang_fd, EVFILT_VNODE, EV_ADD, fflags, 0, root_jobmgr) != -1);
- }
- }
- s_no_hang_fd = _fd(s_no_hang_fd);
-}
-
-size_t
-our_strhash(const char *s)
-{
- size_t c, r = 5381;
-
- /* djb2
- * This algorithm was first reported by Dan Bernstein many years ago in comp.lang.c
- */
-
- while ((c = *s++)) {
- r = ((r << 5) + r) + c; /* hash*33 + c */
- }
-
- return r;
-}
-
-size_t
-hash_label(const char *label)
-{
- return our_strhash(label) % LABEL_HASH_SIZE;
-}
-
-size_t
-hash_ms(const char *msstr)
-{
- return our_strhash(msstr) % MACHSERVICE_HASH_SIZE;
-}
-
-bool
-waiting4removal_new(job_t j, mach_port_t rp)
-{
- struct waiting_for_removal *w4r;
-
- if (!job_assumes(j, (w4r = malloc(sizeof(struct waiting_for_removal))) != NULL)) {
- return false;
- }
-
- w4r->reply_port = rp;
-
- SLIST_INSERT_HEAD(&j->removal_watchers, w4r, sle);
-
- return true;
-}
-
-void
-waiting4removal_delete(job_t j, struct waiting_for_removal *w4r)
-{
- (void)job_assumes(j, job_mig_send_signal_reply(w4r->reply_port, 0) == 0);
-
- SLIST_REMOVE(&j->removal_watchers, w4r, waiting_for_removal, sle);
-
- free(w4r);
-}
-
-size_t
-get_kern_max_proc(void)
-{
- int mib[] = { CTL_KERN, KERN_MAXPROC };
- int max = 100;
- size_t max_sz = sizeof(max);
-
- (void)launchd_assumes(sysctl(mib, 2, &max, &max_sz, NULL, 0) != -1);
-
- return max;
-}
-
-/* See rdar://problem/6271234 */
-void
-eliminate_double_reboot(void)
-{
- if (unlikely(!pid1_magic)) {
- return;
- }
-
- struct stat sb;
- const char *argv[] = { _PATH_BSHELL, "/etc/rc.deferred_install", NULL };
- char *try_again = "Will try again at next boot.";
- int result = ~0;
-
- if (unlikely(stat(argv[1], &sb) != -1)) {
- jobmgr_log(root_jobmgr, LOG_DEBUG | LOG_CONSOLE, "Going to run deferred install script.");
-
- int wstatus;
- pid_t p;
-
- (void)jobmgr_assumes(root_jobmgr, (errno = posix_spawnp(&p, argv[0], NULL, NULL, (char **)argv, environ)) == 0);
-
- if (errno) {
- jobmgr_log(root_jobmgr, LOG_WARNING | LOG_CONSOLE, "Couldn't run deferred install script! %s", try_again);
- goto out;
- }
-
- if (!jobmgr_assumes(root_jobmgr, waitpid(p, &wstatus, 0) != -1)) {
- jobmgr_log(root_jobmgr, LOG_WARNING | LOG_CONSOLE, "Couldn't confirm that deferred install script exited successfully! %s", try_again);
- goto out;
- }
-
- if (jobmgr_assumes(root_jobmgr, WIFEXITED(wstatus) != 0)) {
- if (jobmgr_assumes(root_jobmgr, (result = WEXITSTATUS(wstatus)) == EXIT_SUCCESS)) {
- jobmgr_log(root_jobmgr, LOG_DEBUG | LOG_CONSOLE, "Deferred install script completed successfully.");
- } else {
- jobmgr_log(root_jobmgr, LOG_WARNING | LOG_CONSOLE, "Deferred install script exited with status %d. %s", WEXITSTATUS(wstatus), try_again);
- }
- } else {
- jobmgr_log(root_jobmgr, LOG_WARNING | LOG_CONSOLE, "Confirmed that deferred install script exited, but couldn't confirm that it was successful. %s", try_again);
- }
- }
-out:
- if (result == 0) {
- /* If the unlink(2) was to fail, it would be most likely fail with EBUSY. All the other
- * failure cases for unlink(2) don't apply when we're running under PID 1 and have verified
- * that the file exists. Outside of someone deliberately messing with us (like if /etc/rc.deferredinstall
- * is actually a looping sym-link or a mount point for a filesystem) and I/O errors, we should be good.
- */
- if (!jobmgr_assumes(root_jobmgr, unlink(argv[1]) != -1)) {
- jobmgr_log(root_jobmgr, LOG_WARNING | LOG_CONSOLE, "Deferred install script couldn't be removed!");
- }
- }
-}
-
-void
-jetsam_property_setup(launch_data_t obj, const char *key, job_t j)
-{
- job_log(j, LOG_DEBUG, "Setting Jetsam properties for job...");
- if (strcasecmp(key, LAUNCH_JOBKEY_JETSAMPRIORITY) == 0 && launch_data_get_type(obj) == LAUNCH_DATA_INTEGER) {
- j->jetsam_priority = (typeof(j->jetsam_priority))launch_data_get_integer(obj);
- job_log(j, LOG_DEBUG, "Priority: %d", j->jetsam_priority);
- } else if (strcasecmp(key, LAUNCH_JOBKEY_JETSAMMEMORYLIMIT) == 0 && launch_data_get_type(obj) == LAUNCH_DATA_INTEGER) {
- j->jetsam_memlimit = (typeof(j->jetsam_memlimit))launch_data_get_integer(obj);
- job_log(j, LOG_DEBUG, "Memory limit: %d", j->jetsam_memlimit);
- } else if (strcasecmp(key, LAUNCH_KEY_JETSAMFRONTMOST) == 0) {
- /* Ignore. We only recognize this key so we don't complain when we get SpringBoard's request.
- * You can't set this in a plist.
- */
- } else if (strcasecmp(key, LAUNCH_KEY_JETSAMLABEL) == 0) {
- /* Ignore. This key is present in SpringBoard's request dictionary, so we don't want to
- * complain about it.
- */
- } else {
- job_log(j, LOG_ERR, "Unknown Jetsam key: %s", key);
- }
-
- if (unlikely(!j->jetsam_properties)) {
- j->jetsam_properties = true;
- LIST_INSERT_HEAD(&j->mgr->jetsam_jobs, j, jetsam_sle);
- j->mgr->jetsam_jobs_cnt++;
- }
-
- j->jetsam_seq = s_jetsam_sequence_id++;
-}
-
-int
-launchd_set_jetsam_priorities(launch_data_t priorities)
-{
- if (!launchd_assumes(launch_data_get_type(priorities) == LAUNCH_DATA_ARRAY)) {
- return EINVAL;
- }
-
- jobmgr_t jm = NULL;
-#if !TARGET_OS_EMBEDDED
- /* For testing. */
- jm = jobmgr_find_by_name(root_jobmgr, VPROCMGR_SESSION_AQUA);
- if (!launchd_assumes(jm != NULL)) {
- return EINVAL;
- }
-#else
- /* Since this is for embedded, we can assume that the root job manager holds the Jetsam jobs. */
- jm = root_jobmgr;
-
- if (!g_embedded_privileged_action) {
- return EPERM;
- }
-#endif
-
- size_t npris = launch_data_array_get_count(priorities);
-
- job_t ji = NULL;
- size_t i = 0;
- for (i = 0; i < npris; i++) {
- launch_data_t ldi = launch_data_array_get_index(priorities, i);
- if (!launchd_assumes(launch_data_get_type(ldi) == LAUNCH_DATA_DICTIONARY)) {
- continue;
- }
-
- launch_data_t label = NULL;
- if (!launchd_assumes(label = launch_data_dict_lookup(ldi, LAUNCH_KEY_JETSAMLABEL))) {
- continue;
- }
- const char *_label = launch_data_get_string(label);
-
- ji = job_find(NULL, _label);
- if (!launchd_assumes(ji != NULL)) {
- continue;
- }
-
- launch_data_dict_iterate(ldi, (void (*)(launch_data_t, const char *, void *))jetsam_property_setup, ji);
-
- launch_data_t frontmost = NULL;
- if ((frontmost = launch_data_dict_lookup(ldi, LAUNCH_KEY_JETSAMFRONTMOST)) && launch_data_get_type(frontmost) == LAUNCH_DATA_BOOL) {
- ji->jetsam_frontmost = launch_data_get_bool(frontmost);
- }
- }
-
- i = 0;
- job_t *jobs = (job_t *)calloc(jm->jetsam_jobs_cnt, sizeof(job_t));
- if (launchd_assumes(jobs != NULL)) {
- LIST_FOREACH(ji, &jm->jetsam_jobs, jetsam_sle) {
- if (ji->p) {
- jobs[i] = ji;
- i++;
- }
- }
- }
-
- size_t totalpris = i;
-
- int result = EINVAL;
-
- /* It is conceivable that there could be no Jetsam jobs running. */
- if (totalpris > 0) {
- /* Yay blocks! */
- qsort_b((void *)jobs, totalpris, sizeof(job_t), ^ int (const void *lhs, const void *rhs) {
- job_t _lhs = *(job_t *)lhs;
- job_t _rhs = *(job_t *)rhs;
- /* Sort in descending order. (Priority correlates to the soonishness with which you will be killed.) */
- if (_lhs->jetsam_priority > _rhs->jetsam_priority) {
- return -1;
- } else if (_lhs->jetsam_priority < _rhs->jetsam_priority) {
- return 1;
- }
- /* Priority is equal, so sort by sequence ID to maintain LRU order */
- if( (int)(_lhs->jetsam_seq - _rhs->jetsam_seq) > 0 ) {
- return 1;
- } else if( (int)(_lhs->jetsam_seq - _rhs->jetsam_seq) < 0 ) {
- return -1;
- }
-
- return 0;
- });
-
- jetsam_priority_entry_t *jpris = (jetsam_priority_entry_t *)calloc(totalpris, sizeof(jetsam_priority_entry_t));
- if (!launchd_assumes(jpris != NULL)) {
- result = ENOMEM;
- } else {
- for (i = 0; i < totalpris; i++) {
- jpris[i].pid = jobs[i]->p; /* Subject to time-of-use vs. time-of-check, obviously. */
- jpris[i].flags |= jobs[i]->jetsam_frontmost ? kJetsamFlagsFrontmost : 0;
- jpris[i].hiwat_pages = jobs[i]->jetsam_memlimit;
- }
-
- (void)launchd_assumes((result = sysctlbyname("kern.memorystatus_priority_list", NULL, NULL, &jpris[0], totalpris * sizeof(jetsam_priority_entry_t))) != -1);
- result = result != 0 ? errno : 0;
-
- free(jpris);
- }
- }
-
- if (jobs) {
- free(jobs);
- }
-
- return result;
-}
+++ /dev/null
-#ifndef __LAUNCHD_CORE_LOGIC__
-#define __LAUNCHD_CORE_LOGIC__
-/*
- * Copyright (c) 2005 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 "launchd_runtime.h"
-#include "bootstrap.h"
-#include "launch.h"
-
-typedef struct job_s *job_t;
-typedef struct jobmgr_s *jobmgr_t;
-
-extern jobmgr_t root_jobmgr;
-extern mach_port_t inherited_bootstrap_port;
-extern mach_port_t g_audit_session_port;
-extern au_asid_t g_audit_session;
-extern bool g_flat_mach_namespace;
-extern bool g_embedded_privileged_action;
-
-void jobmgr_init(bool);
-jobmgr_t jobmgr_shutdown(jobmgr_t jm);
-void jobmgr_dispatch_all_semaphores(jobmgr_t jm);
-void jobmgr_dispatch_all_interested(jobmgr_t jm, job_t j);
-jobmgr_t jobmgr_delete_anything_with_port(jobmgr_t jm, mach_port_t port);
-
-launch_data_t job_export_all(void);
-
-job_t job_dispatch(job_t j, bool kickstart); /* returns j on success, NULL on job removal */
-job_t job_find(jobmgr_t jm, const char *label);
-job_t job_find_by_service_port(mach_port_t p);
-bool job_ack_port_destruction(mach_port_t p);
-bool job_is_anonymous(job_t j);
-launch_data_t job_export(job_t j);
-void job_stop(job_t j);
-void job_checkin(job_t j);
-void job_remove(job_t j);
-bool job_is_god(job_t j);
-job_t job_import(launch_data_t pload);
-launch_data_t job_import_bulk(launch_data_t pload);
-job_t job_mig_intran(mach_port_t mp);
-void job_mig_destructor(job_t j);
-void job_ack_no_senders(job_t j);
-void job_log(job_t j, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4)));
-void job_set_pid_crashed(pid_t p);
-
-int launchd_set_jetsam_priorities(launch_data_t priorities);
-
-#endif
+++ /dev/null
-#include <mach/std_types.defs>
-#include <mach/mach_types.defs>
-#include "launchd_mig_types.defs"
-import "vproc.h";
-import "vproc_priv.h";
-import "vproc_internal.h";
-
-subsystem launchd_helper 4241011;
-
-userprefix helper_downcall_;
-serverprefix helper_recv_;
-
-simpleroutine
-ping(
- p : mach_port_move_send_once_t;
-ServerAuditToken token : audit_token_t
-);
-
-/* For coreservicesd to harvest exit status, not actually for UserEventAgent. */
-simpleroutine
-wait(
- p : mach_port_move_send_once_t;
- status : int
-);
+++ /dev/null
-/*
- * Copyright (c) 2006 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@
- */
-
-subsystem launchd_internal 137000;
-
-#include <mach/std_types.defs>
-#include <mach/mach_types.defs>
-
-serverprefix x_;
-
-routine
-handle_kqueue(
- p : mach_port_t;
- fd : integer_t
-);
+++ /dev/null
-#include "launchd_ktrace.h"
-
-void
-runtime_ktrace1(runtime_ktrace_code_t code)
-{
- void *ra = __builtin_extract_return_addr(__builtin_return_address(1));
-
- /* This syscall returns EINVAL when the trace isn't enabled. */
- if (do_apple_internal_logging) {
- syscall(180, code, 0, 0, 0, (long)ra);
- }
-}
-
-void
-runtime_ktrace0(runtime_ktrace_code_t code)
-{
- void *ra = __builtin_extract_return_addr(__builtin_return_address(0));
-
- /* This syscall returns EINVAL when the trace isn't enabled. */
- if (do_apple_internal_logging) {
- syscall(180, code, 0, 0, 0, (long)ra);
- }
-}
-
-void
-runtime_ktrace(runtime_ktrace_code_t code, long a, long b, long c)
-{
- void *ra = __builtin_extract_return_addr(__builtin_return_address(0));
-
- /* This syscall returns EINVAL when the trace isn't enabled. */
- if (do_apple_internal_logging) {
- syscall(180, code, a, b, c, (long)ra);
- }
-}
+++ /dev/null
-#ifndef __LAUNCHD_KTRACE_H__
-#define __LAUNCHD_KTRACE_H__
-
-#include <unistd.h>
-#include <stdbool.h>
-
-extern bool do_apple_internal_logging;
-
-#ifndef DBG_LAUNCHD
- #define DBG_LAUNCHD 34
-#endif
-
-/* Class(8) | SubClass(8) | Code(14) | Qual(2) */
-#define RTKT_CODE(c) ((DBG_LAUNCHD << 24) | (((c) & 0x3fffff) << 2))
-
-typedef enum {
- RTKT_LAUNCHD_STARTING = RTKT_CODE(1),
- RTKT_LAUNCHD_EXITING = RTKT_CODE(2),
- RTKT_LAUNCHD_FINDING_STRAY_PG = RTKT_CODE(3),
- RTKT_LAUNCHD_FINDING_ALL_STRAYS = RTKT_CODE(4),
- RTKT_LAUNCHD_FINDING_EXECLESS = RTKT_CODE(5),
- RTKT_LAUNCHD_FINDING_WEIRD_UIDS = RTKT_CODE(6),
- RTKT_LAUNCHD_DATA_PACK = RTKT_CODE(7),
- RTKT_LAUNCHD_DATA_UNPACK = RTKT_CODE(8),
- RTKT_LAUNCHD_BUG = RTKT_CODE(9),
- RTKT_LAUNCHD_MACH_IPC = RTKT_CODE(10),
- RTKT_LAUNCHD_BSD_KEVENT = RTKT_CODE(11),
- RTKT_VPROC_TRANSACTION_INCREMENT = RTKT_CODE(12),
- RTKT_VPROC_TRANSACTION_DECREMENT = RTKT_CODE(13),
-} runtime_ktrace_code_t;
-
-/* All of these log the return address as "arg4" */
-void runtime_ktrace1(runtime_ktrace_code_t code);
-void runtime_ktrace0(runtime_ktrace_code_t code);
-void runtime_ktrace(runtime_ktrace_code_t code, long a, long b, long c);
-
-#endif /* __LAUNCHD_KTRACE_H__ */
+++ /dev/null
-/*
- * Copyright (c) 1999-2006 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@
- */
-/*
- * bootstrap -- fundamental service initiator and port server
- * Mike DeMoney, NeXT, Inc.
- * Copyright, 1990. All rights reserved.
- */
-
-type pid_t = integer_t;
-type pid_array_t = ^array [] of pid_t;
-type uid_t = natural_t;
-type gid_t = natural_t;
-type vproc_gsk_t = integer_t;
-type logmsg_t = c_string[*:2048];
-type cmd_t = c_string[512];
-type name_t = c_string[128];
-type name_array_t = ^array [] of name_t;
-type bootstrap_property_t = natural_t;
-type bootstrap_property_array_t = ^array [] of bootstrap_property_t;
-type bootstrap_status_t = integer_t;
-type bootstrap_status_array_t = ^array [] of bootstrap_status_t;
-type uuid_t = array [16] of MACH_MSG_TYPE_BYTE;
-type event_token_array_t = array [1024] of uint64_t;
-
-type job_t = mach_port_t
- intran : job_t job_mig_intran(mach_port_t)
- outtran : mach_port_t job_mig_outtran(job_t)
- destructor : job_mig_destructor(job_t)
- cusertype : vproc_mig_t;
+++ /dev/null
-/*
- * Copyright (c) 1999-2008 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@
- */
-
-static const char *const __rcs_file_version__ = "$Revision: 25122 $";
-
-#include "config.h"
-#include "launchd_runtime.h"
-
-#include <mach/mach.h>
-#include <mach/mach_error.h>
-#include <mach/boolean.h>
-#include <mach/message.h>
-#include <mach/notify.h>
-#include <mach/mig_errors.h>
-#include <mach/mach_traps.h>
-#include <mach/mach_interface.h>
-#include <mach/host_info.h>
-#include <mach/mach_host.h>
-#include <mach/mach_time.h>
-#include <mach/exception.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/sysctl.h>
-#include <sys/time.h>
-#include <sys/proc.h>
-#include <sys/event.h>
-#include <sys/queue.h>
-#include <sys/socket.h>
-#include <sys/mount.h>
-#include <sys/reboot.h>
-#include <sys/fcntl.h>
-#include <sys/kdebug.h>
-#include <bsm/libbsm.h>
-#include <malloc/malloc.h>
-#include <unistd.h>
-#include <pthread.h>
-#include <errno.h>
-#include <string.h>
-#include <ctype.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <syslog.h>
-#include <signal.h>
-#include <dlfcn.h>
-
-#include "launchd_internalServer.h"
-#include "launchd_internal.h"
-#include "notifyServer.h"
-#include "mach_excServer.h"
-
-/* We shouldn't be including these */
-#include "launch.h"
-#include "launchd.h"
-#include "launchd_core_logic.h"
-#include "vproc.h"
-#include "vproc_priv.h"
-#include "vproc_internal.h"
-#include "protocol_vprocServer.h"
-#include "protocol_job_reply.h"
-
-#if !TARGET_OS_EMBEDDED
-#include "domainServer.h"
-#endif /* !TARGET_OS_EMBEDDED */
-#include "eventsServer.h"
-
-static mach_port_t ipc_port_set;
-static mach_port_t demand_port_set;
-static mach_port_t launchd_internal_port;
-static int mainkq;
-
-#define BULK_KEV_MAX 100
-static struct kevent *bulk_kev;
-static int bulk_kev_i;
-static int bulk_kev_cnt;
-
-static pthread_t kqueue_demand_thread;
-
-static void mportset_callback(void);
-static kq_callback kqmportset_callback = (kq_callback)mportset_callback;
-static void *kqueue_demand_loop(void *arg);
-
-boolean_t launchd_internal_demux(mach_msg_header_t *Request, mach_msg_header_t *Reply);
-static void record_caller_creds(mach_msg_header_t *mh);
-static void launchd_runtime2(mach_msg_size_t msg_size, mig_reply_error_t *bufRequest, mig_reply_error_t *bufReply);
-static mach_msg_size_t max_msg_size;
-static mig_callback *mig_cb_table;
-static size_t mig_cb_table_sz;
-static timeout_callback runtime_idle_callback;
-static mach_msg_timeout_t runtime_idle_timeout;
-static struct ldcred ldc;
-static size_t runtime_standby_cnt;
-
-static STAILQ_HEAD(, logmsg_s) logmsg_queue = STAILQ_HEAD_INITIALIZER(logmsg_queue);
-static size_t logmsg_queue_sz;
-static size_t logmsg_queue_cnt;
-static mach_port_t drain_reply_port;
-static void runtime_log_uncork_pending_drain(void);
-static kern_return_t runtime_log_pack(vm_offset_t *outval, mach_msg_type_number_t *outvalCnt);
-
-static bool logmsg_add(struct runtime_syslog_attr *attr, int err_num, const char *msg);
-static void logmsg_remove(struct logmsg_s *lm);
-
-static void do_file_init(void) __attribute__((constructor));
-static mach_timebase_info_data_t tbi;
-static uint64_t tbi_safe_math_max;
-static uint64_t time_of_mach_msg_return;
-static double tbi_float_val;
-
-static const int sigigns[] = { SIGHUP, SIGINT, SIGPIPE, SIGALRM, SIGTERM,
- SIGURG, SIGTSTP, SIGTSTP, SIGCONT, SIGTTIN, SIGTTOU, SIGIO, SIGXCPU,
- SIGXFSZ, SIGVTALRM, SIGPROF, SIGWINCH, SIGINFO, SIGUSR1, SIGUSR2
-};
-static sigset_t sigign_set;
-static FILE *ourlogfile;
-bool pid1_magic;
-bool do_apple_internal_logging;
-bool low_level_debug;
-bool g_flat_mach_namespace = true;
-bool g_simulate_pid1_crash = false;
-bool g_malloc_log_stacks = false;
-bool g_use_gmalloc = false;
-bool g_log_per_user_shutdown = false;
-#if !TARGET_OS_EMBEDDED
-bool g_log_pid1_shutdown = true;
-#else
-bool g_log_pid1_shutdown = false;
-#endif
-bool g_log_strict_usage = false;
-bool g_trap_sigkill_bugs = false;
-pid_t g_wsp = 0;
-size_t runtime_busy_cnt;
-
-mach_port_t
-runtime_get_kernel_port(void)
-{
- return launchd_internal_port;
-}
-
-// static const char *__crashreporter_info__ = "";
-
-static int internal_mask_pri = LOG_UPTO(LOG_NOTICE);
-
-
-void
-launchd_runtime_init(void)
-{
- mach_msg_size_t mxmsgsz;
- pid_t p = getpid();
-
- launchd_assert((mainkq = kqueue()) != -1);
-
- launchd_assert((errno = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_PORT_SET, &demand_port_set)) == KERN_SUCCESS);
- launchd_assert((errno = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_PORT_SET, &ipc_port_set)) == KERN_SUCCESS);
-
- launchd_assert(kevent_mod(demand_port_set, EVFILT_MACHPORT, EV_ADD, 0, 0, &kqmportset_callback) != -1);
-
- launchd_assert(launchd_mport_create_recv(&launchd_internal_port) == KERN_SUCCESS);
- launchd_assert(launchd_mport_make_send(launchd_internal_port) == KERN_SUCCESS);
-
- /* Sigh... at the moment, MIG has maxsize == sizeof(reply union) */
- mxmsgsz = sizeof(union __RequestUnion__x_launchd_internal_subsystem);
- if (x_launchd_internal_subsystem.maxsize > mxmsgsz) {
- mxmsgsz = x_launchd_internal_subsystem.maxsize;
- }
-
- launchd_assert(runtime_add_mport(launchd_internal_port, launchd_internal_demux, mxmsgsz) == KERN_SUCCESS);
- launchd_assert(pthread_create(&kqueue_demand_thread, NULL, kqueue_demand_loop, NULL) == 0);
- launchd_assert(pthread_detach(kqueue_demand_thread) == 0);
-
- (void)launchd_assumes(sysctlbyname("vfs.generic.noremotehang", NULL, NULL, &p, sizeof(p)) != -1);
-}
-
-void
-launchd_runtime_init2(void)
-{
- size_t i;
-
- for (i = 0; i < (sizeof(sigigns) / sizeof(int)); i++) {
- sigaddset(&sigign_set, sigigns[i]);
- (void)launchd_assumes(signal(sigigns[i], SIG_IGN) != SIG_ERR);
- }
-}
-
-#define FLAGIF(f) if (flags & f) { flags_off += sprintf(flags_off, #f); flags &= ~f; }
-const char *
-reboot_flags_to_C_names(unsigned int flags)
-{
-#define MAX_RB_STR "RB_ASKNAME|RB_SINGLE|RB_NOSYNC|RB_HALT|RB_INITNAME|RB_DFLTROOT|RB_ALTBOOT|RB_UNIPROC|RB_SAFEBOOT|RB_UPSDELAY|0xdeadbeeffeedface"
- static char flags_buf[sizeof(MAX_RB_STR)];
- char *flags_off = NULL;
-
- if (flags == 0) {
- return "RB_AUTOBOOT";
- }
-
- while (flags) {
- if (flags_off) {
- *flags_off = '|';
- flags_off++;
- *flags_off = '\0';
- } else {
- flags_off = flags_buf;
- }
-
- FLAGIF(RB_ASKNAME)
- else FLAGIF(RB_SINGLE)
- else FLAGIF(RB_NOSYNC)
- else FLAGIF(RB_HALT)
- else FLAGIF(RB_INITNAME)
- else FLAGIF(RB_DFLTROOT)
- else FLAGIF(RB_ALTBOOT)
- else FLAGIF(RB_UNIPROC)
- else FLAGIF(RB_SAFEBOOT)
- else FLAGIF(RB_UPSDELAY)
- else {
- flags_off += sprintf(flags_off, "0x%x", flags);
- flags = 0;
- }
- }
-
- return flags_buf;
-}
-
-const char *
-signal_to_C_name(unsigned int sig)
-{
- static char unknown[25];
-
-#define SIG2CASE(sg) case sg: return #sg
-
- switch (sig) {
- SIG2CASE(SIGHUP);
- SIG2CASE(SIGINT);
- SIG2CASE(SIGQUIT);
- SIG2CASE(SIGILL);
- SIG2CASE(SIGTRAP);
- SIG2CASE(SIGABRT);
- SIG2CASE(SIGFPE);
- SIG2CASE(SIGKILL);
- SIG2CASE(SIGBUS);
- SIG2CASE(SIGSEGV);
- SIG2CASE(SIGSYS);
- SIG2CASE(SIGPIPE);
- SIG2CASE(SIGALRM);
- SIG2CASE(SIGTERM);
- SIG2CASE(SIGURG);
- SIG2CASE(SIGSTOP);
- SIG2CASE(SIGTSTP);
- SIG2CASE(SIGCONT);
- SIG2CASE(SIGCHLD);
- SIG2CASE(SIGTTIN);
- SIG2CASE(SIGTTOU);
- SIG2CASE(SIGIO);
- SIG2CASE(SIGXCPU);
- SIG2CASE(SIGXFSZ);
- SIG2CASE(SIGVTALRM);
- SIG2CASE(SIGPROF);
- SIG2CASE(SIGWINCH);
- SIG2CASE(SIGINFO);
- SIG2CASE(SIGUSR1);
- SIG2CASE(SIGUSR2);
- default:
- snprintf(unknown, sizeof(unknown), "%u", sig);
- return unknown;
- }
-}
-
-void
-log_kevent_struct(int level, struct kevent *kev_base, int indx)
-{
- struct kevent *kev = &kev_base[indx];
- const char *filter_str;
- char ident_buf[100];
- char filter_buf[100];
- char fflags_buf[1000];
- char flags_buf[1000] = "0x0";
- char *flags_off = NULL;
- char *fflags_off = NULL;
- unsigned short flags = kev->flags;
- unsigned int fflags = kev->fflags;
-
- if (likely(!(LOG_MASK(level) & internal_mask_pri))) {
- return;
- }
-
- if (flags) while (flags) {
- if (flags_off) {
- *flags_off = '|';
- flags_off++;
- *flags_off = '\0';
- } else {
- flags_off = flags_buf;
- }
-
- FLAGIF(EV_ADD)
- else FLAGIF(EV_RECEIPT)
- else FLAGIF(EV_DELETE)
- else FLAGIF(EV_ENABLE)
- else FLAGIF(EV_DISABLE)
- else FLAGIF(EV_CLEAR)
- else FLAGIF(EV_EOF)
- else FLAGIF(EV_ONESHOT)
- else FLAGIF(EV_ERROR)
- else {
- flags_off += sprintf(flags_off, "0x%hx", flags);
- flags = 0;
- }
- }
-
- snprintf(ident_buf, sizeof(ident_buf), "%ld", kev->ident);
- snprintf(fflags_buf, sizeof(fflags_buf), "0x%x", fflags);
-
- switch (kev->filter) {
- case EVFILT_READ:
- filter_str = "EVFILT_READ";
- break;
- case EVFILT_WRITE:
- filter_str = "EVFILT_WRITE";
- break;
- case EVFILT_AIO:
- filter_str = "EVFILT_AIO";
- break;
- case EVFILT_VNODE:
- filter_str = "EVFILT_VNODE";
- if (fflags) while (fflags) {
- if (fflags_off) {
- *fflags_off = '|';
- fflags_off++;
- *fflags_off = '\0';
- } else {
- fflags_off = fflags_buf;
- }
-
-#define FFLAGIF(ff) if (fflags & ff) { fflags_off += sprintf(fflags_off, #ff); fflags &= ~ff; }
-
- FFLAGIF(NOTE_DELETE)
- else FFLAGIF(NOTE_WRITE)
- else FFLAGIF(NOTE_EXTEND)
- else FFLAGIF(NOTE_ATTRIB)
- else FFLAGIF(NOTE_LINK)
- else FFLAGIF(NOTE_RENAME)
- else FFLAGIF(NOTE_REVOKE)
- else {
- fflags_off += sprintf(fflags_off, "0x%x", fflags);
- fflags = 0;
- }
- }
- break;
- case EVFILT_PROC:
- filter_str = "EVFILT_PROC";
- if (fflags) while (fflags) {
- if (fflags_off) {
- *fflags_off = '|';
- fflags_off++;
- *fflags_off = '\0';
- } else {
- fflags_off = fflags_buf;
- }
-
- FFLAGIF(NOTE_EXIT)
- else FFLAGIF(NOTE_REAP)
- else FFLAGIF(NOTE_FORK)
- else FFLAGIF(NOTE_EXEC)
- else FFLAGIF(NOTE_SIGNAL)
- else FFLAGIF(NOTE_TRACK)
- else FFLAGIF(NOTE_TRACKERR)
- else FFLAGIF(NOTE_CHILD)
- else {
- fflags_off += sprintf(fflags_off, "0x%x", fflags);
- fflags = 0;
- }
- }
- break;
- case EVFILT_SIGNAL:
- filter_str = "EVFILT_SIGNAL";
- strcpy(ident_buf, signal_to_C_name(kev->ident));
- break;
- case EVFILT_TIMER:
- filter_str = "EVFILT_TIMER";
- snprintf(ident_buf, sizeof(ident_buf), "0x%lx", kev->ident);
- if (fflags) while (fflags) {
- if (fflags_off) {
- *fflags_off = '|';
- fflags_off++;
- *fflags_off = '\0';
- } else {
- fflags_off = fflags_buf;
- }
-
- FFLAGIF(NOTE_SECONDS)
- else FFLAGIF(NOTE_USECONDS)
- else FFLAGIF(NOTE_NSECONDS)
- else FFLAGIF(NOTE_ABSOLUTE)
- else {
- fflags_off += sprintf(fflags_off, "0x%x", fflags);
- fflags = 0;
- }
- }
- break;
- case EVFILT_MACHPORT:
- filter_str = "EVFILT_MACHPORT";
- snprintf(ident_buf, sizeof(ident_buf), "0x%lx", kev->ident);
- break;
- case EVFILT_FS:
- filter_str = "EVFILT_FS";
- snprintf(ident_buf, sizeof(ident_buf), "0x%lx", kev->ident);
- if (fflags) while (fflags) {
- if (fflags_off) {
- *fflags_off = '|';
- fflags_off++;
- *fflags_off = '\0';
- } else {
- fflags_off = fflags_buf;
- }
-
- FFLAGIF(VQ_NOTRESP)
- else FFLAGIF(VQ_NEEDAUTH)
- else FFLAGIF(VQ_LOWDISK)
- else FFLAGIF(VQ_MOUNT)
- else FFLAGIF(VQ_UNMOUNT)
- else FFLAGIF(VQ_DEAD)
- else FFLAGIF(VQ_ASSIST)
- else FFLAGIF(VQ_NOTRESPLOCK)
- else FFLAGIF(VQ_UPDATE)
- else {
- fflags_off += sprintf(fflags_off, "0x%x", fflags);
- fflags = 0;
- }
- }
- break;
- default:
- snprintf(filter_buf, sizeof(filter_buf), "%hd", kev->filter);
- filter_str = filter_buf;
- break;
- }
-
- runtime_syslog(level, "KEVENT[%d]: udata = %p data = 0x%lx ident = %s filter = %s flags = %s fflags = %s",
- indx, kev->udata, kev->data, ident_buf, filter_str, flags_buf, fflags_buf);
-}
-
-void
-mportset_callback(void)
-{
- mach_port_name_array_t members;
- mach_msg_type_number_t membersCnt;
- mach_port_status_t status;
- mach_msg_type_number_t statusCnt;
- struct kevent kev;
- unsigned int i;
-
- if (!launchd_assumes((errno = mach_port_get_set_status(mach_task_self(), demand_port_set, &members, &membersCnt)) == KERN_SUCCESS)) {
- return;
- }
-
- for (i = 0; i < membersCnt; i++) {
- statusCnt = MACH_PORT_RECEIVE_STATUS_COUNT;
- if (mach_port_get_attributes(mach_task_self(), members[i], MACH_PORT_RECEIVE_STATUS, (mach_port_info_t)&status,
- &statusCnt) != KERN_SUCCESS) {
- continue;
- }
- if (status.mps_msgcount) {
- EV_SET(&kev, members[i], EVFILT_MACHPORT, 0, 0, 0, job_find_by_service_port(members[i]));
-#if 0
- if (launchd_assumes(kev.udata != NULL)) {
-#endif
- log_kevent_struct(LOG_DEBUG, &kev, 0);
- (*((kq_callback *)kev.udata))(kev.udata, &kev);
-#if 0
- } else {
- log_kevent_struct(LOG_ERR, &kev, 0);
- }
-#endif
- /* the callback may have tainted our ability to continue this for loop */
- break;
- }
- }
-
- (void)launchd_assumes(vm_deallocate(mach_task_self(), (vm_address_t)members,
- (vm_size_t) membersCnt * sizeof(mach_port_name_t)) == KERN_SUCCESS);
-}
-
-void *
-kqueue_demand_loop(void *arg __attribute__((unused)))
-{
- fd_set rfds;
-
- /*
- * Yes, at first glance, calling select() on a kqueue seems silly.
- *
- * This avoids a race condition between the main thread and this helper
- * thread by ensuring that we drain kqueue events on the same thread
- * that manipulates the kqueue.
- */
-
- for (;;) {
- FD_ZERO(&rfds);
- FD_SET(mainkq, &rfds);
- if (launchd_assumes(select(mainkq + 1, &rfds, NULL, NULL, NULL) == 1)) {
- (void)launchd_assumes(handle_kqueue(launchd_internal_port, mainkq) == 0);
- }
- }
-
- return NULL;
-}
-
-kern_return_t
-x_handle_kqueue(mach_port_t junk __attribute__((unused)), integer_t fd)
-{
- struct timespec ts = { 0, 0 };
- struct kevent *kevi, kev[BULK_KEV_MAX];
- int i;
-
- bulk_kev = kev;
-
- if (launchd_assumes((bulk_kev_cnt = kevent(fd, NULL, 0, kev, BULK_KEV_MAX, &ts)) != -1)) {
-#if 0
- for (i = 0; i < bulk_kev_cnt; i++) {
- log_kevent_struct(LOG_DEBUG, &kev[0], i);
- }
-#endif
- for (i = 0; i < bulk_kev_cnt; i++) {
- bulk_kev_i = i;
- kevi = &kev[i];
-
- if (kevi->filter) {
- runtime_syslog(LOG_DEBUG, "Dispatching kevent...");
- log_kevent_struct(LOG_DEBUG, kev, i);
-#if 0
- /* Check if kevi->udata was either malloc(3)ed or is a valid function pointer.
- * If neither, it's probably an invalid pointer and we should log it.
- */
- Dl_info dli;
- if (launchd_assumes(malloc_size(kevi->udata) || dladdr(kevi->udata, &dli))) {
- runtime_ktrace(RTKT_LAUNCHD_BSD_KEVENT|DBG_FUNC_START, kevi->ident, kevi->filter, kevi->fflags);
- (*((kq_callback *)kevi->udata))(kevi->udata, kevi);
- runtime_ktrace0(RTKT_LAUNCHD_BSD_KEVENT|DBG_FUNC_END);
- } else {
- runtime_syslog(LOG_ERR, "The following kevent had invalid context data.");
- log_kevent_struct(LOG_EMERG, &kev[0], i);
- }
-#else
- struct job_check_s {
- kq_callback kqc;
- };
-
- struct job_check_s *check = kevi->udata;
- if (check && check->kqc) {
- runtime_ktrace(RTKT_LAUNCHD_BSD_KEVENT|DBG_FUNC_START, kevi->ident, kevi->filter, kevi->fflags);
- (*((kq_callback *)kevi->udata))(kevi->udata, kevi);
- runtime_ktrace0(RTKT_LAUNCHD_BSD_KEVENT|DBG_FUNC_END);
- } else {
- runtime_syslog(LOG_ERR, "The following kevent had invalid context data. Please file a bug with the following information:");
- log_kevent_struct(LOG_EMERG, &kev[0], i);
- }
-#endif
- }
- }
- }
-
- bulk_kev = NULL;
-
- return 0;
-}
-
-void
-launchd_runtime(void)
-{
- mig_reply_error_t *req = NULL, *resp = NULL;
- mach_msg_size_t mz = max_msg_size;
- int flags = VM_MAKE_TAG(VM_MEMORY_MACH_MSG)|TRUE;
-
- for (;;) {
- if (likely(req)) {
- (void)launchd_assumes(vm_deallocate(mach_task_self(), (vm_address_t)req, mz) == KERN_SUCCESS);
- req = NULL;
- }
- if (likely(resp)) {
- (void)launchd_assumes(vm_deallocate(mach_task_self(), (vm_address_t)resp, mz) == KERN_SUCCESS);
- resp = NULL;
- }
-
- mz = max_msg_size;
-
- if (!launchd_assumes(vm_allocate(mach_task_self(), (vm_address_t *)&req, mz, flags) == KERN_SUCCESS)) {
- continue;
- }
- if (!launchd_assumes(vm_allocate(mach_task_self(), (vm_address_t *)&resp, mz, flags) == KERN_SUCCESS)) {
- continue;
- }
-
- launchd_runtime2(mz, req, resp);
-
- /* If we get here, max_msg_size probably changed... */
- }
-}
-
-kern_return_t
-launchd_set_bport(mach_port_t name)
-{
- return errno = task_set_bootstrap_port(mach_task_self(), name);
-}
-
-kern_return_t
-launchd_get_bport(mach_port_t *name)
-{
- return errno = task_get_bootstrap_port(mach_task_self(), name);
-}
-
-kern_return_t
-launchd_mport_notify_req(mach_port_t name, mach_msg_id_t which)
-{
- mach_port_mscount_t msgc = (which == MACH_NOTIFY_PORT_DESTROYED) ? 0 : 1;
- mach_port_t previous, where = (which == MACH_NOTIFY_NO_SENDERS) ? name : launchd_internal_port;
-
- if (which == MACH_NOTIFY_NO_SENDERS) {
- /* Always make sure the send count is zero, in case a receive right is reused */
- errno = mach_port_set_mscount(mach_task_self(), name, 0);
- if (unlikely(errno != KERN_SUCCESS)) {
- return errno;
- }
- }
-
- errno = mach_port_request_notification(mach_task_self(), name, which, msgc, where,
- MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous);
-
- if (likely(errno == 0) && previous != MACH_PORT_NULL) {
- (void)launchd_assumes(launchd_mport_deallocate(previous) == KERN_SUCCESS);
- }
-
- return errno;
-}
-
-pid_t
-runtime_fork(mach_port_t bsport)
-{
- sigset_t emptyset, oset;
- pid_t r = -1;
- int saved_errno;
- size_t i;
-
- sigemptyset(&emptyset);
-
- (void)launchd_assumes(launchd_mport_make_send(bsport) == KERN_SUCCESS);
- (void)launchd_assumes(launchd_set_bport(bsport) == KERN_SUCCESS);
- (void)launchd_assumes(launchd_mport_deallocate(bsport) == KERN_SUCCESS);
-
- (void)launchd_assumes(sigprocmask(SIG_BLOCK, &sigign_set, &oset) != -1);
- for (i = 0; i < (sizeof(sigigns) / sizeof(int)); i++) {
- (void)launchd_assumes(signal(sigigns[i], SIG_DFL) != SIG_ERR);
- }
-
- r = fork();
- saved_errno = errno;
-
- if (r != 0) {
- for (i = 0; i < (sizeof(sigigns) / sizeof(int)); i++) {
- (void)launchd_assumes(signal(sigigns[i], SIG_IGN) != SIG_ERR);
- }
- (void)launchd_assumes(sigprocmask(SIG_SETMASK, &oset, NULL) != -1);
- (void)launchd_assumes(launchd_set_bport(MACH_PORT_NULL) == KERN_SUCCESS);
- } else {
- pid_t p = -getpid();
- (void)launchd_assumes(sysctlbyname("vfs.generic.noremotehang", NULL, NULL, &p, sizeof(p)) != -1);
-
- (void)launchd_assumes(sigprocmask(SIG_SETMASK, &emptyset, NULL) != -1);
- }
-
- errno = saved_errno;
-
- return r;
-}
-
-
-void
-runtime_set_timeout(timeout_callback to_cb, unsigned int sec)
-{
- if (sec == 0 || to_cb == NULL) {
- runtime_idle_callback = NULL;
- runtime_idle_timeout = 0;
- }
-
- runtime_idle_callback = to_cb;
- runtime_idle_timeout = sec * 1000;
-}
-
-kern_return_t
-runtime_add_mport(mach_port_t name, mig_callback demux, mach_msg_size_t msg_size)
-{
- size_t needed_table_sz = (MACH_PORT_INDEX(name) + 1) * sizeof(mig_callback);
- mach_port_t target_set = demux ? ipc_port_set : demand_port_set;
-
- msg_size = round_page(msg_size + MAX_TRAILER_SIZE);
-
- if (unlikely(needed_table_sz > mig_cb_table_sz)) {
- needed_table_sz *= 2; /* Let's try and avoid realloc'ing for a while */
- mig_callback *new_table = malloc(needed_table_sz);
-
- if (!launchd_assumes(new_table != NULL)) {
- return KERN_RESOURCE_SHORTAGE;
- }
-
- if (likely(mig_cb_table)) {
- memcpy(new_table, mig_cb_table, mig_cb_table_sz);
- free(mig_cb_table);
- }
-
- mig_cb_table_sz = needed_table_sz;
- mig_cb_table = new_table;
- }
-
- mig_cb_table[MACH_PORT_INDEX(name)] = demux;
-
- if (msg_size > max_msg_size) {
- max_msg_size = msg_size;
- }
-
- return errno = mach_port_move_member(mach_task_self(), name, target_set);
-}
-
-kern_return_t
-runtime_remove_mport(mach_port_t name)
-{
- mig_cb_table[MACH_PORT_INDEX(name)] = NULL;
-
- return errno = mach_port_move_member(mach_task_self(), name, MACH_PORT_NULL);
-}
-
-kern_return_t
-launchd_mport_make_send(mach_port_t name)
-{
- return errno = mach_port_insert_right(mach_task_self(), name, name, MACH_MSG_TYPE_MAKE_SEND);
-}
-
-kern_return_t
-launchd_mport_copy_send(mach_port_t name)
-{
- return errno = mach_port_insert_right(mach_task_self(), name, name, MACH_MSG_TYPE_COPY_SEND);
-}
-
-kern_return_t
-launchd_mport_make_send_once(mach_port_t name, mach_port_t *so)
-{
- mach_msg_type_name_t right = 0;
- return errno = mach_port_extract_right(mach_task_self(), name, MACH_MSG_TYPE_MAKE_SEND_ONCE, so, &right);
-}
-
-kern_return_t
-launchd_mport_close_recv(mach_port_t name)
-{
- return errno = mach_port_mod_refs(mach_task_self(), name, MACH_PORT_RIGHT_RECEIVE, -1);
-}
-
-kern_return_t
-launchd_mport_create_recv(mach_port_t *name)
-{
- return errno = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, name);
-}
-
-kern_return_t
-launchd_mport_deallocate(mach_port_t name)
-{
- return errno = mach_port_deallocate(mach_task_self(), name);
-}
-
-int
-kevent_bulk_mod(struct kevent *kev, size_t kev_cnt)
-{
- size_t i;
-
- for (i = 0; i < kev_cnt; i++) {
- kev[i].flags |= EV_CLEAR|EV_RECEIPT;
- }
-
- return kevent(mainkq, kev, kev_cnt, kev, kev_cnt, NULL);
-}
-
-int
-kevent_mod(uintptr_t ident, short filter, u_short flags, u_int fflags, intptr_t data, void *udata)
-{
- struct kevent kev;
- int r;
-
- switch (filter) {
- case EVFILT_READ:
- case EVFILT_WRITE:
- break;
- case EVFILT_TIMER:
- /* Workaround 5225889 */
- if (flags & EV_ADD) {
- kevent_mod(ident, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
- }
- /* fall through */
- default:
- flags |= EV_CLEAR;
- break;
- }
-
- flags |= EV_RECEIPT;
-
- if (flags & EV_ADD && !launchd_assumes(udata != NULL)) {
- errno = EINVAL;
- return -1;
- } else if ((flags & EV_DELETE) && bulk_kev) {
- int i = 0;
- for (i = bulk_kev_i + 1; i < bulk_kev_cnt; i++) {
- if (bulk_kev[i].filter == filter && bulk_kev[i].ident == ident) {
- runtime_syslog(LOG_DEBUG, "Pruning the following kevent:");
- log_kevent_struct(LOG_DEBUG, &bulk_kev[0], i);
- bulk_kev[i].filter = (short)0;
- }
- }
- }
-
- EV_SET(&kev, ident, filter, flags, fflags, data, udata);
-
- r = kevent(mainkq, &kev, 1, &kev, 1, NULL);
-
- if (!launchd_assumes(r == 1)) {
- return -1;
- }
-
- if (launchd_assumes(kev.flags & EV_ERROR)) {
- if ((flags & EV_ADD) && kev.data) {
- runtime_syslog(LOG_DEBUG, "%s(): See the next line...", __func__);
- log_kevent_struct(LOG_DEBUG, &kev, 0);
- errno = kev.data;
- return -1;
- }
- }
-
- return r;
-}
-
-boolean_t
-launchd_internal_demux(mach_msg_header_t *Request, mach_msg_header_t *Reply)
-{
- if (launchd_internal_server_routine(Request)) {
- return launchd_internal_server(Request, Reply);
- } else if (notify_server_routine(Request)) {
- return notify_server(Request, Reply);
- } else {
- return mach_exc_server(Request, Reply);
- }
-}
-
-kern_return_t
-do_mach_notify_port_destroyed(mach_port_t notify __attribute__((unused)), mach_port_t rights)
-{
- /* This message is sent to us when a receive right is returned to us. */
-
- if (!launchd_assumes(job_ack_port_destruction(rights))) {
- (void)launchd_assumes(launchd_mport_close_recv(rights) == KERN_SUCCESS);
- }
-
- return KERN_SUCCESS;
-}
-
-kern_return_t
-do_mach_notify_port_deleted(mach_port_t notify __attribute__((unused)), mach_port_name_t name __attribute__((unused)))
-{
- /* If we deallocate/destroy/mod_ref away a port with a pending
- * notification, the original notification message is replaced with
- * this message. To quote a Mach kernel expert, "the kernel has a
- * send-once right that has to be used somehow."
- */
- return KERN_SUCCESS;
-}
-
-kern_return_t
-do_mach_notify_no_senders(mach_port_t notify, mach_port_mscount_t mscount __attribute__((unused)))
-{
- job_t j = job_mig_intran(notify);
-
- /* This message is sent to us when the last customer of one of our
- * objects goes away.
- */
-
- if (!launchd_assumes(j != NULL)) {
- return KERN_FAILURE;
- }
-
- job_ack_no_senders(j);
-
- return KERN_SUCCESS;
-}
-
-kern_return_t
-do_mach_notify_send_once(mach_port_t notify __attribute__((unused)))
-{
- /*
- * This message is sent for each send-once right that is deallocated
- * without being used.
- */
-
- return KERN_SUCCESS;
-}
-
-kern_return_t
-do_mach_notify_dead_name(mach_port_t notify __attribute__((unused)), mach_port_name_t name)
-{
- /* This message is sent to us when one of our send rights no longer has
- * a receiver somewhere else on the system.
- */
-
- if (name == drain_reply_port) {
- (void)launchd_assumes(launchd_mport_deallocate(name) == KERN_SUCCESS);
- drain_reply_port = MACH_PORT_NULL;
- }
-
- if (launchd_assumes(root_jobmgr != NULL)) {
- root_jobmgr = jobmgr_delete_anything_with_port(root_jobmgr, name);
- }
-
- /* A dead-name notification about a port appears to increment the
- * rights on said port. Let's deallocate it so that we don't leak
- * dead-name ports.
- */
- (void)launchd_assumes(launchd_mport_deallocate(name) == KERN_SUCCESS);
-
- return KERN_SUCCESS;
-}
-
-void
-record_caller_creds(mach_msg_header_t *mh)
-{
- mach_msg_max_trailer_t *tp;
- size_t trailer_size;
-
- tp = (mach_msg_max_trailer_t *)((vm_offset_t)mh + round_msg(mh->msgh_size));
-
- trailer_size = tp->msgh_trailer_size - (mach_msg_size_t)(sizeof(mach_msg_trailer_type_t) - sizeof(mach_msg_trailer_size_t));
-
- if (launchd_assumes(trailer_size >= (mach_msg_size_t)sizeof(audit_token_t))) {
- audit_token_to_au32(tp->msgh_audit, /* audit UID */ NULL, &ldc.euid,
- &ldc.egid, &ldc.uid, &ldc.gid, &ldc.pid,
- &ldc.asid, /* au_tid_t */ NULL);
- }
-
-}
-
-struct ldcred *
-runtime_get_caller_creds(void)
-{
- return &ldc;
-}
-
-mach_msg_return_t
-launchd_exc_runtime_once(mach_port_t port, mach_msg_size_t rcv_msg_size, mach_msg_size_t send_msg_size, mig_reply_error_t *bufRequest, mig_reply_error_t *bufReply, mach_msg_timeout_t to)
-{
- mach_msg_return_t mr = ~MACH_MSG_SUCCESS;
- mach_msg_option_t rcv_options = MACH_RCV_MSG |
- MACH_RCV_TIMEOUT |
- MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) |
- MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) ;
-
- do {
- mr = mach_msg(&bufRequest->Head, rcv_options, 0, rcv_msg_size, port, to, MACH_PORT_NULL);
- switch (mr) {
- case MACH_RCV_TIMED_OUT :
- runtime_syslog(LOG_DEBUG, "Message queue is empty.");
- break;
- case MACH_RCV_TOO_LARGE :
- runtime_syslog(LOG_INFO, "Message is larger than %u bytes.", rcv_msg_size);
- break;
- default :
- (void)launchd_assumes(mr == MACH_MSG_SUCCESS);
- }
-
- if (mr == MACH_MSG_SUCCESS) {
- if (!launchd_assumes(mach_exc_server(&bufRequest->Head, &bufReply->Head) == TRUE)) {
- runtime_syslog(LOG_WARNING, "Exception server routine failed.");
- break;
- }
-
- mach_msg_return_t smr = ~MACH_MSG_SUCCESS;
- mach_msg_option_t send_options = MACH_SEND_MSG |
- MACH_SEND_TIMEOUT ;
-
- (void)launchd_assumes(bufReply->Head.msgh_size <= send_msg_size);
- smr = mach_msg(&bufReply->Head, send_options, bufReply->Head.msgh_size, 0, MACH_PORT_NULL, to + 100, MACH_PORT_NULL);
- switch (smr) {
- case MACH_SEND_TIMED_OUT :
- runtime_syslog(LOG_WARNING, "Timed out while trying to send reply to exception message.");
- break;
- case MACH_SEND_INVALID_DEST :
- runtime_syslog(LOG_WARNING, "Tried sending a message to a port that we don't possess a send right to.");
- break;
- default :
- if (!launchd_assumes(smr == MACH_MSG_SUCCESS)) {
- runtime_syslog(LOG_WARNING, "Couldn't deliver exception reply: 0x%x", smr);
- }
- break;
- }
- }
- } while (0);
-
- return mr;
-}
-
-void
-launchd_runtime2(mach_msg_size_t msg_size, mig_reply_error_t *bufRequest, mig_reply_error_t *bufReply)
-{
- mach_msg_options_t options, tmp_options;
- mig_reply_error_t *bufTemp;
- mig_callback the_demux;
- mach_msg_timeout_t to;
- mach_msg_return_t mr;
- size_t busy_cnt;
-
- options = MACH_RCV_MSG|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) |
- MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0);
-
- tmp_options = options;
-
- for (;;) {
- busy_cnt = runtime_busy_cnt + runtime_standby_cnt;
- to = MACH_MSG_TIMEOUT_NONE;
-
- if (unlikely(msg_size != max_msg_size)) {
- /* The buffer isn't big enough to receive messages anymore... */
- tmp_options &= ~MACH_RCV_MSG;
- options &= ~MACH_RCV_MSG;
- if (!(tmp_options & MACH_SEND_MSG)) {
- return;
- }
- }
-
- if ((tmp_options & MACH_RCV_MSG) && (runtime_idle_callback || (busy_cnt == 0))) {
- tmp_options |= MACH_RCV_TIMEOUT;
-
- if (!(tmp_options & MACH_SEND_TIMEOUT)) {
-#if !TARGET_OS_EMBEDDED
- to = busy_cnt ? runtime_idle_timeout : (_vproc_standby_timeout() * 1000);
-#else
- to = runtime_idle_timeout;
-#endif
- }
- }
-
- runtime_log_push();
-
- mr = mach_msg(&bufReply->Head, tmp_options, bufReply->Head.msgh_size,
- msg_size, ipc_port_set, to, MACH_PORT_NULL);
-
- time_of_mach_msg_return = runtime_get_opaque_time();
-
- tmp_options = options;
-
- /* It looks like the compiler doesn't optimize switch(unlikely(...)) See: 5691066 */
- if (unlikely(mr)) switch (mr) {
- case MACH_SEND_INVALID_DEST:
- case MACH_SEND_TIMED_OUT:
- /* We need to clean up and start over. */
- if (bufReply->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) {
- mach_msg_destroy(&bufReply->Head);
- }
- continue;
- case MACH_RCV_TIMED_OUT:
- if (to != MACH_MSG_TIMEOUT_NONE) {
- if (busy_cnt == 0) {
- runtime_syslog(LOG_INFO, "Idle exiting.");
- launchd_shutdown();
- } else if (runtime_idle_callback) {
- runtime_idle_callback();
- }
- }
- continue;
- default:
- if (!launchd_assumes(mr == MACH_MSG_SUCCESS)) {
- runtime_syslog(LOG_ERR, "mach_msg(): %u: %s", mr, mach_error_string(mr));
- }
- continue;
- }
-
- bufTemp = bufRequest;
- bufRequest = bufReply;
- bufReply = bufTemp;
-
- if (unlikely(!(tmp_options & MACH_RCV_MSG))) {
- continue;
- }
-
- /* we have another request message */
-#if 0
- if (!launchd_assumes(mig_cb_table != NULL)) {
- break;
- }
-#endif
-
- the_demux = mig_cb_table[MACH_PORT_INDEX(bufRequest->Head.msgh_local_port)];
-
-#if 0
- if (!launchd_assumes(the_demux != NULL)) {
- break;
- }
-#endif
-
- record_caller_creds(&bufRequest->Head);
- runtime_ktrace(RTKT_LAUNCHD_MACH_IPC|DBG_FUNC_START, bufRequest->Head.msgh_local_port, bufRequest->Head.msgh_id, (long)the_demux);
-
- if (the_demux(&bufRequest->Head, &bufReply->Head) == FALSE) {
- /* XXX - also gross */
- if (likely(bufRequest->Head.msgh_id == MACH_NOTIFY_NO_SENDERS)) {
- notify_server(&bufRequest->Head, &bufReply->Head);
- } else if (the_demux == protocol_vproc_server) {
-
-#if !TARGET_OS_EMBEDDED
- /* Similarly gross. */
- if (xpc_domain_server(&bufRequest->Head, &bufReply->Head) == FALSE) {
- (void)xpc_events_server(&bufRequest->Head, &bufReply->Head);
- }
-#else
- (void)xpc_events_server(&bufRequest->Head, &bufReply->Head);
-#endif /* !TARGET_OS_EMBEDDED */
- }
- }
-
- runtime_ktrace(RTKT_LAUNCHD_MACH_IPC|DBG_FUNC_END, bufReply->Head.msgh_remote_port, bufReply->Head.msgh_bits, bufReply->RetCode);
-
- /* bufReply is a union. If MACH_MSGH_BITS_COMPLEX is set, then bufReply->RetCode is assumed to be zero. */
- if (!(bufReply->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX)) {
- if (unlikely(bufReply->RetCode != KERN_SUCCESS)) {
- if (likely(bufReply->RetCode == MIG_NO_REPLY)) {
- bufReply->Head.msgh_remote_port = MACH_PORT_NULL;
- } else if (bufRequest->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) {
- /* destroy the request - but not the reply port */
- bufRequest->Head.msgh_remote_port = MACH_PORT_NULL;
- mach_msg_destroy(&bufRequest->Head);
- }
- }
- }
-
- if (likely(bufReply->Head.msgh_remote_port != MACH_PORT_NULL)) {
- tmp_options |= MACH_SEND_MSG;
-
- if (unlikely(MACH_MSGH_BITS_REMOTE(bufReply->Head.msgh_bits) != MACH_MSG_TYPE_MOVE_SEND_ONCE)) {
- tmp_options |= MACH_SEND_TIMEOUT;
- }
- }
- }
-}
-
-int
-runtime_close(int fd)
-{
- int i;
-
- if (bulk_kev) for (i = bulk_kev_i + 1; i < bulk_kev_cnt; i++) {
- switch (bulk_kev[i].filter) {
- case EVFILT_VNODE:
- case EVFILT_WRITE:
- case EVFILT_READ:
- if (unlikely((int)bulk_kev[i].ident == fd)) {
- runtime_syslog(LOG_DEBUG, "Skipping kevent index: %d", i);
- bulk_kev[i].filter = 0;
- }
- default:
- break;
- }
- }
-
- return close(fd);
-}
-
-void
-runtime_closelog(void)
-{
- runtime_log_push();
-
- if (ourlogfile) {
- (void)launchd_assumes(fflush(ourlogfile) == 0);
- (void)launchd_assumes(runtime_fsync(fileno(ourlogfile)) != -1);
- }
-}
-
-int
-runtime_fsync(int fd)
-{
-#if 0
- if (do_apple_internal_logging) {
- return fcntl(fd, F_FULLFSYNC, NULL);
- } else {
- return fsync(fd);
- }
-#else
- return fsync(fd);
-#endif
-}
-
-int
-runtime_setlogmask(int maskpri)
-{
- internal_mask_pri = maskpri;
-
- return internal_mask_pri;
-}
-
-void
-runtime_syslog(int pri, const char *message, ...)
-{
- struct runtime_syslog_attr attr = {
- g_my_label,
- g_my_label,
- pid1_magic ? "System" : "Background",
- pri,
- getuid(),
- getpid(),
- getpid()
- };
- va_list ap;
-
- va_start(ap, message);
- runtime_vsyslog(&attr, message, ap);
-
- va_end(ap);
-}
-
-void
-runtime_vsyslog(struct runtime_syslog_attr *attr, const char *message, va_list args)
-{
- int saved_errno = errno;
- char newmsg[10000];
- bool echo_to_console= false;
-
- if (attr->priority == LOG_APPLEONLY) {
- if (do_apple_internal_logging) {
- attr->priority = LOG_NOTICE;
- } else {
- return;
- }
- } else if (attr->priority == LOG_SCOLDING) {
- attr->priority = g_log_strict_usage ? LOG_NOTICE : LOG_DEBUG;
- }
-
- if (attr->priority & LOG_CONSOLE) {
- echo_to_console = true;
- attr->priority &= ~LOG_CONSOLE;
- }
-
- if (!(LOG_MASK(attr->priority) & internal_mask_pri)) {
- return;
- }
-
- vsnprintf(newmsg, sizeof(newmsg), message, args);
-
- if (g_console && (unlikely(low_level_debug) || echo_to_console)) {
- fprintf(g_console, "%s %u\t%s %u\t%s\n", attr->from_name, attr->from_pid, attr->about_name, attr->about_pid, newmsg);
- }
-
- logmsg_add(attr, saved_errno, newmsg);
-}
-
-bool
-logmsg_add(struct runtime_syslog_attr *attr, int err_num, const char *msg)
-{
- size_t lm_sz = sizeof(struct logmsg_s) + strlen(msg) + strlen(attr->from_name) + strlen(attr->about_name) + strlen(attr->session_name) + 4;
- char *data_off;
- struct logmsg_s *lm;
-
-#define ROUND_TO_64BIT_WORD_SIZE(x) ((x + 7) & ~7)
-
- /* we do this to make the unpacking for the log_drain cause unalignment faults */
- lm_sz = ROUND_TO_64BIT_WORD_SIZE(lm_sz);
-
- if (unlikely((lm = calloc(1, lm_sz)) == NULL)) {
- return false;
- }
-
- data_off = lm->data;
-
- lm->when = runtime_get_wall_time();
- lm->from_pid = attr->from_pid;
- lm->about_pid = attr->about_pid;
- lm->err_num = err_num;
- lm->pri = attr->priority;
- lm->obj_sz = lm_sz;
- lm->msg = data_off;
- data_off += sprintf(data_off, "%s", msg) + 1;
- lm->from_name = data_off;
- data_off += sprintf(data_off, "%s", attr->from_name) + 1;
- lm->about_name = data_off;
- data_off += sprintf(data_off, "%s", attr->about_name) + 1;
- lm->session_name = data_off;
- data_off += sprintf(data_off, "%s", attr->session_name) + 1;
-
- STAILQ_INSERT_TAIL(&logmsg_queue, lm, sqe);
- logmsg_queue_sz += lm_sz;
- logmsg_queue_cnt++;
-
- return true;
-}
-
-void
-logmsg_remove(struct logmsg_s *lm)
-{
- STAILQ_REMOVE(&logmsg_queue, lm, logmsg_s, sqe);
- logmsg_queue_sz -= lm->obj_sz;
- logmsg_queue_cnt--;
-
- free(lm);
-}
-
-kern_return_t
-runtime_log_pack(vm_offset_t *outval, mach_msg_type_number_t *outvalCnt)
-{
- struct logmsg_s *lm;
- void *offset;
-
- *outvalCnt = logmsg_queue_sz;
-
- mig_allocate(outval, *outvalCnt);
-
- if (unlikely(*outval == 0)) {
- return 1;
- }
-
- offset = (void *)*outval;
-
- if (g_log_per_user_shutdown && !ourlogfile && !pid1_magic && shutdown_in_progress) {
- char logfile[NAME_MAX];
- snprintf(logfile, sizeof(logfile), "/var/tmp/launchd-%s.shutdown.log", g_username);
-
- char logfile1[NAME_MAX];
- snprintf(logfile1, sizeof(logfile1), "/var/tmp/launchd-%s.shutdown.log.1", g_username);
-
- rename(logfile, logfile1);
- ourlogfile = fopen(logfile, "a");
- }
-
- static int64_t shutdown_start = 0;
- if (shutdown_start == 0) {
- shutdown_start = runtime_get_wall_time();
- }
-
- while ((lm = STAILQ_FIRST(&logmsg_queue))) {
- int64_t log_delta = lm->when - shutdown_start;
- if (!pid1_magic && ourlogfile) {
- fprintf(ourlogfile, "%8lld%6u %-40s%6u %-40s %s\n", log_delta,
- lm->from_pid, lm->from_name, lm->about_pid, lm->about_name, lm->msg);
- fflush(ourlogfile);
- }
-
- lm->from_name_offset = lm->from_name - (char *)lm;
- lm->about_name_offset = lm->about_name - (char *)lm;
- lm->msg_offset = lm->msg - (char *)lm;
- lm->session_name_offset = lm->session_name - (char *)lm;
-
- memcpy(offset, lm, lm->obj_sz);
-
- offset += lm->obj_sz;
-
- logmsg_remove(lm);
- }
-
- if (ourlogfile) {
- fflush(ourlogfile);
- }
-
- return 0;
-}
-
-void
-runtime_log_uncork_pending_drain(void)
-{
- mach_msg_type_number_t outvalCnt;
- mach_port_t tmp_port;
- vm_offset_t outval;
-
- if (!drain_reply_port) {
- return;
- }
-
- if (logmsg_queue_cnt == 0) {
- return;
- }
-
- if (runtime_log_pack(&outval, &outvalCnt) != 0) {
- return;
- }
-
- tmp_port = drain_reply_port;
- drain_reply_port = MACH_PORT_NULL;
-
- if (unlikely(errno = job_mig_log_drain_reply(tmp_port, 0, outval, outvalCnt))) {
- (void)launchd_assumes(errno == MACH_SEND_INVALID_DEST);
- (void)launchd_assumes(launchd_mport_deallocate(tmp_port) == KERN_SUCCESS);
- }
-
- mig_deallocate(outval, outvalCnt);
-}
-
-void
-runtime_log_push(void)
-{
- static pthread_mutex_t ourlock = PTHREAD_MUTEX_INITIALIZER;
- static int64_t shutdown_start, log_delta;
- mach_msg_type_number_t outvalCnt;
- struct logmsg_s *lm;
- vm_offset_t outval;
-
- if (logmsg_queue_cnt == 0) {
- (void)launchd_assumes(STAILQ_EMPTY(&logmsg_queue));
- return;
- } else if (!pid1_magic) {
- if (runtime_log_pack(&outval, &outvalCnt) == 0) {
- (void)launchd_assumes(_vprocmgr_log_forward(inherited_bootstrap_port, (void *)outval, outvalCnt) == NULL);
- mig_deallocate(outval, outvalCnt);
- }
- return;
- }
-
- if (likely(!shutdown_in_progress && !fake_shutdown_in_progress)) {
- runtime_log_uncork_pending_drain();
- return;
- }
-
- if (unlikely(shutdown_start == 0)) {
- shutdown_start = runtime_get_wall_time();
- launchd_log_vm_stats();
- }
-
- pthread_mutex_lock(&ourlock);
-
- if (unlikely(ourlogfile == NULL) && g_log_pid1_shutdown) {
- rename("/var/log/launchd-shutdown.log", "/var/log/launchd-shutdown.log.1");
- ourlogfile = fopen("/var/log/launchd-shutdown.log", "a");
- }
-
- pthread_mutex_unlock(&ourlock);
-
- if (unlikely(!ourlogfile)) {
- return;
- }
-
- while ((lm = STAILQ_FIRST(&logmsg_queue))) {
- log_delta = lm->when - shutdown_start;
-
- fprintf(ourlogfile, "%8lld%6u %-40s%6u %-40s %s\n", log_delta,
- lm->from_pid, lm->from_name, lm->about_pid, lm->about_name, lm->msg);
-
- logmsg_remove(lm);
- }
-
- fflush(ourlogfile);
-}
-
-kern_return_t
-runtime_log_forward(uid_t forward_uid, gid_t forward_gid, vm_offset_t inval, mach_msg_type_number_t invalCnt)
-{
- struct logmsg_s *lm, *lm_walk;
- mach_msg_type_number_t data_left = invalCnt;
-
- if (inval == 0) {
- return 0;
- }
-
- for (lm_walk = (struct logmsg_s *)inval; (data_left > 0) && (lm_walk->obj_sz <= data_left); lm_walk = ((void *)lm_walk + lm_walk->obj_sz)) {
- /* malloc() does not return NULL if you ask it for an allocation of size 0.
- * It will return a valid pointer that can be passed to free(). If we don't
- * do this check, we'll wind up corrupting our heap in the subsequent
- * assignments.
- *
- * We break out if this check fails because, obj_sz is supposed to include
- * the size of the logmsg_s struct. If it claims to be of zero size, we
- * can't safely increment our counter because something obviously got screwed
- * up along the way, since this should always be at least sizeof(struct logmsg_s).
- */
- if (!launchd_assumes(lm_walk->obj_sz > 0)) {
- runtime_syslog(LOG_WARNING, "Encountered a log message of size 0 with %u bytes left in forwarded data. Ignoring remaining messages.", data_left);
- break;
- }
-
- /* If malloc() keeps failing, we shouldn't put additional pressure on the system
- * by attempting to add more messages to the log until it returns success
- * log a failure, hope pressure lets off, and move on.
- */
- if (!launchd_assumes(lm = malloc(lm_walk->obj_sz))) {
- runtime_syslog(LOG_WARNING, "Failed to allocate %llu bytes for log message with %u bytes left in forwarded data. Ignoring remaining messages.", lm_walk->obj_sz, data_left);
- break;
- }
-
- memcpy(lm, lm_walk, lm_walk->obj_sz);
- lm->sender_uid = forward_uid;
- lm->sender_gid = forward_gid;
-
- lm->from_name += (size_t)lm;
- lm->about_name += (size_t)lm;
- lm->msg += (size_t)lm;
- lm->session_name += (size_t)lm;
-
- STAILQ_INSERT_TAIL(&logmsg_queue, lm, sqe);
- logmsg_queue_sz += lm->obj_sz;
- logmsg_queue_cnt++;
-
- data_left -= lm->obj_sz;
- }
-
- mig_deallocate(inval, invalCnt);
-
- return 0;
-}
-
-kern_return_t
-runtime_log_drain(mach_port_t srp, vm_offset_t *outval, mach_msg_type_number_t *outvalCnt)
-{
- (void)launchd_assumes(drain_reply_port == 0);
-
- if ((logmsg_queue_cnt == 0) || shutdown_in_progress || fake_shutdown_in_progress) {
- drain_reply_port = srp;
- (void)launchd_assumes(launchd_mport_notify_req(drain_reply_port, MACH_NOTIFY_DEAD_NAME) == KERN_SUCCESS);
-
- return MIG_NO_REPLY;
- }
-
- return runtime_log_pack(outval, outvalCnt);
-}
-
-/*
- * We should break this into two reference counts.
- *
- * One for hard references that would prevent exiting.
- * One for soft references that would only prevent idle exiting.
- *
- * In the long run, reference counting should completely automate when a
- * process can and should exit.
- */
-void
-runtime_add_ref(void)
-{
- if (!pid1_magic) {
- #if !TARGET_OS_EMBEDDED
- _vproc_transaction_begin();
- #endif
- }
-
- runtime_busy_cnt++;
- runtime_remove_timer();
-}
-
-void
-runtime_del_ref(void)
-{
- if (!pid1_magic) {
- #if !TARGET_OS_EMBEDDED
- if (_vproc_transaction_count() == 0) {
- runtime_syslog(LOG_INFO, "Exiting cleanly.");
- }
-
- runtime_closelog();
- _vproc_transaction_end();
- #endif
- }
-
- runtime_busy_cnt--;
- runtime_install_timer();
-}
-
-void
-runtime_add_weak_ref(void)
-{
- if (!pid1_magic) {
- #if !TARGET_OS_EMBEDDED
- _vproc_standby_begin();
- #endif
- }
- runtime_standby_cnt++;
-}
-
-void
-runtime_del_weak_ref(void)
-{
- if (!pid1_magic) {
- #if !TARGET_OS_EMBEDDED
- _vproc_standby_end();
- #endif
- }
- runtime_standby_cnt--;
-}
-
-void
-runtime_install_timer(void)
-{
- if (!pid1_magic && runtime_busy_cnt == 0) {
- (void)launchd_assumes(kevent_mod((uintptr_t)&g_runtime_busy_time, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, 30, root_jobmgr) != -1);
- }
-}
-
-void
-runtime_remove_timer(void)
-{
- if (!pid1_magic && runtime_busy_cnt > 0) {
- (void)launchd_assumes(kevent_mod((uintptr_t)&g_runtime_busy_time, EVFILT_TIMER, EV_DELETE, 0, 0, NULL) != -1);
- }
-}
-
-kern_return_t
-catch_mach_exception_raise(mach_port_t exception_port __attribute__((unused)), mach_port_t thread, mach_port_t task,
- exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt)
-{
- pid_t p4t = -1;
-
- (void)launchd_assumes(pid_for_task(task, &p4t) == 0);
-
- runtime_syslog(LOG_NOTICE, "%s(): PID: %u thread: 0x%x type: 0x%x code: %p codeCnt: 0x%x",
- __func__, p4t, thread, exception, code, codeCnt);
-
- (void)launchd_assumes(launchd_mport_deallocate(thread) == KERN_SUCCESS);
- (void)launchd_assumes(launchd_mport_deallocate(task) == KERN_SUCCESS);
-
- return KERN_SUCCESS;
-}
-
-kern_return_t
-catch_mach_exception_raise_state(mach_port_t exception_port __attribute__((unused)),
- exception_type_t exception, const mach_exception_data_t code, mach_msg_type_number_t codeCnt,
- int *flavor, const thread_state_t old_state, mach_msg_type_number_t old_stateCnt,
- thread_state_t new_state, mach_msg_type_number_t *new_stateCnt)
-{
- runtime_syslog(LOG_NOTICE, "%s(): type: 0x%x code: %p codeCnt: 0x%x flavor: %p old_state: %p old_stateCnt: 0x%x new_state: %p new_stateCnt: %p",
- __func__, exception, code, codeCnt, flavor, old_state, old_stateCnt, new_state, new_stateCnt);
-
- memcpy(new_state, old_state, old_stateCnt * sizeof(old_state[0]));
- *new_stateCnt = old_stateCnt;
-
- return KERN_SUCCESS;
-}
-
-kern_return_t
-catch_mach_exception_raise_state_identity(mach_port_t exception_port __attribute__((unused)), mach_port_t thread, mach_port_t task,
- exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt,
- int *flavor, thread_state_t old_state, mach_msg_type_number_t old_stateCnt,
- thread_state_t new_state, mach_msg_type_number_t *new_stateCnt)
-{
- pid_t p4t = -1;
-
- (void)launchd_assumes(pid_for_task(task, &p4t) == 0);
-
- runtime_syslog(LOG_NOTICE, "%s(): PID: %u thread: 0x%x type: 0x%x code: %p codeCnt: 0x%x flavor: %p old_state: %p old_stateCnt: 0x%x new_state: %p new_stateCnt: %p",
- __func__, p4t, thread, exception, code, codeCnt, flavor, old_state, old_stateCnt, new_state, new_stateCnt);
-
- memcpy(new_state, old_state, old_stateCnt * sizeof(old_state[0]));
- *new_stateCnt = old_stateCnt;
-
- (void)launchd_assumes(launchd_mport_deallocate(thread) == KERN_SUCCESS);
- (void)launchd_assumes(launchd_mport_deallocate(task) == KERN_SUCCESS);
-
- return KERN_SUCCESS;
-}
-
-void
-launchd_log_vm_stats(void)
-{
- static struct vm_statistics orig_stats;
- static bool did_first_pass;
- unsigned int count = HOST_VM_INFO_COUNT;
- struct vm_statistics stats, *statsp;
- mach_port_t mhs = mach_host_self();
-
- statsp = did_first_pass ? &stats : &orig_stats;
-
- if (!launchd_assumes(host_statistics(mhs, HOST_VM_INFO, (host_info_t)statsp, &count) == KERN_SUCCESS)) {
- return;
- }
-
- (void)launchd_assumes(count == HOST_VM_INFO_COUNT);
-
- if (did_first_pass) {
- runtime_syslog(LOG_DEBUG, "VM statistics (now - orig): Free: %d Active: %d Inactive: %d Reactivations: %d PageIns: %d PageOuts: %d Faults: %d COW-Faults: %d Purgeable: %d Purges: %d",
- stats.free_count - orig_stats.free_count,
- stats.active_count - orig_stats.active_count,
- stats.inactive_count - orig_stats.inactive_count,
- stats.reactivations - orig_stats.reactivations,
- stats.pageins - orig_stats.pageins,
- stats.pageouts - orig_stats.pageouts,
- stats.faults - orig_stats.faults,
- stats.cow_faults - orig_stats.cow_faults,
- stats.purgeable_count - orig_stats.purgeable_count,
- stats.purges - orig_stats.purges);
- } else {
- runtime_syslog(LOG_DEBUG, "VM statistics (now): Free: %d Active: %d Inactive: %d Reactivations: %d PageIns: %d PageOuts: %d Faults: %d COW-Faults: %d Purgeable: %d Purges: %d",
- orig_stats.free_count,
- orig_stats.active_count,
- orig_stats.inactive_count,
- orig_stats.reactivations,
- orig_stats.pageins,
- orig_stats.pageouts,
- orig_stats.faults,
- orig_stats.cow_faults,
- orig_stats.purgeable_count,
- orig_stats.purges);
-
- did_first_pass = true;
- }
-
- launchd_mport_deallocate(mhs);
-}
-
-int64_t
-runtime_get_wall_time(void)
-{
- struct timeval tv;
- int64_t r;
-
- (void)launchd_assumes(gettimeofday(&tv, NULL) != -1);
-
- r = tv.tv_sec;
- r *= USEC_PER_SEC;
- r += tv.tv_usec;
-
- return r;
-}
-
-uint64_t
-runtime_get_opaque_time(void)
-{
- return mach_absolute_time();
-}
-
-uint64_t
-runtime_get_opaque_time_of_event(void)
-{
- return time_of_mach_msg_return;
-}
-
-uint64_t
-runtime_get_nanoseconds_since(uint64_t o)
-{
- return runtime_opaque_time_to_nano(runtime_get_opaque_time_of_event() - o);
-}
-
-uint64_t
-runtime_opaque_time_to_nano(uint64_t o)
-{
-#if defined(__i386__) || defined(__x86_64__)
- if (unlikely(tbi.numer != tbi.denom)) {
-#elif defined(__ppc__) || defined(__ppc64__)
- if (likely(tbi.numer != tbi.denom)) {
-#else
- if (tbi.numer != tbi.denom) {
-#endif
-#ifdef __LP64__
- __uint128_t tmp = o;
- tmp *= tbi.numer;
- tmp /= tbi.denom;
- o = tmp;
-#else
- if (o <= tbi_safe_math_max) {
- o *= tbi.numer;
- o /= tbi.denom;
- } else {
- double d = o;
- d *= tbi_float_val;
- o = d;
- }
-#endif
- }
-
- return o;
-}
-
-void
-do_file_init(void)
-{
- struct stat sb;
-
- launchd_assert(mach_timebase_info(&tbi) == 0);
- tbi_float_val = tbi.numer;
- tbi_float_val /= tbi.denom;
- tbi_safe_math_max = UINT64_MAX / tbi.numer;
-
- if (getpid() == 1) {
- pid1_magic = true;
- }
-
- if (stat("/AppleInternal", &sb) == 0 && stat("/var/db/disableAppleInternal", &sb) == -1) {
- do_apple_internal_logging = true;
- }
-
- if (stat("/var/db/.debug_launchd", &sb) == 0) {
- internal_mask_pri = LOG_UPTO(LOG_DEBUG);
- low_level_debug = true;
- }
-
- if (stat("/var/db/.launchd_log_per_user_shutdown", &sb) == 0) {
- g_log_per_user_shutdown = true;
- }
-
- if (stat("/var/db/.launchd_use_gmalloc", &sb) == 0) {
- g_use_gmalloc = true;
- }
-
- if (stat("/var/db/.launchd_malloc_log_stacks", &sb) == 0) {
- g_malloc_log_stacks = true;
- g_use_gmalloc = false;
- }
-
- if (pid1_magic && stat("/var/db/.launchd_log_pid1_shutdown", &sb) == 0) {
- g_log_pid1_shutdown = true;
- }
-
- char bootargs[128];
- size_t len = sizeof(bootargs) - 1;
- int r = pid1_magic ? sysctlbyname("kern.bootargs", bootargs, &len, NULL, 0) : -1;
- if (r == 0) {
- if (strnstr(bootargs, "-v", len)) {
- g_verbose_boot = true;
- }
- if (strnstr(bootargs, "launchd_trap_sigkill_bugs", len)) {
- g_trap_sigkill_bugs = true;
- }
- }
-
- if (pid1_magic && g_verbose_boot && stat("/var/db/.launchd_shutdown_debugging", &sb) == 0) {
- g_shutdown_debugging = true;
- }
-
- if (stat("/var/db/.launchd_log_strict_usage", &sb) == 0) {
- g_log_strict_usage = true;
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2005 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@
- */
-#ifndef __LAUNCHD_RUNTIME_H__
-#define __LAUNCHD_RUNTIME_H__
-
-#include <mach/mach.h>
-#include <sys/types.h>
-#include <bsm/libbsm.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <float.h>
-#include <syslog.h>
-
-#include "launchd_runtime_kill.h"
-#include "launchd_ktrace.h"
-
-#if 0
-
-/* I need to do more testing of these macros */
-
-#define min_of_type(x) \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), long double), LDBL_MIN, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), double), DBL_MIN, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), float), FLT_MIN, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), char), 0, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), signed char), INT8_MIN, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), short), INT16_MIN, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int), INT32_MIN, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), long), (__builtin_choose_expr(sizeof(x) == 4, INT32_MIN, INT64_MIN)), \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), long long), INT64_MIN, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), unsigned char), 0, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), unsigned short), 0, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), unsigned int), 0, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), unsigned long), 0, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), unsigned long long), 0, \
- (void)0))))))))))))))
-
-#define max_of_type(x) \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), long double), LDBL_MAX, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), double), DBL_MAX, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), float), FLT_MAX, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), char), UINT8_MAX, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), signed char), INT8_MAX, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), short), INT16_MIN, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int), INT32_MAX, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), long), (__builtin_choose_expr(sizeof(x) == 4, INT32_MAX, INT64_MAX)), \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), long long), INT64_MAX, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), unsigned char), UINT8_MAX, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), unsigned short), UINT16_MAX, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), unsigned int), UINT32_MAX, \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), unsigned long), (__builtin_choose_expr(sizeof(x) == 4, UINT32_MAX, UINT64_MAX)), \
- __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), unsigned long long), UINT64_MAX, \
- (void)0))))))))))))))
-
-#endif
-
-#define likely(x) __builtin_expect((bool)(x), true)
-#define unlikely(x) __builtin_expect((bool)(x), false)
-
-struct ldcred {
- uid_t euid;
- uid_t uid;
- gid_t egid;
- gid_t gid;
- pid_t pid;
- au_asid_t asid;
- mach_port_t asport;
-};
-
-/*
- * Use launchd_assumes() when we can recover, even if it means we leak or limp along.
- *
- * Use launchd_assert() for core initialization routines.
- */
-#define launchd_assumes(e) \
- (unlikely(!(e)) ? _log_launchd_bug(__rcs_file_version__, __FILE__, __LINE__, #e), false : true)
-
-#define launchd_assert(e) if (__builtin_constant_p(e)) { char __compile_time_assert__[e ? 1 : -1] __attribute__((unused)); } else if (!launchd_assumes(e)) { abort(); }
-
-void _log_launchd_bug(const char *rcs_rev, const char *path, unsigned int line, const char *test);
-
-typedef void (*kq_callback)(void *, struct kevent *);
-typedef boolean_t (*mig_callback)(mach_msg_header_t *, mach_msg_header_t *);
-typedef void (*timeout_callback)(void);
-
-extern bool pid1_magic;
-extern bool low_level_debug;
-extern char g_username[128];
-extern char g_my_label[128];
-extern bool g_shutdown_debugging;
-extern bool g_verbose_boot;
-extern bool g_use_gmalloc;
-extern bool g_malloc_log_stacks;
-extern bool g_log_per_user_shutdown;
-extern bool g_log_strict_usage;
-extern bool g_embedded_shutdown_log;
-extern bool g_runtime_busy_time;
-extern bool g_trap_sigkill_bugs;
-extern size_t runtime_busy_cnt;
-extern int32_t g_sync_frequency;
-extern pid_t g_wsp;
-
-mach_port_t runtime_get_kernel_port(void);
-extern boolean_t launchd_internal_demux(mach_msg_header_t *Request, mach_msg_header_t *Reply);
-
-void runtime_add_ref(void);
-void runtime_del_ref(void);
-void runtime_add_weak_ref(void);
-void runtime_del_weak_ref(void);
-void runtime_install_timer(void);
-void runtime_remove_timer(void);
-
-void launchd_runtime_init(void);
-void launchd_runtime_init2(void);
-void launchd_runtime(void) __attribute__((noreturn));
-
-void launchd_log_vm_stats(void);
-
-int runtime_close(int fd);
-int runtime_fsync(int fd);
-
-#define RUNTIME_ADVISABLE_IDLE_TIMEOUT 30
-
-void runtime_set_timeout(timeout_callback to_cb, unsigned int sec);
-kern_return_t runtime_add_mport(mach_port_t name, mig_callback demux, mach_msg_size_t msg_size);
-kern_return_t runtime_remove_mport(mach_port_t name);
-struct ldcred *runtime_get_caller_creds(void);
-
-const char *signal_to_C_name(unsigned int sig);
-const char *reboot_flags_to_C_names(unsigned int flags);
-
-int kevent_bulk_mod(struct kevent *kev, size_t kev_cnt);
-int kevent_mod(uintptr_t ident, short filter, u_short flags, u_int fflags, intptr_t data, void *udata);
-void log_kevent_struct(int level, struct kevent *kev_base, int indx);
-
-pid_t runtime_fork(mach_port_t bsport);
-
-mach_msg_return_t launchd_exc_runtime_once(mach_port_t port, mach_msg_size_t rcv_msg_size, mach_msg_size_t send_msg_size, mig_reply_error_t *bufRequest, mig_reply_error_t *bufReply, mach_msg_timeout_t to);
-
-kern_return_t runtime_log_forward(uid_t forward_uid, gid_t forward_gid, vm_offset_t inval, mach_msg_type_number_t invalCnt);
-kern_return_t runtime_log_drain(mach_port_t srp, vm_offset_t *outval, mach_msg_type_number_t *outvalCnt);
-
-#define LOG_APPLEONLY 0x4141504c /* AAPL in hex */
-#define LOG_SCOLDING 0x3030493b
-#define LOG_CONSOLE (1 << 31)
-
-struct runtime_syslog_attr {
- const char *from_name;
- const char *about_name;
- const char *session_name;
- int priority;
- uid_t from_uid;
- pid_t from_pid;
- pid_t about_pid;
-};
-
-int runtime_setlogmask(int maskpri);
-void runtime_closelog(void);
-void runtime_syslog(int pri, const char *message, ...) __attribute__((format(printf, 2, 3)));
-void runtime_vsyslog(struct runtime_syslog_attr *attr, const char *message, va_list args) __attribute__((format(printf, 2, 0)));
-void runtime_log_push(void);
-
-int64_t runtime_get_wall_time(void) __attribute__((warn_unused_result));
-uint64_t runtime_get_opaque_time(void) __attribute__((warn_unused_result));
-uint64_t runtime_get_opaque_time_of_event(void) __attribute__((pure, warn_unused_result));
-uint64_t runtime_opaque_time_to_nano(uint64_t o) __attribute__((const, warn_unused_result));
-uint64_t runtime_get_nanoseconds_since(uint64_t o) __attribute__((pure, warn_unused_result));
-
-kern_return_t launchd_set_bport(mach_port_t name);
-kern_return_t launchd_get_bport(mach_port_t *name);
-kern_return_t launchd_mport_notify_req(mach_port_t name, mach_msg_id_t which);
-kern_return_t launchd_mport_notify_cancel(mach_port_t name, mach_msg_id_t which);
-kern_return_t launchd_mport_create_recv(mach_port_t *name);
-kern_return_t launchd_mport_deallocate(mach_port_t name);
-kern_return_t launchd_mport_make_send(mach_port_t name);
-kern_return_t launchd_mport_copy_send(mach_port_t name);
-kern_return_t launchd_mport_make_send_once(mach_port_t name, mach_port_t *so);
-kern_return_t launchd_mport_close_recv(mach_port_t name);
-
-#endif
+++ /dev/null
-/*
- * Copyright (c) 2007 Apple 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 <sys/syscall.h>
-#include <unistd.h>
-#include <signal.h>
-
-#include "launchd_runtime_kill.h"
-
-int
-runtime_kill(pid_t pid, int sig)
-{
- /*
- * POSIX defines consistency over correctness, and consequently
- * kill/killpg now returns EPERM instead of ESRCH.
- *
- * I've filed 5487498 to get a non-portable kill().
- * We'll regretfully take advantage of implementation details for now.
- */
- return syscall(SYS_kill, pid, sig, 0);
-}
-
-int
-runtime_killpg(pid_t pgrp, int sig)
-{
- return runtime_kill(-pgrp, sig);
-}
+++ /dev/null
-/*
- * Copyright (c) 2007 Apple 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@
- */
-#ifndef __LAUNCHD_RUNTIME_KILL_H__
-#define __LAUNCHD_RUNTIME_KILL_H__
-
-#include <sys/types.h>
-
-int runtime_kill(pid_t pid, int sig);
-int runtime_killpg(pid_t pgrp, int sig);
-
-#endif
+++ /dev/null
-/*
- * Copyright (c) 2005 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@
- */
-
-static const char *const __rcs_file_version__ = "$Revision: 24557 $";
-
-#include "config.h"
-#include "launchd_unix_ipc.h"
-
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/queue.h>
-#include <sys/event.h>
-#include <sys/stat.h>
-#include <sys/ucred.h>
-#include <sys/fcntl.h>
-#include <sys/un.h>
-#include <sys/wait.h>
-#include <sys/sysctl.h>
-#include <sys/sockio.h>
-#include <sys/time.h>
-#include <sys/resource.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
-#include <signal.h>
-#include <errno.h>
-#include <libgen.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <paths.h>
-#include <string.h>
-
-#include "launch.h"
-#include "launch_priv.h"
-#include "launchd.h"
-#include "launchd_runtime.h"
-#include "launchd_core_logic.h"
-
-extern char **environ;
-
-static LIST_HEAD(, conncb) connections;
-
-static launch_data_t adjust_rlimits(launch_data_t in);
-
-static void ipc_readmsg2(launch_data_t data, const char *cmd, void *context);
-static void ipc_readmsg(launch_data_t msg, void *context);
-
-static void ipc_listen_callback(void *obj __attribute__((unused)), struct kevent *kev);
-
-static kq_callback kqipc_listen_callback = ipc_listen_callback;
-
-static pid_t ipc_self = 0;
-
-char *sockpath = NULL;
-static char *sockdir = NULL;
-
-static bool ipc_inited = false;
-
-static void
-ipc_clean_up(void)
-{
- if (ipc_self != getpid()) {
- return;
- }
-
- if (-1 == unlink(sockpath)) {
- runtime_syslog(LOG_WARNING, "unlink(\"%s\"): %s", sockpath, strerror(errno));
- } else if (-1 == rmdir(sockdir)) {
- runtime_syslog(LOG_WARNING, "rmdir(\"%s\"): %s", sockdir, strerror(errno));
- }
-}
-
-void
-ipc_server_init(void)
-{
- struct sockaddr_un sun;
- mode_t oldmask;
- int r, fd = -1;
- char ourdir[1024];
-
- if (ipc_inited) {
- return;
- }
-
- memset(&sun, 0, sizeof(sun));
- sun.sun_family = AF_UNIX;
-
- if (pid1_magic) {
- strcpy(ourdir, LAUNCHD_SOCK_PREFIX);
- strncpy(sun.sun_path, LAUNCHD_SOCK_PREFIX "/sock", sizeof(sun.sun_path));
-
- unlink(ourdir);
- if (mkdir(ourdir, S_IRWXU) == -1) {
- if (errno == EROFS) {
- goto out_bad;
- } else if (errno == EEXIST) {
- struct stat sb;
- stat(ourdir, &sb);
- if (!S_ISDIR(sb.st_mode)) {
- errno = EEXIST;
- runtime_syslog(LOG_ERR, "mkdir(\"%s\"): %s", LAUNCHD_SOCK_PREFIX, strerror(errno));
- goto out_bad;
- }
- } else {
- runtime_syslog(LOG_ERR, "mkdir(\"%s\"): %s", ourdir, strerror(errno));
- goto out_bad;
- }
- }
- } else {
- snprintf(ourdir, sizeof(ourdir), _PATH_TMP "launchd-%u.XXXXXX", getpid());
- if (mkdtemp(ourdir) == NULL) {
- runtime_syslog(LOG_ERR, "Could not create critical directory \"%s\": %s", ourdir, strerror(errno));
- goto out_bad;
- }
- snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/sock", ourdir);
- }
-
- if (unlink(sun.sun_path) == -1 && errno != ENOENT) {
- if (errno != EROFS) {
- runtime_syslog(LOG_ERR, "unlink(\"thesocket\"): %s", strerror(errno));
- }
- goto out_bad;
- }
-
- if (!launchd_assumes((fd = _fd(socket(AF_UNIX, SOCK_STREAM, 0))) != -1)) {
- goto out_bad;
- }
-
- oldmask = umask(S_IRWXG|S_IRWXO);
- r = bind(fd, (struct sockaddr *)&sun, sizeof(sun));
- umask(oldmask);
-
- if (r == -1) {
- if (errno != EROFS) {
- runtime_syslog(LOG_ERR, "bind(\"thesocket\"): %s", strerror(errno));
- }
- goto out_bad;
- }
-
- if (listen(fd, SOMAXCONN) == -1) {
- runtime_syslog(LOG_ERR, "listen(\"thesocket\"): %s", strerror(errno));
- goto out_bad;
- }
-
- if (kevent_mod(fd, EVFILT_READ, EV_ADD, 0, 0, &kqipc_listen_callback) == -1) {
- runtime_syslog(LOG_ERR, "kevent_mod(\"thesocket\", EVFILT_READ): %s", strerror(errno));
- goto out_bad;
- }
-
- ipc_inited = true;
-
- sockdir = strdup(ourdir);
- sockpath = strdup(sun.sun_path);
- ipc_self = getpid();
- atexit(ipc_clean_up);
-
-out_bad:
- if (!ipc_inited && fd != -1) {
- (void)launchd_assumes(runtime_close(fd) == 0);
- }
-}
-
-void
-ipc_open(int fd, job_t j)
-{
- struct conncb *c = calloc(1, sizeof(struct conncb));
-
- fcntl(fd, F_SETFL, O_NONBLOCK);
-
- c->kqconn_callback = ipc_callback;
- if (j) {
- c->conn = launchd_fdopen(-1, fd);
- } else {
- c->conn = launchd_fdopen(fd, -1);
- }
-
- c->j = j;
- LIST_INSERT_HEAD(&connections, c, sle);
- kevent_mod(fd, EVFILT_READ, EV_ADD, 0, 0, &c->kqconn_callback);
-}
-
-void
-ipc_listen_callback(void *obj __attribute__((unused)), struct kevent *kev)
-{
- struct sockaddr_un sun;
- socklen_t sl = sizeof(sun);
- int cfd;
-
- if ((cfd = _fd(accept(kev->ident, (struct sockaddr *)&sun, &sl))) == -1) {
- return;
- }
-
- ipc_open(cfd, NULL);
-}
-
-void
-ipc_callback(void *obj, struct kevent *kev)
-{
- struct conncb *c = obj;
- int r;
-
- if (kev->filter == EVFILT_READ) {
- if (launchd_msg_recv(c->conn, ipc_readmsg, c) == -1 && errno != EAGAIN) {
- if (errno != ECONNRESET) {
- runtime_syslog(LOG_DEBUG, "%s(): recv: %s", __func__, strerror(errno));
- }
- ipc_close(c);
- }
- } else if (kev->filter == EVFILT_WRITE) {
- r = launchd_msg_send(c->conn, NULL);
- if (r == -1) {
- if (errno != EAGAIN) {
- runtime_syslog(LOG_DEBUG, "%s(): send: %s", __func__, strerror(errno));
- ipc_close(c);
- }
- } else if (r == 0) {
- kevent_mod(launchd_getfd(c->conn), EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
- }
- } else {
- runtime_syslog(LOG_DEBUG, "%s(): unknown filter type!", __func__);
- ipc_close(c);
- }
-}
-
-static void
-set_user_env(launch_data_t obj, const char *key, void *context __attribute__((unused)))
-{
- const char *v = launch_data_get_string(obj);
- if (v) {
- setenv(key, v, 1);
- } else {
- runtime_syslog(LOG_WARNING, "Attempt to set NULL environment variable: %s (type = %d)", key, launch_data_get_type(obj));
- }
-}
-
-void
-ipc_close_all_with_job(job_t j)
-{
- struct conncb *ci, *cin;
-
- LIST_FOREACH_SAFE(ci, &connections, sle, cin) {
- if (ci->j == j) {
- ipc_close(ci);
- }
- }
-}
-
-void
-ipc_close_fds(launch_data_t o)
-{
- size_t i;
-
- switch (launch_data_get_type(o)) {
- case LAUNCH_DATA_DICTIONARY:
- launch_data_dict_iterate(o, (void (*)(launch_data_t, const char *, void *))ipc_close_fds, NULL);
- break;
- case LAUNCH_DATA_ARRAY:
- for (i = 0; i < launch_data_array_get_count(o); i++)
- ipc_close_fds(launch_data_array_get_index(o, i));
- break;
- case LAUNCH_DATA_FD:
- if (launch_data_get_fd(o) != -1) {
- (void)launchd_assumes(runtime_close(launch_data_get_fd(o)) == 0);
- }
- break;
- default:
- break;
- }
-}
-
-void
-ipc_revoke_fds(launch_data_t o)
-{
- size_t i;
-
- switch (launch_data_get_type(o)) {
- case LAUNCH_DATA_DICTIONARY:
- launch_data_dict_iterate(o, (void (*)(launch_data_t, const char *, void *))ipc_revoke_fds, NULL);
- break;
- case LAUNCH_DATA_ARRAY:
- for (i = 0; i < launch_data_array_get_count(o); i++)
- ipc_revoke_fds(launch_data_array_get_index(o, i));
- break;
- case LAUNCH_DATA_FD:
- launch_data_set_fd(o, -1);
- break;
- default:
- break;
- }
-}
-
-struct readmsg_context {
- struct conncb *c;
- launch_data_t resp;
-};
-
-void
-ipc_readmsg(launch_data_t msg, void *context)
-{
- struct readmsg_context rmc = { context, NULL };
-
- if (LAUNCH_DATA_DICTIONARY == launch_data_get_type(msg)) {
- launch_data_dict_iterate(msg, ipc_readmsg2, &rmc);
- } else if (LAUNCH_DATA_STRING == launch_data_get_type(msg)) {
- ipc_readmsg2(NULL, launch_data_get_string(msg), &rmc);
- } else {
- rmc.resp = launch_data_new_errno(EINVAL);
- }
-
- if (NULL == rmc.resp) {
- rmc.resp = launch_data_new_errno(ENOSYS);
- }
-
- ipc_close_fds(msg);
-
- if (launchd_msg_send(rmc.c->conn, rmc.resp) == -1) {
- if (errno == EAGAIN) {
- kevent_mod(launchd_getfd(rmc.c->conn), EVFILT_WRITE, EV_ADD, 0, 0, &rmc.c->kqconn_callback);
- } else {
- runtime_syslog(LOG_DEBUG, "launchd_msg_send() == -1: %s", strerror(errno));
- ipc_close(rmc.c);
- }
- }
- launch_data_free(rmc.resp);
-}
-
-void
-ipc_readmsg2(launch_data_t data, const char *cmd, void *context)
-{
- struct readmsg_context *rmc = context;
- launch_data_t resp = NULL;
- job_t j;
-
- if (rmc->resp) {
- return;
- }
-
-// job_log(rmc->c->j, LOG_NOTICE, "Socket IPC request: %s.", cmd);
-
- /* Do not allow commands other than check-in to come over the trusted socket
- * on the Desktop. On Embedded, allow all commands over the trusted socket if
- * the job has the God Mode key set.
- */
-#if TARGET_OS_EMBEDDED
- bool allow_privileged_ops = ( !rmc->c->j || job_is_god(rmc->c->j) );
-#else
- bool allow_privileged_ops = !rmc->c->j;
-#endif
-
- if (rmc->c->j && strcmp(cmd, LAUNCH_KEY_CHECKIN) == 0) {
- resp = job_export(rmc->c->j);
- job_checkin(rmc->c->j);
- } else if (allow_privileged_ops) {
- #if TARGET_OS_EMBEDDED
- g_embedded_privileged_action = rmc->c->j && job_is_god(rmc->c->j);
- #endif
- if (data == NULL) {
- if (!strcmp(cmd, LAUNCH_KEY_SHUTDOWN)) {
- launchd_shutdown();
- resp = launch_data_new_errno(0);
- } else if (!strcmp(cmd, LAUNCH_KEY_SINGLEUSER)) {
- launchd_single_user();
- resp = launch_data_new_errno(0);
- } else if (!strcmp(cmd, LAUNCH_KEY_GETJOBS)) {
- resp = job_export_all();
- ipc_revoke_fds(resp);
- } else if (!strcmp(cmd, LAUNCH_KEY_GETRESOURCELIMITS)) {
- resp = adjust_rlimits(NULL);
- } else if (!strcmp(cmd, LAUNCH_KEY_GETRUSAGESELF)) {
- struct rusage rusage;
- getrusage(RUSAGE_SELF, &rusage);
- resp = launch_data_new_opaque(&rusage, sizeof(rusage));
- } else if (!strcmp(cmd, LAUNCH_KEY_GETRUSAGECHILDREN)) {
- struct rusage rusage;
- getrusage(RUSAGE_CHILDREN, &rusage);
- resp = launch_data_new_opaque(&rusage, sizeof(rusage));
- }
- } else {
- if (!strcmp(cmd, LAUNCH_KEY_STARTJOB)) {
- if ((j = job_find(NULL, launch_data_get_string(data))) != NULL) {
- errno = job_dispatch(j, true) ? 0 : errno;
- }
- resp = launch_data_new_errno(errno);
- } else if (!strcmp(cmd, LAUNCH_KEY_STOPJOB)) {
- if ((j = job_find(NULL, launch_data_get_string(data))) != NULL) {
- errno = 0;
- job_stop(j);
- }
- resp = launch_data_new_errno(errno);
- } else if (!strcmp(cmd, LAUNCH_KEY_REMOVEJOB)) {
- if ((j = job_find(NULL, launch_data_get_string(data))) != NULL) {
- errno = 0;
- job_remove(j);
- }
- resp = launch_data_new_errno(errno);
- } else if (!strcmp(cmd, LAUNCH_KEY_SUBMITJOB)) {
- if (launch_data_get_type(data) == LAUNCH_DATA_ARRAY) {
- resp = job_import_bulk(data);
- } else {
- if (job_import(data)) {
- errno = 0;
- }
- resp = launch_data_new_errno(errno);
- }
- } else if (!strcmp(cmd, LAUNCH_KEY_UNSETUSERENVIRONMENT)) {
- unsetenv(launch_data_get_string(data));
- resp = launch_data_new_errno(0);
- } else if (!strcmp(cmd, LAUNCH_KEY_SETUSERENVIRONMENT)) {
- launch_data_dict_iterate(data, set_user_env, NULL);
- resp = launch_data_new_errno(0);
- } else if (!strcmp(cmd, LAUNCH_KEY_SETRESOURCELIMITS)) {
- resp = adjust_rlimits(data);
- } else if (!strcmp(cmd, LAUNCH_KEY_GETJOB)) {
- if ((j = job_find(NULL, launch_data_get_string(data))) == NULL) {
- resp = launch_data_new_errno(errno);
- } else {
- resp = job_export(j);
- ipc_revoke_fds(resp);
- }
- } else if (!strcmp(cmd, LAUNCH_KEY_SETPRIORITYLIST)) {
- resp = launch_data_new_errno(launchd_set_jetsam_priorities(data));
- }
- }
- #if TARGET_OS_EMBEDDED
- g_embedded_privileged_action = false;
- #endif
- } else {
- resp = launch_data_new_errno(EACCES);
- }
-
- rmc->resp = resp;
-}
-
-static int
-close_abi_fixup(int fd)
-{
- return runtime_close(fd);
-}
-
-void
-ipc_close(struct conncb *c)
-{
- LIST_REMOVE(c, sle);
- launchd_close(c->conn, close_abi_fixup);
- free(c);
-}
-
-launch_data_t
-adjust_rlimits(launch_data_t in)
-{
- struct rlimit l[RLIM_NLIMITS];
- struct rlimit *ltmp;
- size_t i,ltmpsz;
-
- for (i = 0; i < RLIM_NLIMITS; i++) {
- (void)launchd_assumes(getrlimit(i, l + i) != -1);
- }
-
- if (in) {
- ltmp = launch_data_get_opaque(in);
- ltmpsz = launch_data_get_opaque_size(in);
-
- if (ltmpsz > sizeof(l)) {
- runtime_syslog(LOG_WARNING, "Too much rlimit data sent!");
- ltmpsz = sizeof(l);
- }
-
- for (i = 0; i < (ltmpsz / sizeof(struct rlimit)); i++) {
- if (ltmp[i].rlim_cur == l[i].rlim_cur && ltmp[i].rlim_max == l[i].rlim_max) {
- continue;
- }
-
- if (/* XXX readcfg_pid && */ pid1_magic && (i == RLIMIT_NOFILE || i == RLIMIT_NPROC)) {
- int gmib[] = { CTL_KERN, KERN_MAXPROC };
- int pmib[] = { CTL_KERN, KERN_MAXPROCPERUID };
- const char *gstr = "kern.maxproc";
- const char *pstr = "kern.maxprocperuid";
- int gval = ltmp[i].rlim_max;
- int pval = ltmp[i].rlim_cur;
- switch (i) {
- case RLIMIT_NOFILE:
- gmib[1] = KERN_MAXFILES;
- pmib[1] = KERN_MAXFILESPERPROC;
- gstr = "kern.maxfiles";
- pstr = "kern.maxfilesperproc";
- break;
- default:
- break;
- }
-
- if (gval > 0) {
- (void)launchd_assumes(sysctl(gmib, 2, NULL, NULL, &gval, sizeof(gval)) != -1);
- } else {
- runtime_syslog(LOG_WARNING, "sysctl(\"%s\"): can't be zero", gstr);
- }
- if (pval > 0) {
- (void)launchd_assumes(sysctl(pmib, 2, NULL, NULL, &pval, sizeof(pval)) != -1);
- } else {
- runtime_syslog(LOG_WARNING, "sysctl(\"%s\"): can't be zero", pstr);
- }
- }
- (void)launchd_assumes(setrlimit(i, ltmp + i) != -1);
- /* the kernel may have clamped the values we gave it */
- (void)launchd_assumes(getrlimit(i, l + i) != -1);
- }
- }
-
- return launch_data_new_opaque(l, sizeof(struct rlimit) * RLIM_NLIMITS);
-}
+++ /dev/null
-#ifndef __LAUNCHD_UNIX_IPC__
-#define __LAUNCHD_UNIX_IPC__
-/*
- * Copyright (c) 2005 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 <sys/queue.h>
-
-#include "launchd_runtime.h"
-#include "launchd_core_logic.h"
-#include "launch_priv.h"
-#include "launch_internal.h"
-
-struct conncb {
- kq_callback kqconn_callback;
- LIST_ENTRY(conncb) sle;
- launch_t conn;
- job_t j;
-};
-
-extern char *sockpath;
-
-void ipc_open(int fd, job_t j);
-void ipc_close_all_with_job(job_t j);
-void ipc_close(struct conncb *c);
-void ipc_callback(void *, struct kevent *);
-void ipc_revoke_fds(launch_data_t o);
-void ipc_close_fds(launch_data_t o);
-void ipc_server_init(void);
-
-#endif
+++ /dev/null
-.Dd February 2, 2005
-.Dt launchproxy 8
-.Os Darwin
-.Sh NAME
-.Nm launchproxy
-.Nd inetd job emulation helper
-.Sh SYNOPSIS
-.Nm
-.Sh DESCRIPTION
-.Nm
-handles the inetd emulation for
-.Nm launchd .
-This program may be merged into
-.Nm launchd
-in the future.
-.Sh SEE ALSO
-.Xr launchctl 1 ,
-.Xr launchd.plist 5 ,
-.Xr launchd 8
+++ /dev/null
-/*
- * Copyright (c) 2005 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 "config.h"
-#include <sys/types.h>
-#include <sys/select.h>
-#include <sys/event.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <syslog.h>
-#include <libgen.h>
-#include <getopt.h>
-#include <signal.h>
-#include <netdb.h>
-
-#if !TARGET_OS_EMBEDDED
-#include <bsm/audit.h>
-#include <bsm/audit_session.h>
-#endif // !TARGET_OS_EMBEDDED
-
-#include "launch.h"
-
-static int kq = 0;
-
-static void find_fds(launch_data_t o, const char *key __attribute__((unused)), void *context __attribute__((unused)))
-{
- struct kevent kev;
- size_t i;
- int fd;
-
- switch (launch_data_get_type(o)) {
- case LAUNCH_DATA_FD:
- fd = launch_data_get_fd(o);
- if (-1 == fd)
- break;
- fcntl(fd, F_SETFD, 1);
- EV_SET(&kev, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
- if (kevent(kq, &kev, 1, NULL, 0, NULL) == -1)
- syslog(LOG_DEBUG, "kevent(%d): %m", fd);
- break;
- case LAUNCH_DATA_ARRAY:
- for (i = 0; i < launch_data_array_get_count(o); i++)
- find_fds(launch_data_array_get_index(o, i), NULL, NULL);
- break;
- case LAUNCH_DATA_DICTIONARY:
- launch_data_dict_iterate(o, find_fds, NULL);
- break;
- default:
- break;
- }
-}
-
-int main(int argc __attribute__((unused)), char *argv[])
-{
- struct timespec timeout = { 10, 0 };
- struct sockaddr_storage ss;
- socklen_t slen = (socklen_t)sizeof ss;
- struct kevent kev;
- int r, ec = EXIT_FAILURE;
- launch_data_t tmp, resp, msg = launch_data_alloc(LAUNCH_DATA_STRING);
- const char *prog = argv[1];
- bool w = false, dupstdout = true, dupstderr = true;
-
- launch_data_set_string(msg, LAUNCH_KEY_CHECKIN);
-
- openlog(getprogname(), LOG_PERROR|LOG_PID|LOG_CONS, LOG_LAUNCHD);
-
- kq = kqueue();
-
- if ((resp = launch_msg(msg)) == NULL) {
- syslog(LOG_ERR, "launch_msg(%s): %m", LAUNCH_KEY_CHECKIN);
- goto out;
- }
-
- launch_data_free(msg);
-
- tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_SOCKETS);
- if (tmp) {
- find_fds(tmp, NULL, NULL);
- } else {
- syslog(LOG_ERR, "No FDs found to answer requests on!");
- goto out;
- }
-
- tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_TIMEOUT);
- if (tmp)
- timeout.tv_sec = (int)launch_data_get_integer(tmp);
-
- tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_PROGRAM);
- if (tmp)
- prog = launch_data_get_string(tmp);
-
- tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_INETDCOMPATIBILITY);
- if (tmp) {
- tmp = launch_data_dict_lookup(tmp, LAUNCH_JOBINETDCOMPATIBILITY_WAIT);
- if (tmp)
- w = launch_data_get_bool(tmp);
- }
-
- if (launch_data_dict_lookup(resp, LAUNCH_JOBKEY_STANDARDOUTPATH))
- dupstdout = false;
-
- if (launch_data_dict_lookup(resp, LAUNCH_JOBKEY_STANDARDERRORPATH))
- dupstderr = false;
-
- if (!w)
- signal(SIGCHLD, SIG_IGN);
-
- for (;;) {
- if ((r = kevent(kq, NULL, 0, &kev, 1, &timeout)) == -1) {
- syslog(LOG_DEBUG, "kevent(): %m");
- goto out;
- } else if (r == 0) {
- ec = EXIT_SUCCESS;
- goto out;
- }
-
- if (w) {
- dup2((int)kev.ident, STDIN_FILENO);
- if (dupstdout)
- dup2((int)kev.ident, STDOUT_FILENO);
- if (dupstderr)
- dup2((int)kev.ident, STDERR_FILENO);
- execv(prog, argv + 1);
- syslog(LOG_ERR, "execv(): %m");
- exit(EXIT_FAILURE);
- }
-
- if ((r = accept((int)kev.ident, (struct sockaddr *)&ss, &slen)) == -1) {
- if (errno == EWOULDBLOCK)
- continue;
- syslog(LOG_WARNING, "accept(): %m");
- goto out;
- } else {
- if (ss.ss_family == AF_INET || ss.ss_family == AF_INET6) {
- char fromhost[NI_MAXHOST];
- char fromport[NI_MAXSERV];
- int gni_r;
-
- gni_r = getnameinfo((struct sockaddr *)&ss, slen,
- fromhost, (socklen_t) sizeof fromhost,
- fromport, (socklen_t) sizeof fromport,
- NI_NUMERICHOST | NI_NUMERICSERV);
-
- if (gni_r) {
- syslog(LOG_WARNING, "%s: getnameinfo(): %s", prog, gai_strerror(gni_r));
- } else {
- syslog(LOG_INFO, "%s: Connection from: %s on port: %s", prog, fromhost, fromport);
- }
- } else {
- syslog(LOG_WARNING, "%s: getnameinfo() only supports IPv4/IPv6. Connection from address family: %u", prog, ss.ss_family);
- }
-
- switch (fork()) {
- case -1:
- syslog(LOG_WARNING, "fork(): %m");
- if (errno != ENOMEM) {
- continue;
- }
- goto out;
- case 0:
- break;
- default:
- close(r);
- continue;
- }
-
- setpgid(0, 0);
-
-#if !TARGET_OS_EMBEDDED
- if ((tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_SESSIONCREATE)) && launch_data_get_bool(tmp)) {
- auditinfo_addr_t auinfo = {
- .ai_termid = { .at_type = AU_IPv4 },
- .ai_asid = AU_ASSIGN_ASID,
- .ai_auid = getuid(),
- .ai_flags = 0,
- };
- if (setaudit_addr(&auinfo, sizeof(auinfo)) == 0) {
- char session[16];
- snprintf(session, sizeof(session), "%x", auinfo.ai_asid);
- setenv("SECURITYSESSIONID", session, 1);
- } else {
- syslog(LOG_NOTICE, "%s: Setting Audit Session ID failed: %d", prog, errno);
- }
- }
-#endif // !TARGET_OS_EMBEDDED
- fcntl(r, F_SETFL, 0);
- fcntl(r, F_SETFD, 1);
- dup2(r, STDIN_FILENO);
- if (dupstdout)
- dup2(r, STDOUT_FILENO);
- if (dupstderr)
- dup2(r, STDERR_FILENO);
- signal(SIGCHLD, SIG_DFL);
- execv(prog, argv + 1);
- syslog(LOG_ERR, "execv(): %m");
- exit(EXIT_FAILURE);
- }
- }
-
-out:
- exit(ec);
-}
+++ /dev/null
-/*
- * Copyright (c) 1999-2005 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 "config.h"
-#include "launch.h"
-#include "launch_priv.h"
-#include "bootstrap.h"
-#include "bootstrap_priv.h"
-#include "vproc.h"
-#include "vproc_priv.h"
-
-#include <mach/mach.h>
-#include <mach/vm_map.h>
-#include <sys/types.h>
-#include <sys/syslog.h>
-#include <sys/stat.h>
-#include <pthread.h>
-#include <stdlib.h>
-
-#include "protocol_vproc.h"
-
-mach_port_t bootstrap_port = MACH_PORT_NULL;
-
-void
-bootstrap_init(void)
-{
- kern_return_t kr = task_get_special_port(task_self_trap(), TASK_BOOTSTRAP_PORT, &bootstrap_port);
- if (kr != KERN_SUCCESS) {
- abort();
- }
-}
-
-kern_return_t
-bootstrap_create_server(mach_port_t bp, cmd_t server_cmd, uid_t server_uid, boolean_t on_demand, mach_port_t *server_port)
-{
- kern_return_t kr;
-
- kr = vproc_mig_create_server(bp, server_cmd, server_uid, on_demand, server_port);
-
- if (kr == VPROC_ERR_TRY_PER_USER) {
- mach_port_t puc;
-
- if (vproc_mig_lookup_per_user_context(bp, 0, &puc) == 0) {
- kr = vproc_mig_create_server(puc, server_cmd, server_uid, on_demand, server_port);
- mach_port_deallocate(mach_task_self(), puc);
- }
- }
-
- return kr;
-}
-
-kern_return_t
-bootstrap_subset(mach_port_t bp, mach_port_t requestor_port, mach_port_t *subset_port)
-{
- return vproc_mig_subset(bp, requestor_port, subset_port);
-}
-
-kern_return_t
-bootstrap_unprivileged(mach_port_t bp, mach_port_t *unpriv_port)
-{
- kern_return_t kr;
-
- *unpriv_port = MACH_PORT_NULL;
-
- kr = mach_port_mod_refs(mach_task_self(), bp, MACH_PORT_RIGHT_SEND, 1);
-
- if (kr == KERN_SUCCESS) {
- *unpriv_port = bp;
- }
-
- return kr;
-}
-
-kern_return_t
-bootstrap_parent(mach_port_t bp, mach_port_t *parent_port)
-{
- return vproc_mig_parent(bp, parent_port);
-}
-
-kern_return_t
-bootstrap_register(mach_port_t bp, name_t service_name, mach_port_t sp)
-{
- return bootstrap_register2(bp, service_name, sp, 0);
-}
-
-kern_return_t
-bootstrap_register2(mach_port_t bp, name_t service_name, mach_port_t sp, uint64_t flags)
-{
- kern_return_t kr = vproc_mig_register2(bp, service_name, sp, flags);
-
- if (kr == VPROC_ERR_TRY_PER_USER) {
- mach_port_t puc;
-
- if (vproc_mig_lookup_per_user_context(bp, 0, &puc) == 0) {
- kr = vproc_mig_register2(puc, service_name, sp, flags);
- mach_port_deallocate(mach_task_self(), puc);
- }
- }
-
- return kr;
-}
-
-kern_return_t
-bootstrap_create_service(mach_port_t bp, name_t service_name, mach_port_t *sp)
-{
- kern_return_t kr;
-
- if ((kr = bootstrap_check_in(bp, service_name, sp))) {
- return kr;
- }
-
- if ((kr = mach_port_mod_refs(mach_task_self(), *sp, MACH_PORT_RIGHT_RECEIVE, -1))) {
- return kr;
- }
-
- return bootstrap_look_up(bp, service_name, sp);
-}
-
-kern_return_t
-bootstrap_check_in(mach_port_t bp, const name_t service_name, mach_port_t *sp)
-{
- uuid_t junk;
- return vproc_mig_check_in2(bp, (char *)service_name, sp, junk, 0);
-}
-
-kern_return_t
-bootstrap_check_in2(mach_port_t bp, const name_t service_name, mach_port_t *sp, uint64_t flags)
-{
- uuid_t junk;
- return vproc_mig_check_in2(bp, (char *)service_name, sp, junk, flags);
-}
-
-kern_return_t
-bootstrap_look_up_per_user(mach_port_t bp, const name_t service_name, uid_t target_user, mach_port_t *sp)
-{
- audit_token_t au_tok;
- kern_return_t kr;
- mach_port_t puc;
-
- /* See rdar://problem/4890134. */
-
- if ((kr = vproc_mig_lookup_per_user_context(bp, target_user, &puc)) != 0) {
- return kr;
- }
-
- if (!service_name) {
- *sp = puc;
- } else {
- uuid_t junk;
- kr = vproc_mig_look_up2(puc, (char *)service_name, sp, &au_tok, 0, junk, 0);
- mach_port_deallocate(mach_task_self(), puc);
- }
-
- return kr;
-}
-
-kern_return_t
-bootstrap_lookup_children(mach_port_t bp, mach_port_array_t *children, name_array_t *names, bootstrap_property_array_t *properties, mach_msg_type_number_t *n_children)
-{
- mach_msg_type_number_t junk = 0;
- return vproc_mig_lookup_children(bp, children, &junk, names, n_children, properties, &junk);
-}
-
-kern_return_t
-bootstrap_look_up(mach_port_t bp, const name_t service_name, mach_port_t *sp)
-{
- return bootstrap_look_up2(bp, service_name, sp, 0, 0);
-}
-
-kern_return_t
-bootstrap_look_up2(mach_port_t bp, const name_t service_name, mach_port_t *sp, pid_t target_pid, uint64_t flags)
-{
- uuid_t instance_id;
- return bootstrap_look_up3(bp, service_name, sp, target_pid, instance_id, flags);
-}
-
-kern_return_t
-bootstrap_look_up3(mach_port_t bp, const name_t service_name, mach_port_t *sp, pid_t target_pid, const uuid_t instance_id, uint64_t flags)
-{
- audit_token_t au_tok;
- bool privileged_server_lookup = flags & BOOTSTRAP_PRIVILEGED_SERVER;
- kern_return_t kr = 0;
- mach_port_t puc;
-
- // We have to cast instance_id here because the MIG-generated method
- // doesn't expect a const parameter.
- if ((kr = vproc_mig_look_up2(bp, (char *)service_name, sp, &au_tok, target_pid, (unsigned char*)instance_id, flags)) != VPROC_ERR_TRY_PER_USER) {
- goto out;
- }
-
- if ((kr = vproc_mig_lookup_per_user_context(bp, 0, &puc)) != 0) {
- goto out;
- }
-
- kr = vproc_mig_look_up2(puc, (char *)service_name, sp, &au_tok, target_pid, (unsigned char*)instance_id, flags);
- mach_port_deallocate(mach_task_self(), puc);
-
-out:
- if ((kr == 0) && privileged_server_lookup) {
- uid_t server_euid;
-
- /*
- * The audit token magic is dependent on the per-user launchd
- * forwarding MIG requests to the root launchd when it cannot
- * find the answer locally.
- */
-
- /* This API should be in Libsystem, but is not */
- //audit_token_to_au32(au_tok, NULL, &server_euid, NULL, NULL, NULL, NULL, NULL, NULL);
-
- server_euid = au_tok.val[1];
-
- if (server_euid) {
- mach_port_deallocate(mach_task_self(), *sp);
- *sp = MACH_PORT_NULL;
- kr = BOOTSTRAP_NOT_PRIVILEGED;
- }
- }
-
- return kr;
-}
-
-kern_return_t
-bootstrap_check_in3(mach_port_t bp, const name_t service_name, mach_port_t *sp, uuid_t instance_id, uint64_t flags)
-{
- return vproc_mig_check_in2(bp, (char *)service_name, sp, instance_id, flags);
-}
-
-kern_return_t
-bootstrap_get_root(mach_port_t bp, mach_port_t *root)
-{
- return vproc_mig_get_root_bootstrap(bp, root);
-}
-
-kern_return_t
-bootstrap_status(mach_port_t bp, name_t service_name, bootstrap_status_t *service_active)
-{
- kern_return_t kr;
- mach_port_t p;
-
- if ((kr = bootstrap_look_up(bp, service_name, &p))) {
- return kr;
- }
-
- mach_port_deallocate(mach_task_self(), p);
- *service_active = BOOTSTRAP_STATUS_ACTIVE;
-
- if (bootstrap_check_in(bp, service_name, &p) == BOOTSTRAP_SUCCESS) {
- mach_port_mod_refs(mach_task_self(), p, MACH_PORT_RIGHT_RECEIVE, -1);
- *service_active = BOOTSTRAP_STATUS_ON_DEMAND;
- }
-
- return BOOTSTRAP_SUCCESS;
-}
-
-kern_return_t
-bootstrap_info(mach_port_t bp,
- name_array_t *service_names, mach_msg_type_number_t *service_namesCnt,
- name_array_t *service_jobs, mach_msg_type_number_t *service_jobsCnt,
- bootstrap_status_array_t *service_active, mach_msg_type_number_t *service_activeCnt,
- uint64_t flags)
-{
- return vproc_mig_info(bp, service_names, service_namesCnt, service_jobs, service_jobsCnt, service_active, service_activeCnt, flags);
-}
-
-const char *
-bootstrap_strerror(kern_return_t r)
-{
- switch (r) {
- case BOOTSTRAP_SUCCESS:
- return "Success";
- case BOOTSTRAP_NOT_PRIVILEGED:
- return "Permission denied";
- case BOOTSTRAP_NAME_IN_USE:
- case BOOTSTRAP_SERVICE_ACTIVE:
- return "Service name already exists";
- case BOOTSTRAP_UNKNOWN_SERVICE:
- return "Unknown service name";
- case BOOTSTRAP_BAD_COUNT:
- return "Too many lookups were requested in one request";
- case BOOTSTRAP_NO_MEMORY:
- return "Out of memory";
- default:
- return mach_error_string(r);
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2005 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 "config.h"
-#include "launch.h"
-#include "launch_priv.h"
-#include "launch_internal.h"
-#include "launchd_ktrace.h"
-
-#include <mach/mach.h>
-#include <libkern/OSByteOrder.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/fcntl.h>
-#include <sys/un.h>
-#include <sys/uio.h>
-#include <sys/stat.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <pthread.h>
-#include <unistd.h>
-#include <errno.h>
-#include <pwd.h>
-#include <assert.h>
-#include <uuid/uuid.h>
-#include <sys/syscall.h>
-#include <dlfcn.h>
-
-#ifdef __LP64__
-/* workaround: 5723161 */
-#ifndef __DARWIN_ALIGN32
-#define __DARWIN_ALIGN32(x) (((size_t)(x) + 3) & ~3)
-#endif
-#undef CMSG_DATA
-#define CMSG_DATA(cmsg) \
- ((uint8_t *)(cmsg) + __DARWIN_ALIGN32(sizeof(struct cmsghdr)))
-#undef CMSG_SPACE
-#define CMSG_SPACE(l) \
- (__DARWIN_ALIGN32(sizeof(struct cmsghdr)) + __DARWIN_ALIGN32(l))
-#undef CMSG_LEN
-#define CMSG_LEN(l) \
- (__DARWIN_ALIGN32(sizeof(struct cmsghdr)) + (l))
-#endif
-
-#include "bootstrap.h"
-#include "vproc.h"
-#include "vproc_priv.h"
-#include "vproc_internal.h"
-
-/* __OSBogusByteSwap__() must not really exist in the symbol namespace
- * in order for the following to generate an error at build time.
- */
-extern void __OSBogusByteSwap__(void);
-
-#define host2wire(x) \
- ({ typeof (x) _X, _x = (x); \
- switch (sizeof(_x)) { \
- case 8: \
- _X = OSSwapHostToLittleInt64(_x); \
- break; \
- case 4: \
- _X = OSSwapHostToLittleInt32(_x); \
- break; \
- case 2: \
- _X = OSSwapHostToLittleInt16(_x); \
- break; \
- case 1: \
- _X = _x; \
- break; \
- default: \
- __OSBogusByteSwap__(); \
- break; \
- } \
- _X; \
- })
-
-
-#define big2wire(x) \
- ({ typeof (x) _X, _x = (x); \
- switch (sizeof(_x)) { \
- case 8: \
- _X = OSSwapLittleToHostInt64(_x); \
- break; \
- case 4: \
- _X = OSSwapLittleToHostInt32(_x); \
- break; \
- case 2: \
- _X = OSSwapLittleToHostInt16(_x); \
- break; \
- case 1: \
- _X = _x; \
- break; \
- default: \
- __OSBogusByteSwap__(); \
- break; \
- } \
- _X; \
- })
-
-
-struct launch_msg_header {
- uint64_t magic;
- uint64_t len;
-};
-
-#define LAUNCH_MSG_HEADER_MAGIC 0xD2FEA02366B39A41ull
-
-enum {
- LAUNCHD_USE_CHECKIN_FD,
- LAUNCHD_USE_OTHER_FD,
-};
-struct _launch {
- void *sendbuf;
- int *sendfds;
- void *recvbuf;
- int *recvfds;
- size_t sendlen;
- size_t sendfdcnt;
- size_t recvlen;
- size_t recvfdcnt;
- int which;
- int cifd;
- int fd;
-};
-
-static launch_data_t launch_data_array_pop_first(launch_data_t where);
-static int _fd(int fd);
-static void launch_client_init(void);
-static void launch_msg_getmsgs(launch_data_t m, void *context);
-static launch_data_t launch_msg_internal(launch_data_t d);
-static void launch_mach_checkin_service(launch_data_t obj, const char *key, void *context);
-
-static int64_t s_am_embedded_god = false;
-static launch_t in_flight_msg_recv_client;
-static pthread_once_t _lc_once = PTHREAD_ONCE_INIT;
-
-bool do_apple_internal_logging = false;
-
-static struct _launch_client {
- pthread_mutex_t mtx;
- launch_t l;
- launch_data_t async_resp;
-} *_lc = NULL;
-
-void
-launch_client_init(void)
-{
- struct sockaddr_un sun;
- char *where = getenv(LAUNCHD_SOCKET_ENV);
- char *_launchd_fd = getenv(LAUNCHD_TRUSTED_FD_ENV);
- int dfd, lfd = -1, cifd = -1;
- name_t spath;
-
- _lc = calloc(1, sizeof(struct _launch_client));
-
- if (!_lc)
- return;
-
- pthread_mutex_init(&_lc->mtx, NULL);
-
- if (_launchd_fd) {
- cifd = strtol(_launchd_fd, NULL, 10);
- if ((dfd = dup(cifd)) >= 0) {
- close(dfd);
- _fd(cifd);
- } else {
- cifd = -1;
- }
- unsetenv(LAUNCHD_TRUSTED_FD_ENV);
- }
-
- memset(&sun, 0, sizeof(sun));
- sun.sun_family = AF_UNIX;
-
- /* The rules are as follows.
- * - All users (including root) talk to their per-user launchd's by default.
- * - If we have been invoked under sudo, talk to the system launchd.
- * - If we're the root user and the __USE_SYSTEM_LAUNCHD environment variable is set, then
- * talk to the system launchd.
- */
- if (where && where[0] != '\0') {
- strncpy(sun.sun_path, where, sizeof(sun.sun_path));
- } else {
- if (_vprocmgr_getsocket(spath) == 0) {
- if ((getenv("SUDO_COMMAND") || getenv("__USE_SYSTEM_LAUNCHD")) && geteuid() == 0) {
- /* Talk to the system launchd. */
- strncpy(sun.sun_path, LAUNCHD_SOCK_PREFIX "/sock", sizeof(sun.sun_path));
- } else {
- /* Talk to our per-user launchd. */
- size_t min_len;
-
- min_len = sizeof(sun.sun_path) < sizeof(spath) ? sizeof(sun.sun_path) : sizeof(spath);
-
- strncpy(sun.sun_path, spath, min_len);
- }
- }
- }
-
- if ((lfd = _fd(socket(AF_UNIX, SOCK_STREAM, 0))) == -1) {
- goto out_bad;
- }
-
-#if TARGET_OS_EMBEDDED
- (void)vproc_swap_integer(NULL, VPROC_GSK_EMBEDDEDROOTEQUIVALENT, NULL, &s_am_embedded_god);
-#endif
- if (-1 == connect(lfd, (struct sockaddr *)&sun, sizeof(sun))) {
- if (cifd != -1 || s_am_embedded_god) {
- /* There is NO security enforced by this check. This is just a hint to our
- * library that we shouldn't error out due to failing to open this socket. If
- * we inherited a trusted file descriptor, we shouldn't fail. This should be
- * adequate for clients' expectations.
- */
- close(lfd);
- lfd = -1;
- } else {
- goto out_bad;
- }
- }
-
- if (!(_lc->l = launchd_fdopen(lfd, cifd))) {
- goto out_bad;
- }
-
- if (!(_lc->async_resp = launch_data_alloc(LAUNCH_DATA_ARRAY))) {
- goto out_bad;
- }
-
- return;
-out_bad:
- if (_lc->l)
- launchd_close(_lc->l, close);
- else if (lfd != -1)
- close(lfd);
- if (cifd != -1) {
- close(cifd);
- }
- if (_lc)
- free(_lc);
- _lc = NULL;
-}
-
-launch_data_t
-launch_data_alloc(launch_data_type_t t)
-{
- launch_data_t d = calloc(1, sizeof(struct _launch_data));
-
- if (d) {
- d->type = t;
- switch (t) {
- case LAUNCH_DATA_DICTIONARY:
- case LAUNCH_DATA_ARRAY:
- d->_array = malloc(0);
- break;
- case LAUNCH_DATA_OPAQUE:
- d->opaque = malloc(0);
- default:
- break;
- }
- }
-
- return d;
-}
-
-launch_data_type_t
-launch_data_get_type(launch_data_t d)
-{
- return d->type;
-}
-
-void
-launch_data_free(launch_data_t d)
-{
- size_t i;
-
- switch (d->type) {
- case LAUNCH_DATA_DICTIONARY:
- case LAUNCH_DATA_ARRAY:
- for (i = 0; i < d->_array_cnt; i++) {
- if (d->_array[i]) {
- launch_data_free(d->_array[i]);
- }
- }
- free(d->_array);
- break;
- case LAUNCH_DATA_STRING:
- if (d->string)
- free(d->string);
- break;
- case LAUNCH_DATA_OPAQUE:
- if (d->opaque)
- free(d->opaque);
- break;
- default:
- break;
- }
- free(d);
-}
-
-size_t
-launch_data_dict_get_count(launch_data_t dict)
-{
- return dict->_array_cnt / 2;
-}
-
-bool
-launch_data_dict_insert(launch_data_t dict, launch_data_t what, const char *key)
-{
- size_t i;
- launch_data_t thekey = launch_data_alloc(LAUNCH_DATA_STRING);
-
- launch_data_set_string(thekey, key);
-
- for (i = 0; i < dict->_array_cnt; i += 2) {
- if (!strcasecmp(key, dict->_array[i]->string)) {
- launch_data_array_set_index(dict, thekey, i);
- launch_data_array_set_index(dict, what, i + 1);
- return true;
- }
- }
- launch_data_array_set_index(dict, thekey, i);
- launch_data_array_set_index(dict, what, i + 1);
- return true;
-}
-
-launch_data_t
-launch_data_dict_lookup(launch_data_t dict, const char *key)
-{
- size_t i;
-
- if (LAUNCH_DATA_DICTIONARY != dict->type)
- return NULL;
-
- for (i = 0; i < dict->_array_cnt; i += 2) {
- if (!strcasecmp(key, dict->_array[i]->string))
- return dict->_array[i + 1];
- }
-
- return NULL;
-}
-
-bool
-launch_data_dict_remove(launch_data_t dict, const char *key)
-{
- size_t i;
-
- for (i = 0; i < dict->_array_cnt; i += 2) {
- if (!strcasecmp(key, dict->_array[i]->string))
- break;
- }
- if (i == dict->_array_cnt)
- return false;
- launch_data_free(dict->_array[i]);
- launch_data_free(dict->_array[i + 1]);
- memmove(dict->_array + i, dict->_array + i + 2, (dict->_array_cnt - (i + 2)) * sizeof(launch_data_t));
- dict->_array_cnt -= 2;
- return true;
-}
-
-void
-launch_data_dict_iterate(launch_data_t dict, void (*cb)(launch_data_t, const char *, void *), void *context)
-{
- size_t i;
-
- if (LAUNCH_DATA_DICTIONARY != dict->type) {
- return;
- }
-
- for (i = 0; i < dict->_array_cnt; i += 2) {
- cb(dict->_array[i + 1], dict->_array[i]->string, context);
- }
-}
-
-bool
-launch_data_array_set_index(launch_data_t where, launch_data_t what, size_t ind)
-{
- if ((ind + 1) >= where->_array_cnt) {
- where->_array = reallocf(where->_array, (ind + 1) * sizeof(launch_data_t));
- memset(where->_array + where->_array_cnt, 0, (ind + 1 - where->_array_cnt) * sizeof(launch_data_t));
- where->_array_cnt = ind + 1;
- }
-
- if (where->_array[ind]) {
- launch_data_free(where->_array[ind]);
- }
-
- where->_array[ind] = what;
- return true;
-}
-
-launch_data_t
-launch_data_array_get_index(launch_data_t where, size_t ind)
-{
- if (LAUNCH_DATA_ARRAY != where->type || ind >= where->_array_cnt) {
- return NULL;
- } else {
- return where->_array[ind];
- }
-}
-
-launch_data_t
-launch_data_array_pop_first(launch_data_t where)
-{
- launch_data_t r = NULL;
-
- if (where->_array_cnt > 0) {
- r = where->_array[0];
- memmove(where->_array, where->_array + 1, (where->_array_cnt - 1) * sizeof(launch_data_t));
- where->_array_cnt--;
- }
- return r;
-}
-
-size_t
-launch_data_array_get_count(launch_data_t where)
-{
- if (LAUNCH_DATA_ARRAY != where->type)
- return 0;
- return where->_array_cnt;
-}
-
-bool
-launch_data_set_errno(launch_data_t d, int e)
-{
- d->err = e;
- return true;
-}
-
-bool
-launch_data_set_fd(launch_data_t d, int fd)
-{
- d->fd = fd;
- return true;
-}
-
-bool
-launch_data_set_machport(launch_data_t d, mach_port_t p)
-{
- d->mp = p;
- return true;
-}
-
-bool
-launch_data_set_integer(launch_data_t d, long long n)
-{
- d->number = n;
- return true;
-}
-
-bool
-launch_data_set_bool(launch_data_t d, bool b)
-{
- d->boolean = b;
- return true;
-}
-
-bool
-launch_data_set_real(launch_data_t d, double n)
-{
- d->float_num = n;
- return true;
-}
-
-bool
-launch_data_set_string(launch_data_t d, const char *s)
-{
- if (d->string)
- free(d->string);
- d->string = strdup(s);
- if (d->string) {
- d->string_len = strlen(d->string);
- return true;
- }
- return false;
-}
-
-bool
-launch_data_set_opaque(launch_data_t d, const void *o, size_t os)
-{
- d->opaque_size = os;
- if (d->opaque)
- free(d->opaque);
- d->opaque = malloc(os);
- if (d->opaque) {
- memcpy(d->opaque, o, os);
- return true;
- }
- return false;
-}
-
-int
-launch_data_get_errno(launch_data_t d)
-{
- return d->err;
-}
-
-int
-launch_data_get_fd(launch_data_t d)
-{
- return d->fd;
-}
-
-mach_port_t
-launch_data_get_machport(launch_data_t d)
-{
- return d->mp;
-}
-
-long long
-launch_data_get_integer(launch_data_t d)
-{
- return d->number;
-}
-
-bool
-launch_data_get_bool(launch_data_t d)
-{
- return d->boolean;
-}
-
-double
-launch_data_get_real(launch_data_t d)
-{
- return d->float_num;
-}
-
-const char *
-launch_data_get_string(launch_data_t d)
-{
- if (LAUNCH_DATA_STRING != d->type)
- return NULL;
- return d->string;
-}
-
-void *
-launch_data_get_opaque(launch_data_t d)
-{
- if (LAUNCH_DATA_OPAQUE != d->type)
- return NULL;
- return d->opaque;
-}
-
-size_t
-launch_data_get_opaque_size(launch_data_t d)
-{
- return d->opaque_size;
-}
-
-int
-launchd_getfd(launch_t l)
-{
- return ( l->which == LAUNCHD_USE_CHECKIN_FD ) ? l->cifd : l->fd;
-}
-
-launch_t
-launchd_fdopen(int fd, int cifd)
-{
- launch_t c;
-
- c = calloc(1, sizeof(struct _launch));
- if (!c)
- return NULL;
-
- c->fd = fd;
- c->cifd = cifd;
-
- if (c->fd == -1 || (c->fd != -1 && c->cifd != -1)) {
- c->which = LAUNCHD_USE_CHECKIN_FD;
- } else if (c->cifd == -1) {
- c->which = LAUNCHD_USE_OTHER_FD;
- }
-
- fcntl(fd, F_SETFL, O_NONBLOCK);
- fcntl(cifd, F_SETFL, O_NONBLOCK);
-
- if ((c->sendbuf = malloc(0)) == NULL)
- goto out_bad;
- if ((c->sendfds = malloc(0)) == NULL)
- goto out_bad;
- if ((c->recvbuf = malloc(0)) == NULL)
- goto out_bad;
- if ((c->recvfds = malloc(0)) == NULL)
- goto out_bad;
-
- return c;
-
-out_bad:
- if (c->sendbuf)
- free(c->sendbuf);
- if (c->sendfds)
- free(c->sendfds);
- if (c->recvbuf)
- free(c->recvbuf);
- if (c->recvfds)
- free(c->recvfds);
- free(c);
- return NULL;
-}
-
-void
-launchd_close(launch_t lh, typeof(close) closefunc)
-{
- if (in_flight_msg_recv_client == lh) {
- in_flight_msg_recv_client = NULL;
- }
-
- if (lh->sendbuf)
- free(lh->sendbuf);
- if (lh->sendfds)
- free(lh->sendfds);
- if (lh->recvbuf)
- free(lh->recvbuf);
- if (lh->recvfds)
- free(lh->recvfds);
- closefunc(lh->fd);
- closefunc(lh->cifd);
- free(lh);
-}
-
-#define ROUND_TO_64BIT_WORD_SIZE(x) ((x + 7) & ~7)
-
-size_t
-launch_data_pack(launch_data_t d, void *where, size_t len, int *fd_where, size_t *fd_cnt)
-{
- launch_data_t o_in_w = where;
- size_t i, rsz, node_data_len = sizeof(struct _launch_data);
-
- if (node_data_len > len) {
- return 0;
- }
-
- where += node_data_len;
-
- o_in_w->type = host2wire(d->type);
-
- size_t pad_len = 0;
- switch (d->type) {
- case LAUNCH_DATA_INTEGER:
- o_in_w->number = host2wire(d->number);
- break;
- case LAUNCH_DATA_REAL:
- o_in_w->float_num = host2wire(d->float_num);
- break;
- case LAUNCH_DATA_BOOL:
- o_in_w->boolean = host2wire(d->boolean);
- break;
- case LAUNCH_DATA_ERRNO:
- o_in_w->err = host2wire(d->err);
- break;
- case LAUNCH_DATA_FD:
- o_in_w->fd = host2wire(d->fd);
- if (fd_where && d->fd != -1) {
- fd_where[*fd_cnt] = d->fd;
- (*fd_cnt)++;
- }
- break;
- case LAUNCH_DATA_STRING:
- o_in_w->string_len = host2wire(d->string_len);
- node_data_len += ROUND_TO_64BIT_WORD_SIZE(d->string_len + 1);
-
- if (node_data_len > len) {
- return 0;
- }
- memcpy(where, d->string, d->string_len + 1);
-
- /* Zero padded data. */
- pad_len = ROUND_TO_64BIT_WORD_SIZE(d->string_len + 1) - (d->string_len + 1);
- bzero(where + d->string_len + 1, pad_len);
-
- break;
- case LAUNCH_DATA_OPAQUE:
- o_in_w->opaque_size = host2wire(d->opaque_size);
- node_data_len += ROUND_TO_64BIT_WORD_SIZE(d->opaque_size);
- if (node_data_len > len) {
- return 0;
- }
- memcpy(where, d->opaque, d->opaque_size);
-
- /* Zero padded data. */
- pad_len = ROUND_TO_64BIT_WORD_SIZE(d->opaque_size) - d->opaque_size;
- bzero(where + d->opaque_size, pad_len);
-
- break;
- case LAUNCH_DATA_DICTIONARY:
- case LAUNCH_DATA_ARRAY:
- o_in_w->_array_cnt = host2wire(d->_array_cnt);
- node_data_len += d->_array_cnt * sizeof(uint64_t);
- if (node_data_len > len) {
- return 0;
- }
-
- where += d->_array_cnt * sizeof(uint64_t);
-
- for (i = 0; i < d->_array_cnt; i++) {
- rsz = launch_data_pack(d->_array[i], where, len - node_data_len, fd_where, fd_cnt);
- if (rsz == 0) {
- return 0;
- }
- where += rsz;
- node_data_len += rsz;
- }
- break;
- default:
- break;
- }
-
- return node_data_len;
-}
-
-launch_data_t
-launch_data_unpack(void *data, size_t data_size, int *fds, size_t fd_cnt, size_t *data_offset, size_t *fdoffset)
-{
- launch_data_t r = data + *data_offset;
- size_t i, tmpcnt;
-
- if ((data_size - *data_offset) < sizeof(struct _launch_data))
- return NULL;
- *data_offset += sizeof(struct _launch_data);
-
- switch (big2wire(r->type)) {
- case LAUNCH_DATA_DICTIONARY:
- case LAUNCH_DATA_ARRAY:
- tmpcnt = big2wire(r->_array_cnt);
- if ((data_size - *data_offset) < (tmpcnt * sizeof(uint64_t))) {
- errno = EAGAIN;
- return NULL;
- }
- r->_array = data + *data_offset;
- *data_offset += tmpcnt * sizeof(uint64_t);
- for (i = 0; i < tmpcnt; i++) {
- r->_array[i] = launch_data_unpack(data, data_size, fds, fd_cnt, data_offset, fdoffset);
- if (r->_array[i] == NULL)
- return NULL;
- }
- r->_array_cnt = tmpcnt;
- break;
- case LAUNCH_DATA_STRING:
- tmpcnt = big2wire(r->string_len);
- if ((data_size - *data_offset) < (tmpcnt + 1)) {
- errno = EAGAIN;
- return NULL;
- }
- r->string = data + *data_offset;
- r->string_len = tmpcnt;
- *data_offset += ROUND_TO_64BIT_WORD_SIZE(tmpcnt + 1);
- break;
- case LAUNCH_DATA_OPAQUE:
- tmpcnt = big2wire(r->opaque_size);
- if ((data_size - *data_offset) < tmpcnt) {
- errno = EAGAIN;
- return NULL;
- }
- r->opaque = data + *data_offset;
- r->opaque_size = tmpcnt;
- *data_offset += ROUND_TO_64BIT_WORD_SIZE(tmpcnt);
- break;
- case LAUNCH_DATA_FD:
- if (r->fd != -1 && fd_cnt > *fdoffset) {
- r->fd = _fd(fds[*fdoffset]);
- *fdoffset += 1;
- }
- break;
- case LAUNCH_DATA_INTEGER:
- r->number = big2wire(r->number);
- break;
- case LAUNCH_DATA_REAL:
- r->float_num = big2wire(r->float_num);
- break;
- case LAUNCH_DATA_BOOL:
- r->boolean = big2wire(r->boolean);
- break;
- case LAUNCH_DATA_ERRNO:
- r->err = big2wire(r->err);
- case LAUNCH_DATA_MACHPORT:
- break;
- default:
- errno = EINVAL;
- return NULL;
- break;
- }
-
- r->type = big2wire(r->type);
-
- return r;
-}
-
-int
-launchd_msg_send(launch_t lh, launch_data_t d)
-{
- struct launch_msg_header lmh;
- struct cmsghdr *cm = NULL;
- struct msghdr mh;
- struct iovec iov[2];
- size_t sentctrllen = 0;
- int r;
-
- int fd2use = launchd_getfd(lh);
- if (fd2use == -1) {
- errno = EPERM;
- return -1;
- }
-
- memset(&mh, 0, sizeof(mh));
-
- /* confirm that the next hack works */
- assert((d && lh->sendlen == 0) || (!d && lh->sendlen));
-
- if (d) {
- size_t fd_slots_used = 0;
- size_t good_enough_size = 10 * 1024 * 1024;
- uint64_t msglen;
-
- /* hack, see the above assert to verify "correctness" */
- free(lh->sendbuf);
- lh->sendbuf = malloc(good_enough_size);
- if (!lh->sendbuf) {
- errno = ENOMEM;
- return -1;
- }
-
- free(lh->sendfds);
- lh->sendfds = malloc(4 * 1024);
- if (!lh->sendfds) {
- free(lh->sendbuf);
- lh->sendbuf = NULL;
- errno = ENOMEM;
- return -1;
- }
-
- lh->sendlen = launch_data_pack(d, lh->sendbuf, good_enough_size, lh->sendfds, &fd_slots_used);
-
- if (lh->sendlen == 0) {
- errno = ENOMEM;
- return -1;
- }
-
- lh->sendfdcnt = fd_slots_used;
-
- msglen = lh->sendlen + sizeof(struct launch_msg_header); /* type promotion to make the host2wire() macro work right */
- lmh.len = host2wire(msglen);
- lmh.magic = host2wire(LAUNCH_MSG_HEADER_MAGIC);
-
- iov[0].iov_base = &lmh;
- iov[0].iov_len = sizeof(lmh);
- mh.msg_iov = iov;
- mh.msg_iovlen = 2;
- } else {
- mh.msg_iov = iov + 1;
- mh.msg_iovlen = 1;
- }
-
- iov[1].iov_base = lh->sendbuf;
- iov[1].iov_len = lh->sendlen;
-
-
- if (lh->sendfdcnt > 0) {
- sentctrllen = mh.msg_controllen = CMSG_SPACE(lh->sendfdcnt * sizeof(int));
- cm = alloca(mh.msg_controllen);
- mh.msg_control = cm;
-
- memset(cm, 0, mh.msg_controllen);
-
- cm->cmsg_len = CMSG_LEN(lh->sendfdcnt * sizeof(int));
- cm->cmsg_level = SOL_SOCKET;
- cm->cmsg_type = SCM_RIGHTS;
-
- memcpy(CMSG_DATA(cm), lh->sendfds, lh->sendfdcnt * sizeof(int));
- }
-
- if ((r = sendmsg(fd2use, &mh, 0)) == -1) {
- return -1;
- } else if (r == 0) {
- errno = ECONNRESET;
- return -1;
- } else if (sentctrllen != mh.msg_controllen) {
- errno = ECONNRESET;
- return -1;
- }
-
- if (d) {
- r -= sizeof(struct launch_msg_header);
- }
-
- lh->sendlen -= r;
- if (lh->sendlen > 0) {
- memmove(lh->sendbuf, lh->sendbuf + r, lh->sendlen);
- } else {
- free(lh->sendbuf);
- lh->sendbuf = malloc(0);
- }
-
- lh->sendfdcnt = 0;
- free(lh->sendfds);
- lh->sendfds = malloc(0);
-
- if (lh->sendlen > 0) {
- errno = EAGAIN;
- return -1;
- }
-
- return 0;
-}
-
-int
-launch_get_fd(void)
-{
- pthread_once(&_lc_once, launch_client_init);
-
- if (!_lc) {
- errno = ENOTCONN;
- return -1;
- }
-
- return _lc->l->fd;
-}
-
-void
-launch_msg_getmsgs(launch_data_t m, void *context)
-{
- launch_data_t async_resp, *sync_resp = context;
-
- if ((LAUNCH_DATA_DICTIONARY == launch_data_get_type(m)) && (async_resp = launch_data_dict_lookup(m, LAUNCHD_ASYNC_MSG_KEY))) {
- launch_data_array_set_index(_lc->async_resp, launch_data_copy(async_resp), launch_data_array_get_count(_lc->async_resp));
- } else {
- *sync_resp = launch_data_copy(m);
- }
-}
-
-void
-launch_mach_checkin_service(launch_data_t obj, const char *key, void *context __attribute__((unused)))
-{
- kern_return_t result;
- mach_port_t p;
- name_t srvnm;
-
- strlcpy(srvnm, key, sizeof(srvnm));
-
- result = bootstrap_check_in(bootstrap_port, srvnm, &p);
-
- if (result == BOOTSTRAP_SUCCESS)
- launch_data_set_machport(obj, p);
-}
-
-launch_data_t
-launch_msg(launch_data_t d)
-{
- launch_data_t mps, r = launch_msg_internal(d);
-
- if (launch_data_get_type(d) == LAUNCH_DATA_STRING) {
- if (strcmp(launch_data_get_string(d), LAUNCH_KEY_CHECKIN) != 0)
- return r;
- if (r == NULL)
- return r;
- if (launch_data_get_type(r) != LAUNCH_DATA_DICTIONARY)
- return r;
- mps = launch_data_dict_lookup(r, LAUNCH_JOBKEY_MACHSERVICES);
- if (mps == NULL)
- return r;
- launch_data_dict_iterate(mps, launch_mach_checkin_service, NULL);
- }
-
- return r;
-}
-
-extern kern_return_t vproc_mig_set_security_session(mach_port_t, uuid_t, mach_port_t);
-
-static inline bool
-uuid_data_is_null(launch_data_t d)
-{
- bool result = false;
- if (launch_data_get_type(d) == LAUNCH_DATA_OPAQUE && launch_data_get_opaque_size(d) == sizeof(uuid_t)) {
- uuid_t existing_uuid;
- memcpy(existing_uuid, launch_data_get_opaque(d), sizeof(uuid_t));
-
- /* A NULL UUID tells us to keep the session inherited from the parent. */
- result = (bool)uuid_is_null(existing_uuid);
- }
-
- return result;
-}
-
-launch_data_t
-launch_msg_internal(launch_data_t d)
-{
- launch_data_t resp = NULL;
-
- if (d && (launch_data_get_type(d) == LAUNCH_DATA_STRING)
- && (strcmp(launch_data_get_string(d), LAUNCH_KEY_GETJOBS) == 0)
- && vproc_swap_complex(NULL, VPROC_GSK_ALLJOBS, NULL, &resp) == NULL) {
- return resp;
- }
-
- pthread_once(&_lc_once, launch_client_init);
- if (!_lc) {
- errno = ENOTCONN;
- return NULL;
- }
-
- int fd2use = -1;
- if ((launch_data_get_type(d) == LAUNCH_DATA_STRING && strcmp(launch_data_get_string(d), LAUNCH_KEY_CHECKIN) == 0) || s_am_embedded_god) {
- _lc->l->which = LAUNCHD_USE_CHECKIN_FD;
- } else {
- _lc->l->which = LAUNCHD_USE_OTHER_FD;
- }
-
- fd2use = launchd_getfd(_lc->l);
-
- if (fd2use == -1) {
- errno = EPERM;
- return NULL;
- }
-
-#if !TARGET_OS_EMBEDDED
- uuid_t uuid;
- launch_data_t uuid_d = NULL;
- size_t jobs_that_need_sessions = 0;
- if (d && launch_data_get_type(d) == LAUNCH_DATA_DICTIONARY) {
- launch_data_t v = launch_data_dict_lookup(d, LAUNCH_KEY_SUBMITJOB);
-
- if (v && launch_data_get_type(v) == LAUNCH_DATA_ARRAY) {
- size_t cnt = launch_data_array_get_count(v);
- size_t i = 0;
-
- uuid_generate(uuid);
- for (i = 0; i < cnt; i++) {
- launch_data_t ji = launch_data_array_get_index(v, i);
- if (launch_data_get_type(ji) == LAUNCH_DATA_DICTIONARY) {
- launch_data_t existing_v = launch_data_dict_lookup(ji, LAUNCH_JOBKEY_SECURITYSESSIONUUID);
- if (!existing_v) {
- /* I really wish these were reference-counted. Sigh... */
- uuid_d = launch_data_new_opaque(uuid, sizeof(uuid));
- launch_data_dict_insert(ji, uuid_d, LAUNCH_JOBKEY_SECURITYSESSIONUUID);
- jobs_that_need_sessions++;
- } else if (launch_data_get_type(existing_v) == LAUNCH_DATA_OPAQUE) {
- jobs_that_need_sessions += uuid_data_is_null(existing_v) ? 0 : 1;
- }
- }
- }
- } else if (v && launch_data_get_type(v) == LAUNCH_DATA_DICTIONARY) {
- launch_data_t existing_v = launch_data_dict_lookup(v, LAUNCH_JOBKEY_SECURITYSESSIONUUID);
- if (!existing_v) {
- uuid_generate(uuid);
- uuid_d = launch_data_new_opaque(uuid, sizeof(uuid));
- launch_data_dict_insert(v, uuid_d, LAUNCH_JOBKEY_SECURITYSESSIONUUID);
- jobs_that_need_sessions++;
- } else {
- jobs_that_need_sessions += uuid_data_is_null(existing_v) ? 0 : 1;
- }
- }
- }
-#endif
-
- pthread_mutex_lock(&_lc->mtx);
-
- if (d && launchd_msg_send(_lc->l, d) == -1) {
- do {
- if (errno != EAGAIN)
- goto out;
- } while (launchd_msg_send(_lc->l, NULL) == -1);
- }
-
- while (resp == NULL) {
- if (d == NULL && launch_data_array_get_count(_lc->async_resp) > 0) {
- resp = launch_data_array_pop_first(_lc->async_resp);
- goto out;
- }
- if (launchd_msg_recv(_lc->l, launch_msg_getmsgs, &resp) == -1) {
- if (errno != EAGAIN) {
- goto out;
- } else if (d == NULL) {
- errno = 0;
- goto out;
- } else {
- fd_set rfds;
-
- FD_ZERO(&rfds);
- FD_SET(fd2use, &rfds);
-
- select(fd2use + 1, &rfds, NULL, NULL, NULL);
- }
- }
- }
-
-out:
-#if !TARGET_OS_EMBEDDED
- if (!uuid_is_null(uuid) && resp && jobs_that_need_sessions > 0) {
- mach_port_t session_port = _audit_session_self();
- launch_data_type_t resp_type = launch_data_get_type(resp);
-
- bool set_session = false;
- if (resp_type == LAUNCH_DATA_ERRNO) {
- set_session = ( launch_data_get_errno(resp) == ENEEDAUTH );
- } else if (resp_type == LAUNCH_DATA_ARRAY) {
- set_session = true;
- }
-
- kern_return_t kr = KERN_FAILURE;
- if (set_session) {
- kr = vproc_mig_set_security_session(bootstrap_port, uuid, session_port);
- }
-
- if (kr == KERN_SUCCESS) {
- if (resp_type == LAUNCH_DATA_ERRNO) {
- launch_data_set_errno(resp, 0);
- } else {
- size_t i = 0;
- for (i = 0; i < launch_data_array_get_count(resp); i++) {
- launch_data_t ri = launch_data_array_get_index(resp, i);
-
- int recvd_err = 0;
- if (launch_data_get_type(ri) == LAUNCH_DATA_ERRNO && (recvd_err = launch_data_get_errno(ri))) {
- launch_data_set_errno(ri, recvd_err == ENEEDAUTH ? 0 : recvd_err);
- }
- }
- }
- }
-
- mach_port_deallocate(mach_task_self(), session_port);
- }
-#endif
-
- pthread_mutex_unlock(&_lc->mtx);
-
- return resp;
-}
-
-int
-launchd_msg_recv(launch_t lh, void (*cb)(launch_data_t, void *), void *context)
-{
- struct cmsghdr *cm = alloca(4096);
- launch_data_t rmsg = NULL;
- size_t data_offset, fd_offset;
- struct msghdr mh;
- struct iovec iov;
- int r;
-
- int fd2use = launchd_getfd(lh);
- if (fd2use == -1) {
- errno = EPERM;
- return -1;
- }
-
- memset(&mh, 0, sizeof(mh));
- mh.msg_iov = &iov;
- mh.msg_iovlen = 1;
-
- lh->recvbuf = reallocf(lh->recvbuf, lh->recvlen + 8*1024);
-
- iov.iov_base = lh->recvbuf + lh->recvlen;
- iov.iov_len = 8*1024;
- mh.msg_control = cm;
- mh.msg_controllen = 4096;
-
- if ((r = recvmsg(fd2use, &mh, 0)) == -1)
- return -1;
- if (r == 0) {
- errno = ECONNRESET;
- return -1;
- }
- if (mh.msg_flags & MSG_CTRUNC) {
- errno = ECONNABORTED;
- return -1;
- }
- lh->recvlen += r;
- if (mh.msg_controllen > 0) {
- lh->recvfds = reallocf(lh->recvfds, lh->recvfdcnt * sizeof(int) + mh.msg_controllen - sizeof(struct cmsghdr));
- memcpy(lh->recvfds + lh->recvfdcnt, CMSG_DATA(cm), mh.msg_controllen - sizeof(struct cmsghdr));
- lh->recvfdcnt += (mh.msg_controllen - sizeof(struct cmsghdr)) / sizeof(int);
- }
-
- r = 0;
-
- while (lh->recvlen > 0) {
- struct launch_msg_header *lmhp = lh->recvbuf;
- uint64_t tmplen;
- data_offset = sizeof(struct launch_msg_header);
- fd_offset = 0;
-
- if (lh->recvlen < sizeof(struct launch_msg_header))
- goto need_more_data;
-
- tmplen = big2wire(lmhp->len);
-
- if (big2wire(lmhp->magic) != LAUNCH_MSG_HEADER_MAGIC || tmplen <= sizeof(struct launch_msg_header)) {
- errno = EBADRPC;
- goto out_bad;
- }
-
- if (lh->recvlen < tmplen) {
- goto need_more_data;
- }
-
- if ((rmsg = launch_data_unpack(lh->recvbuf, lh->recvlen, lh->recvfds, lh->recvfdcnt, &data_offset, &fd_offset)) == NULL) {
- errno = EBADRPC;
- goto out_bad;
- }
-
- in_flight_msg_recv_client = lh;
-
- cb(rmsg, context);
-
- /* launchd and only launchd can call launchd_close() as a part of the callback */
- if (in_flight_msg_recv_client == NULL) {
- r = 0;
- break;
- }
-
- lh->recvlen -= data_offset;
- if (lh->recvlen > 0) {
- memmove(lh->recvbuf, lh->recvbuf + data_offset, lh->recvlen);
- } else {
- free(lh->recvbuf);
- lh->recvbuf = malloc(0);
- }
-
- lh->recvfdcnt -= fd_offset;
- if (lh->recvfdcnt > 0) {
- memmove(lh->recvfds, lh->recvfds + fd_offset, lh->recvfdcnt * sizeof(int));
- } else {
- free(lh->recvfds);
- lh->recvfds = malloc(0);
- }
- }
-
- return r;
-
-need_more_data:
- errno = EAGAIN;
-out_bad:
- return -1;
-}
-
-launch_data_t
-launch_data_copy(launch_data_t o)
-{
- launch_data_t r = launch_data_alloc(o->type);
- size_t i;
-
- free(r->_array);
- memcpy(r, o, sizeof(struct _launch_data));
-
- switch (o->type) {
- case LAUNCH_DATA_DICTIONARY:
- case LAUNCH_DATA_ARRAY:
- r->_array = calloc(1, o->_array_cnt * sizeof(launch_data_t));
- for (i = 0; i < o->_array_cnt; i++) {
- if (o->_array[i])
- r->_array[i] = launch_data_copy(o->_array[i]);
- }
- break;
- case LAUNCH_DATA_STRING:
- r->string = strdup(o->string);
- break;
- case LAUNCH_DATA_OPAQUE:
- r->opaque = malloc(o->opaque_size);
- memcpy(r->opaque, o->opaque, o->opaque_size);
- break;
- default:
- break;
- }
-
- return r;
-}
-
-int
-_fd(int fd)
-{
- if (fd >= 0)
- fcntl(fd, F_SETFD, 1);
- return fd;
-}
-
-launch_data_t
-launch_data_new_errno(int e)
-{
- launch_data_t r = launch_data_alloc(LAUNCH_DATA_ERRNO);
-
- if (r)
- launch_data_set_errno(r, e);
-
- return r;
-}
-
-launch_data_t
-launch_data_new_fd(int fd)
-{
- launch_data_t r = launch_data_alloc(LAUNCH_DATA_FD);
-
- if (r)
- launch_data_set_fd(r, fd);
-
- return r;
-}
-
-launch_data_t
-launch_data_new_machport(mach_port_t p)
-{
- launch_data_t r = launch_data_alloc(LAUNCH_DATA_MACHPORT);
-
- if (r)
- launch_data_set_machport(r, p);
-
- return r;
-}
-
-launch_data_t
-launch_data_new_integer(long long n)
-{
- launch_data_t r = launch_data_alloc(LAUNCH_DATA_INTEGER);
-
- if (r)
- launch_data_set_integer(r, n);
-
- return r;
-}
-
-launch_data_t
-launch_data_new_bool(bool b)
-{
- launch_data_t r = launch_data_alloc(LAUNCH_DATA_BOOL);
-
- if (r)
- launch_data_set_bool(r, b);
-
- return r;
-}
-
-launch_data_t
-launch_data_new_real(double d)
-{
- launch_data_t r = launch_data_alloc(LAUNCH_DATA_REAL);
-
- if (r)
- launch_data_set_real(r, d);
-
- return r;
-}
-
-launch_data_t
-launch_data_new_string(const char *s)
-{
- launch_data_t r = launch_data_alloc(LAUNCH_DATA_STRING);
-
- if (r == NULL)
- return NULL;
-
- if (!launch_data_set_string(r, s)) {
- launch_data_free(r);
- return NULL;
- }
-
- return r;
-}
-
-launch_data_t
-launch_data_new_opaque(const void *o, size_t os)
-{
- launch_data_t r = launch_data_alloc(LAUNCH_DATA_OPAQUE);
-
- if (r == NULL)
- return NULL;
-
- if (!launch_data_set_opaque(r, o, os)) {
- launch_data_free(r);
- return NULL;
- }
-
- return r;
-}
-
-void
-load_launchd_jobs_at_loginwindow_prompt(int flags __attribute__((unused)), ...)
-{
- _vprocmgr_init(VPROCMGR_SESSION_LOGINWINDOW);
-}
-
-pid_t
-create_and_switch_to_per_session_launchd(const char *login __attribute__((unused)), int flags, ...)
-{
- uid_t target_user = geteuid() ? geteuid() : getuid();
- if (_vprocmgr_move_subset_to_user(target_user, VPROCMGR_SESSION_AQUA, flags)) {
- return -1;
- }
-
- return 1;
-}
-
-
+++ /dev/null
-/*
- * Copyright (c) 1999-2005 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 "config.h"
-#include "vproc.h"
-#include "vproc_priv.h"
-#include "vproc_internal.h"
-
-#include <mach/mach.h>
-#include <mach/vm_map.h>
-#include <sys/param.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <unistd.h>
-#include <syslog.h>
-#include <pthread.h>
-#include <signal.h>
-#include <assert.h>
-#include <libkern/OSAtomic.h>
-#include <sys/syscall.h>
-#include <sys/event.h>
-
-#if HAVE_QUARANTINE
-#include <quarantine.h>
-#endif
-
-#include "launch.h"
-#include "launch_priv.h"
-#include "launch_internal.h"
-#include "launchd_ktrace.h"
-
-#include "protocol_vproc.h"
-
-#include "launchd_helper.h"
-#include "launchd_helperServer.h"
-
-#include "reboot2.h"
-
-#define likely(x) __builtin_expect((bool)(x), true)
-#define unlikely(x) __builtin_expect((bool)(x), false)
-
-static mach_port_t get_root_bootstrap_port(void);
-
-static int64_t cached_pid = -1;
-static struct vproc_shmem_s *vproc_shmem;
-static pthread_once_t shmem_inited = PTHREAD_ONCE_INIT;
-static uint64_t s_cached_transactions_enabled = 0;
-
-static _vproc_transaction_callout vproc_gone2zero;
-static _vproc_transaction_callout vproc_gonenonzero;
-
-static vproc_helper_recv_ping_t vprocmgr_helper_callout;
-
-struct vproc_s {
- int32_t refcount;
- mach_port_t j_port;
-};
-
-/* These functions are a total nightmare to get to through headers.
- * See rdar://problem/8223092.
- */
-typedef __darwin_mach_port_t fileport_t;
-#define FILEPORT_NULL ((fileport_t)0)
-extern int fileport_makeport(int, fileport_t *);
-extern int fileport_makefd(fileport_t);
-
-vproc_t vprocmgr_lookup_vproc(const char *label)
-{
- struct vproc_s *vp = NULL;
-
- mach_port_t mp = MACH_PORT_NULL;
- kern_return_t kr = vproc_mig_port_for_label(bootstrap_port, (char *)label, &mp);
- if (kr == BOOTSTRAP_SUCCESS) {
- vp = (struct vproc_s *)calloc(1, sizeof(struct vproc_s));
- if (vp) {
- vp->refcount = 1;
- mach_port_mod_refs(mach_task_self(), mp, MACH_PORT_RIGHT_SEND, 1);
- vp->j_port = mp;
- }
- (void)mach_port_deallocate(mach_task_self(), mp);
- }
-
- return vp;
-}
-
-vproc_t vproc_retain(vproc_t vp)
-{
- int32_t orig = OSAtomicAdd32(1, &vp->refcount) - 1;
- if (orig <= 0) {
- /* We've gone from 0 to 1, which means that this object was due to be freed. */
- _vproc_set_crash_log_message("Under-retain / over-release of vproc_t.");
- abort();
- }
-
- return vp;
-}
-
-void vproc_release(vproc_t vp)
-{
- int32_t newval = OSAtomicAdd32(-1, &vp->refcount);
- if (newval < 0) {
- /* We're in negative numbers, which is bad. */
- _vproc_set_crash_log_message("Over-release of vproc_t.");
- abort();
- } else if (newval == 0) {
- mach_port_deallocate(mach_task_self(), vp->j_port);
- free(vp);
- }
-}
-
-static void
-vproc_shmem_init(void)
-{
- vm_address_t vm_addr = 0;
- mach_port_t shmem_port;
- kern_return_t kr;
-
- kr = vproc_mig_setup_shmem(bootstrap_port, &shmem_port);
-
- //assert(kr == 0);
- if (kr) {
- /* rdar://problem/6416724
- * If we fail to set up a shared memory page, just allocate a local chunk
- * of memory. This way, processes can still introspect their own transaction
- * counts if they're being run under a debugger. Moral of the story: Debug
- * from the environment you intend to run in.
- */
- void *_vm_addr = calloc(1, sizeof(struct vproc_shmem_s));
- if (!_vm_addr) {
- return;
- }
-
- vm_addr = (vm_address_t)_vm_addr;
- } else {
- kr = vm_map(mach_task_self(), &vm_addr, getpagesize(), 0, true, shmem_port, 0, false,
- VM_PROT_READ|VM_PROT_WRITE, VM_PROT_READ|VM_PROT_WRITE, VM_INHERIT_NONE);
-
- //assert(kr == 0);
- if (kr) return;
-
- kr = mach_port_deallocate(mach_task_self(), shmem_port);
-
- //assert(kr == 0);
- }
-
- vproc_shmem = (struct vproc_shmem_s *)vm_addr;
-}
-
-static void
-vproc_client_init(void)
-{
- char *val = getenv(LAUNCHD_DO_APPLE_INTERNAL_LOGGING);
- if (val) {
- if (strncmp(val, "true", sizeof("true") - 1) == 0) {
- do_apple_internal_logging = true;
- }
- }
-
- vproc_shmem_init();
-}
-
-vproc_transaction_t
-vproc_transaction_begin(vproc_t vp __attribute__((unused)))
-{
- vproc_transaction_t vpt = (vproc_transaction_t)vproc_shmem_init; /* we need a "random" variable that is testable */
- _vproc_transaction_begin();
-
- return vpt;
-}
-
-void
-_vproc_transaction_begin(void)
-{
- if (unlikely(vproc_shmem == NULL)) {
- int po_r = pthread_once(&shmem_inited, vproc_client_init);
- if (po_r != 0 || vproc_shmem == NULL) {
- return;
- }
- }
-
- /* We need to deal with the potential race condition of trying to open a
- * transaction after launchd has marked the process for death. Consider if
- * one thread closes the last transaction after marking the process for
- * death. Then we call _exit(2). But exiting isn't instantaneous. So if some
- * other threads come in very soon after and try to open transactions, we
- * have to cut them off so that they can't begin their work. Because if they
- * can manage to get to the point of, say, parking themselves in an
- * uninterruptible wait, then the process won't exit.
- *
- * We loop here so that, if someone's calling vproc_transaction_end() at the
- * same time, we can pick up the descent to -1 if launchd has marked us for
- * death.
- */
- typeof(vproc_shmem->vp_shmem_transaction_cnt) old = 0;
- do {
- old = vproc_shmem->vp_shmem_transaction_cnt;
-
- if (unlikely(old < 0)) {
- /* No transactions should be opened after this point, so make sure
- * this thread can't proceed. We don't crash here because it could
- * be a legitimate race, as described above.
- */
- if (vproc_shmem->vp_shmem_flags & VPROC_SHMEM_EXITING) {
- _exit(0);
- } else {
- _vproc_set_crash_log_message("Unbalanced: vproc_transaction_begin()");
- }
- abort();
- } else if (old == 0 && vproc_gonenonzero) {
- vproc_gonenonzero();
- }
- } while (!__sync_bool_compare_and_swap(&vproc_shmem->vp_shmem_transaction_cnt, old, old + 1));
-
- runtime_ktrace(RTKT_VPROC_TRANSACTION_INCREMENT, old + 1, 0, 0);
-}
-
-size_t
-_vproc_transaction_count(void)
-{
- return likely(vproc_shmem) ? vproc_shmem->vp_shmem_transaction_cnt : INT32_MAX;
-}
-
-size_t
-_vproc_standby_count(void)
-{
-#ifdef VPROC_STANDBY_IMPLEMENTED
- return likely(vproc_shmem) ? vproc_shmem->vp_shmem_standby_cnt : INT32_MAX;
-#else
- return 0;
-#endif
-}
-
-size_t
-_vproc_standby_timeout(void)
-{
- return likely(vproc_shmem) ? vproc_shmem->vp_shmem_standby_timeout : 0;
-}
-
-bool
-_vproc_pid_is_managed(pid_t p)
-{
- boolean_t result = false;
- vproc_mig_pid_is_managed(bootstrap_port, p, &result);
-
- return result;
-}
-
-kern_return_t
-_vproc_transaction_count_for_pid(pid_t p, int32_t *count, bool *condemned)
-{
- boolean_t _condemned = false;
- kern_return_t kr = vproc_mig_transaction_count_for_pid(bootstrap_port, p, count, &_condemned);
- if (kr == KERN_SUCCESS && condemned) {
- *condemned = _condemned ? true : false;
- }
-
- return kr;
-}
-
-void
-_vproc_transaction_try_exit(int status)
-{
-#if !TARGET_OS_EMBEDDED
- if (unlikely(vproc_shmem == NULL)) {
- return;
- }
-
- if (__sync_bool_compare_and_swap(&vproc_shmem->vp_shmem_transaction_cnt, 0, -1)) {
- vproc_shmem->vp_shmem_flags |= VPROC_SHMEM_EXITING;
- _exit(status);
- }
-#else
-
-#endif
-}
-
-void
-vproc_transaction_end(vproc_t vp __attribute__((unused)), vproc_transaction_t vpt)
-{
- if (unlikely(vpt != (vproc_transaction_t)vproc_shmem_init)) {
- _vproc_set_crash_log_message("Bogus transaction handle passed to vproc_transaction_end() ");
- abort();
- }
-
- _vproc_transaction_end();
-}
-
-void
-_vproc_transaction_end(void)
-{
- typeof(vproc_shmem->vp_shmem_transaction_cnt) newval;
-
- if (unlikely(vproc_shmem == NULL)) {
- return;
- }
-
- newval = __sync_sub_and_fetch(&vproc_shmem->vp_shmem_transaction_cnt, 1);
-
- runtime_ktrace(RTKT_VPROC_TRANSACTION_DECREMENT, newval, 0, 0);
- if (unlikely(newval < 0)) {
- if (vproc_shmem->vp_shmem_flags & VPROC_SHMEM_EXITING) {
- _exit(0);
- } else {
- _vproc_set_crash_log_message("Unbalanced: vproc_transaction_end()");
- }
- abort();
- } else if (newval == 0 && vproc_gone2zero) {
- vproc_gone2zero();
- }
-}
-
-vproc_standby_t
-vproc_standby_begin(vproc_t vp __attribute__((unused)))
-{
-#ifdef VPROC_STANDBY_IMPLEMENTED
- vproc_standby_t vpsb = (vproc_standby_t)vproc_shmem_init; /* we need a "random" variable that is testable */
-
- _vproc_standby_begin();
-
- return vpsb;
-#else
- return NULL;
-#endif
-}
-
-void
-_vproc_standby_begin(void)
-{
-#ifdef VPROC_STANDBY_IMPLEMENTED
- typeof(vproc_shmem->vp_shmem_standby_cnt) newval;
-
- if (unlikely(vproc_shmem == NULL)) {
- int po_r = pthread_once(&shmem_inited, vproc_client_init);
- if (po_r != 0 || vproc_shmem == NULL) {
- return;
- }
- }
-
- newval = __sync_add_and_fetch(&vproc_shmem->vp_shmem_standby_cnt, 1);
-
- if (unlikely(newval < 1)) {
- _vproc_set_crash_log_message("Unbalanced: vproc_standby_begin()");
- abort();
- }
-#else
- return;
-#endif
-}
-
-void
-vproc_standby_end(vproc_t vp __attribute__((unused)), vproc_standby_t vpt __attribute__((unused)))
-{
-#ifdef VPROC_STANDBY_IMPLEMENTED
- if (unlikely(vpt != (vproc_standby_t)vproc_shmem_init)) {
- _vproc_set_crash_log_message("Bogus standby handle passed to vproc_standby_end() ");
- abort();
- }
-
- _vproc_standby_end();
-#else
- return;
-#endif
-}
-
-void
-_vproc_standby_end(void)
-{
-#ifdef VPROC_STANDBY_IMPLEMENTED
- typeof(vproc_shmem->vp_shmem_standby_cnt) newval;
-
- if (unlikely(vproc_shmem == NULL)) {
- _vproc_set_crash_log_message("Process called vproc_standby_end() when not enrolled in transaction model.");
- abort();
- }
-
- newval = __sync_sub_and_fetch(&vproc_shmem->vp_shmem_standby_cnt, 1);
-
- if (unlikely(newval < 0)) {
- _vproc_set_crash_log_message("Unbalanced: vproc_standby_end()");
- abort();
- }
-#else
- return;
-#endif
-}
-
-int32_t *
-_vproc_transaction_ptr(void)
-{
- return NULL;
-}
-
-void
-_vproc_transaction_set_callouts(_vproc_transaction_callout gone2zero, _vproc_transaction_callout gonenonzero)
-{
- if (unlikely(vproc_shmem == NULL)) {
- int po_r = pthread_once(&shmem_inited, vproc_client_init);
- if (po_r != 0 || vproc_shmem == NULL) {
- return;
- }
- }
-
- static bool once = false;
- if (once) {
- _vproc_set_crash_log_message("This SPI may only be called once. It is only meant for libxpc.");
- abort();
- }
-
- once = true;
-
- vproc_gone2zero = gone2zero;
- vproc_gonenonzero = gonenonzero;
-}
-
-kern_return_t
-_vproc_grab_subset(mach_port_t bp, mach_port_t *reqport, mach_port_t *rcvright, launch_data_t *outval,
- mach_port_array_t *ports, mach_msg_type_number_t *portCnt)
-{
- mach_msg_type_number_t outdata_cnt;
- vm_offset_t outdata = 0;
- size_t data_offset = 0;
- launch_data_t out_obj;
- kern_return_t kr;
-
- if ((kr = vproc_mig_take_subset(bp, reqport, rcvright, &outdata, &outdata_cnt, ports, portCnt))) {
- goto out;
- }
-
- if ((out_obj = launch_data_unpack((void *)outdata, outdata_cnt, NULL, 0, &data_offset, NULL))) {
- *outval = launch_data_copy(out_obj);
- } else {
- kr = 1;
- }
-
-out:
- if (outdata) {
- mig_deallocate(outdata, outdata_cnt);
- }
-
- return kr;
-}
-
-vproc_err_t
-_vproc_post_fork_ping(void)
-{
-#if !TARGET_OS_EMBEDDED
- au_asid_t s = AU_DEFAUDITSID;
- do {
- mach_port_t session = MACH_PORT_NULL;
- kern_return_t kr = vproc_mig_post_fork_ping(bootstrap_port, mach_task_self(), &session);
- if (kr != KERN_SUCCESS) {
- /* If this happens, our bootstrap port probably got hosed. */
- _vproc_log(LOG_ERR, "Post-fork ping failed!");
- break;
- }
-
- /* If we get back MACH_PORT_NULL, that means we just stick with the session
- * we inherited across fork(2).
- */
- if (session == MACH_PORT_NULL) {
- s = ~AU_DEFAUDITSID;
- break;
- }
-
- s = _audit_session_join(session);
- if (s == 0) {
- _vproc_log_error(LOG_ERR, "Could not join security session!");
- s = AU_DEFAUDITSID;
- } else {
- _vproc_log(LOG_DEBUG, "Joined session %d.", s);
- }
- } while (0);
-
- return s != AU_DEFAUDITSID ? NULL : _vproc_post_fork_ping;
-#else
- mach_port_t session = MACH_PORT_NULL;
- return vproc_mig_post_fork_ping(bootstrap_port, mach_task_self(), &session) ? _vproc_post_fork_ping : NULL;
-#endif
-}
-
-vproc_err_t
-_vprocmgr_init(const char *session_type)
-{
- if (vproc_mig_init_session(bootstrap_port, (char *)session_type, _audit_session_self()) == 0) {
- return NULL;
- }
-
- return (vproc_err_t)_vprocmgr_init;
-}
-
-vproc_err_t
-_vprocmgr_move_subset_to_user(uid_t target_user, const char *session_type, uint64_t flags)
-{
- kern_return_t kr = 0;
- bool is_bkgd = (strcmp(session_type, VPROCMGR_SESSION_BACKGROUND) == 0);
- int64_t ldpid, lduid;
-
- if (vproc_swap_integer(NULL, VPROC_GSK_MGR_PID, 0, &ldpid) != 0) {
- return (vproc_err_t)_vprocmgr_move_subset_to_user;
- }
-
- if (vproc_swap_integer(NULL, VPROC_GSK_MGR_UID, 0, &lduid) != 0) {
- return (vproc_err_t)_vprocmgr_move_subset_to_user;
- }
-
- if (!is_bkgd && ldpid != 1) {
- if (lduid == getuid()) {
- return NULL;
- }
- /*
- * Not all sessions can be moved.
- * We should clean up this mess someday.
- */
- return (vproc_err_t)_vprocmgr_move_subset_to_user;
- }
-
- mach_port_t puc = 0, rootbs = get_root_bootstrap_port();
-
- if (vproc_mig_lookup_per_user_context(rootbs, target_user, &puc) != 0) {
- return (vproc_err_t)_vprocmgr_move_subset_to_user;
- }
-
- if (is_bkgd) {
- task_set_bootstrap_port(mach_task_self(), puc);
- mach_port_deallocate(mach_task_self(), bootstrap_port);
- bootstrap_port = puc;
- } else {
- kr = vproc_mig_move_subset(puc, bootstrap_port, (char *)session_type, _audit_session_self(), flags);
- mach_port_deallocate(mach_task_self(), puc);
- }
-
- cached_pid = -1;
-
- if (kr) {
- return (vproc_err_t)_vprocmgr_move_subset_to_user;
- }
-
- return _vproc_post_fork_ping();
-}
-
-vproc_err_t
-_vprocmgr_switch_to_session(const char *target_session, vproc_flags_t flags __attribute__((unused)))
-{
- mach_port_t new_bsport = MACH_PORT_NULL;
- kern_return_t kr = KERN_FAILURE;
-
- mach_port_t tnp = MACH_PORT_NULL;
- task_name_for_pid(mach_task_self(), getpid(), &tnp);
- if ((kr = vproc_mig_switch_to_session(bootstrap_port, tnp, (char *)target_session, _audit_session_self(), &new_bsport)) != KERN_SUCCESS) {
- _vproc_log(LOG_NOTICE, "_vprocmgr_switch_to_session(): kr = 0x%x", kr);
- return (vproc_err_t)_vprocmgr_switch_to_session;
- }
-
- task_set_bootstrap_port(mach_task_self(), new_bsport);
- mach_port_deallocate(mach_task_self(), bootstrap_port);
- bootstrap_port = new_bsport;
-
- return !issetugid() ? _vproc_post_fork_ping() : NULL;
-}
-
-vproc_err_t
-_vprocmgr_detach_from_console(vproc_flags_t flags __attribute__((unused)))
-{
- return _vprocmgr_switch_to_session(VPROCMGR_SESSION_BACKGROUND, 0);
-}
-
-pid_t
-_spawn_via_launchd(const char *label, const char *const *argv, const struct spawn_via_launchd_attr *spawn_attrs, int struct_version)
-{
- size_t i, good_enough_size = 10*1024*1024;
- mach_msg_type_number_t indata_cnt = 0;
- vm_offset_t indata = 0;
- mach_port_t obsvr_port = MACH_PORT_NULL;
- launch_data_t tmp, tmp_array, in_obj;
- const char *const *tmpp;
- kern_return_t kr = 1;
- void *buf = NULL;
- pid_t p = -1;
-
- if ((in_obj = launch_data_alloc(LAUNCH_DATA_DICTIONARY)) == NULL) {
- goto out;
- }
-
- if ((tmp = launch_data_new_string(label)) == NULL) {
- goto out;
- }
-
- launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_LABEL);
-
- if ((tmp_array = launch_data_alloc(LAUNCH_DATA_ARRAY)) == NULL) {
- goto out;
- }
-
- for (i = 0; *argv; i++, argv++) {
- tmp = launch_data_new_string(*argv);
- if (tmp == NULL) {
- goto out;
- }
-
- launch_data_array_set_index(tmp_array, tmp, i);
- }
-
- launch_data_dict_insert(in_obj, tmp_array, LAUNCH_JOBKEY_PROGRAMARGUMENTS);
-
- if (spawn_attrs) switch (struct_version) {
- case 3:
- case 2:
-#if HAVE_QUARANTINE
- if (spawn_attrs->spawn_quarantine) {
- char qbuf[QTN_SERIALIZED_DATA_MAX];
- size_t qbuf_sz = QTN_SERIALIZED_DATA_MAX;
-
- if (qtn_proc_to_data(spawn_attrs->spawn_quarantine, qbuf, &qbuf_sz) == 0) {
- tmp = launch_data_new_opaque(qbuf, qbuf_sz);
- launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_QUARANTINEDATA);
- }
- }
-#endif
-
- if (spawn_attrs->spawn_seatbelt_profile) {
- tmp = launch_data_new_string(spawn_attrs->spawn_seatbelt_profile);
- launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_SANDBOXPROFILE);
- }
-
- if (spawn_attrs->spawn_seatbelt_flags) {
- tmp = launch_data_new_integer(*spawn_attrs->spawn_seatbelt_flags);
- launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_SANDBOXFLAGS);
- }
-
- /* fall through */
- case 1:
- if (spawn_attrs->spawn_binpref) {
- tmp_array = launch_data_alloc(LAUNCH_DATA_ARRAY);
- for (i = 0; i < spawn_attrs->spawn_binpref_cnt; i++) {
- tmp = launch_data_new_integer(spawn_attrs->spawn_binpref[i]);
- launch_data_array_set_index(tmp_array, tmp, i);
- }
- launch_data_dict_insert(in_obj, tmp_array, LAUNCH_JOBKEY_BINARYORDERPREFERENCE);
- }
- /* fall through */
- case 0:
- if (spawn_attrs->spawn_flags & SPAWN_VIA_LAUNCHD_STOPPED) {
- tmp = launch_data_new_bool(true);
- launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_WAITFORDEBUGGER);
- }
- if (spawn_attrs->spawn_flags & SPAWN_VIA_LAUNCHD_TALAPP) {
- tmp = launch_data_new_string(LAUNCH_KEY_POSIXSPAWNTYPE_TALAPP);
- launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_POSIXSPAWNTYPE);
- }
- if (spawn_attrs->spawn_flags & SPAWN_VIA_LAUNCHD_WIDGET) {
- tmp = launch_data_new_string(LAUNCH_KEY_POSIXSPAWNTYPE_WIDGET);
- launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_POSIXSPAWNTYPE);
- }
- if (spawn_attrs->spawn_flags & SPAWN_VIA_LAUNCHD_DISABLE_ASLR) {
- tmp = launch_data_new_bool(true);
- launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_DISABLEASLR);
- }
-
- if (spawn_attrs->spawn_env) {
- launch_data_t tmp_dict = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
-
- for (tmpp = spawn_attrs->spawn_env; *tmpp; tmpp++) {
- char *eqoff, tmpstr[strlen(*tmpp) + 1];
-
- strcpy(tmpstr, *tmpp);
-
- eqoff = strchr(tmpstr, '=');
-
- if (!eqoff) {
- goto out;
- }
-
- *eqoff = '\0';
-
- launch_data_dict_insert(tmp_dict, launch_data_new_string(eqoff + 1), tmpstr);
- }
-
- launch_data_dict_insert(in_obj, tmp_dict, LAUNCH_JOBKEY_ENVIRONMENTVARIABLES);
- }
-
- if (spawn_attrs->spawn_path) {
- tmp = launch_data_new_string(spawn_attrs->spawn_path);
- launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_PROGRAM);
- }
-
- if (spawn_attrs->spawn_chdir) {
- tmp = launch_data_new_string(spawn_attrs->spawn_chdir);
- launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_WORKINGDIRECTORY);
- }
-
- if (spawn_attrs->spawn_umask) {
- tmp = launch_data_new_integer(*spawn_attrs->spawn_umask);
- launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_UMASK);
- }
-
- break;
- default:
- break;
- }
-
- if (!(buf = malloc(good_enough_size))) {
- goto out;
- }
-
- if ((indata_cnt = launch_data_pack(in_obj, buf, good_enough_size, NULL, NULL)) == 0) {
- goto out;
- }
-
- indata = (vm_offset_t)buf;
-
- if (struct_version == 3) {
- kr = vproc_mig_spawn2(bootstrap_port, indata, indata_cnt, _audit_session_self(), &p, &obsvr_port);
- } else {
- _vproc_set_crash_log_message("Bogus version passed to _spawn_via_launchd(). For this release, the only valid version is 3.");
- }
-
- if (kr == VPROC_ERR_TRY_PER_USER) {
- mach_port_t puc;
-
- if (vproc_mig_lookup_per_user_context(bootstrap_port, 0, &puc) == 0) {
- if (struct_version == 3) {
- kr = vproc_mig_spawn2(puc, indata, indata_cnt, _audit_session_self(), &p, &obsvr_port);
- }
- mach_port_deallocate(mach_task_self(), puc);
- }
- }
-
-out:
- if (in_obj) {
- launch_data_free(in_obj);
- }
-
- if (buf) {
- free(buf);
- }
-
- switch (kr) {
- case BOOTSTRAP_SUCCESS:
- if (spawn_attrs && spawn_attrs->spawn_observer_port) {
- *spawn_attrs->spawn_observer_port = obsvr_port;
- } else {
- if (struct_version == 3) {
- mach_port_mod_refs(mach_task_self(), obsvr_port, MACH_PORT_RIGHT_RECEIVE, -1);
- } else {
- mach_port_deallocate(mach_task_self(), obsvr_port);
- }
- }
- return p;
- case BOOTSTRAP_NOT_PRIVILEGED:
- errno = EPERM; break;
- case BOOTSTRAP_NO_MEMORY:
- errno = ENOMEM; break;
- case BOOTSTRAP_NAME_IN_USE:
- errno = EEXIST; break;
- case 1:
- errno = EIO; break;
- default:
- errno = EINVAL; break;
- }
-
- return -1;
-}
-
-kern_return_t
-mpm_wait(mach_port_t ajob __attribute__((unused)), int *wstatus)
-{
- *wstatus = 0;
- return 0;
-}
-
-kern_return_t
-mpm_uncork_fork(mach_port_t ajob __attribute__((unused)))
-{
- return KERN_FAILURE;
-}
-
-kern_return_t
-_vprocmgr_getsocket(name_t sockpath)
-{
- return vproc_mig_getsocket(bootstrap_port, sockpath);
-}
-
-vproc_err_t
-_vproc_get_last_exit_status(int *wstatus)
-{
- int64_t val;
-
- if (vproc_swap_integer(NULL, VPROC_GSK_LAST_EXIT_STATUS, 0, &val) == 0) {
- *wstatus = (int)val;
- return NULL;
- }
-
- return (vproc_err_t)_vproc_get_last_exit_status;
-}
-
-vproc_err_t
-_vproc_send_signal_by_label(const char *label, int sig)
-{
- if (vproc_mig_send_signal(bootstrap_port, (char *)label, sig) == 0) {
- return NULL;
- }
-
- return _vproc_send_signal_by_label;
-}
-
-vproc_err_t
-_vprocmgr_log_forward(mach_port_t mp, void *data, size_t len)
-{
- if (vproc_mig_log_forward(mp, (vm_offset_t)data, len) == 0) {
- return NULL;
- }
-
- return _vprocmgr_log_forward;
-}
-
-vproc_err_t
-_vprocmgr_log_drain(vproc_t vp __attribute__((unused)), pthread_mutex_t *mutex, _vprocmgr_log_drain_callback_t func)
-{
- mach_msg_type_number_t outdata_cnt, tmp_cnt;
- vm_offset_t outdata = 0;
- struct timeval tv;
- struct logmsg_s *lm;
-
- if (!func) {
- return _vprocmgr_log_drain;
- }
-
- if (vproc_mig_log_drain(bootstrap_port, &outdata, &outdata_cnt) != 0) {
- return _vprocmgr_log_drain;
- }
-
- tmp_cnt = outdata_cnt;
-
- if (mutex) {
- pthread_mutex_lock(mutex);
- }
-
- for (lm = (struct logmsg_s *)outdata; tmp_cnt > 0; lm = ((void *)lm + lm->obj_sz)) {
- lm->from_name = (char *)lm + lm->from_name_offset;
- lm->about_name = (char *)lm + lm->about_name_offset;
- lm->msg = (char *)lm + lm->msg_offset;
- lm->session_name = (char *)lm + lm->session_name_offset;
-
- tv.tv_sec = lm->when / USEC_PER_SEC;
- tv.tv_usec = lm->when % USEC_PER_SEC;
-
- func(&tv, lm->from_pid, lm->about_pid, lm->sender_uid, lm->sender_gid, lm->pri,
- lm->from_name, lm->about_name, lm->session_name, lm->msg);
-
- tmp_cnt -= lm->obj_sz;
- }
-
- if (mutex) {
- pthread_mutex_unlock(mutex);
- }
-
- if (outdata) {
- mig_deallocate(outdata, outdata_cnt);
- }
-
- return NULL;
-}
-
-vproc_err_t
-vproc_swap_integer(vproc_t vp, vproc_gsk_t key, int64_t *inval, int64_t *outval)
-{
- static int64_t cached_is_managed = -1;
- int64_t dummyval = 0;
-
- switch (key) {
- case VPROC_GSK_MGR_PID:
- if (cached_pid != -1 && outval) {
- *outval = cached_pid;
- return NULL;
- }
- break;
- case VPROC_GSK_IS_MANAGED:
- if (cached_is_managed != -1 && outval) {
- *outval = cached_is_managed;
- return NULL;
- }
- break;
- case VPROC_GSK_TRANSACTIONS_ENABLED:
- /* Shared memory region is required for transactions. */
- if (unlikely(vproc_shmem == NULL)) {
- int po_r = pthread_once(&shmem_inited, vproc_client_init);
- if (po_r != 0 || vproc_shmem == NULL) {
- if (outval) {
- *outval = -1;
- }
- return (vproc_err_t)vproc_swap_integer;
- }
- }
-
- if (s_cached_transactions_enabled && outval) {
- *outval = s_cached_transactions_enabled;
- return NULL;
- }
- break;
- default:
- break;
- }
-
- kern_return_t kr = KERN_FAILURE;
- mach_port_t mp = vp ? vp->j_port : bootstrap_port;
- if ((kr = vproc_mig_swap_integer(mp, inval ? key : 0, outval ? key : 0, inval ? *inval : 0, outval ? outval : &dummyval)) == 0) {
- switch (key) {
- case VPROC_GSK_MGR_PID:
- cached_pid = outval ? *outval : dummyval;
- break;
- case VPROC_GSK_IS_MANAGED:
- cached_is_managed = outval ? *outval : dummyval;
- break;
- case VPROC_GSK_TRANSACTIONS_ENABLED:
- s_cached_transactions_enabled = 1;
- break;
- case VPROC_GSK_PERUSER_SUSPEND:
- if (dummyval) {
- /* Wait for the per-user launchd to exit before returning. */
- int kq = kqueue();
- struct kevent kev;
- EV_SET(&kev, dummyval, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, 0);
- int r = kevent(kq, &kev, 1, &kev, 1, NULL);
- (void)close(kq);
- if (r != 1) {
- return NULL;
- }
- break;
- }
- default:
- break;
- }
- return NULL;
- }
-
- return (vproc_err_t)vproc_swap_integer;
-}
-
-mach_port_t
-get_root_bootstrap_port(void)
-{
- mach_port_t parent_port = 0;
- mach_port_t previous_port = 0;
-
- do {
- if (previous_port) {
- if (previous_port != bootstrap_port) {
- mach_port_deallocate(mach_task_self(), previous_port);
- }
- previous_port = parent_port;
- } else {
- previous_port = bootstrap_port;
- }
-
- if (bootstrap_parent(previous_port, &parent_port) != 0) {
- return MACH_PORT_NULL;
- }
-
- } while (parent_port != previous_port);
-
- return parent_port;
-}
-
-vproc_err_t
-vproc_swap_complex(vproc_t vp, vproc_gsk_t key, launch_data_t inval, launch_data_t *outval)
-{
- size_t data_offset = 0, good_enough_size = 10*1024*1024;
- mach_msg_type_number_t indata_cnt = 0, outdata_cnt;
- vm_offset_t indata = 0, outdata = 0;
- launch_data_t out_obj;
- void *rval = vproc_swap_complex;
- void *buf = NULL;
-
- if (inval) {
- if (!(buf = malloc(good_enough_size))) {
- goto out;
- }
-
- if ((indata_cnt = launch_data_pack(inval, buf, good_enough_size, NULL, NULL)) == 0) {
- goto out;
- }
-
- indata = (vm_offset_t)buf;
- }
-
- mach_port_t mp = vp ? vp->j_port : bootstrap_port;
- if (vproc_mig_swap_complex(mp, inval ? key : 0, outval ? key : 0, indata, indata_cnt, &outdata, &outdata_cnt) != 0) {
- goto out;
- }
-
- if (outval) {
- if (!(out_obj = launch_data_unpack((void *)outdata, outdata_cnt, NULL, 0, &data_offset, NULL))) {
- goto out;
- }
-
- if (!(*outval = launch_data_copy(out_obj))) {
- goto out;
- }
- }
-
- rval = NULL;
-out:
- if (buf) {
- free(buf);
- }
-
- if (outdata) {
- mig_deallocate(outdata, outdata_cnt);
- }
-
- return rval;
-}
-
-vproc_err_t
-vproc_swap_string(vproc_t vp, vproc_gsk_t key, const char *instr, char **outstr)
-{
- launch_data_t instr_data = instr ? launch_data_new_string(instr) : NULL;
- launch_data_t outstr_data = NULL;
-
- vproc_err_t verr = vproc_swap_complex(vp, key, instr_data, &outstr_data);
- if (!verr && outstr) {
- if (launch_data_get_type(outstr_data) == LAUNCH_DATA_STRING) {
- *outstr = strdup(launch_data_get_string(outstr_data));
- } else {
- verr = (vproc_err_t)vproc_swap_string;
- }
- launch_data_free(outstr_data);
- }
- if (instr_data) {
- launch_data_free(instr_data);
- }
-
- return verr;
-}
-
-void *
-reboot2(uint64_t flags)
-{
- if (vproc_mig_reboot2(get_root_bootstrap_port(), flags) == 0) {
- return NULL;
- }
-
- return reboot2;
-}
-
-vproc_err_t
-_vproc_kickstart_by_label(const char *label, pid_t *out_pid, mach_port_t *out_port_name __attribute__((unused)), mach_port_t *out_obsrvr_port __attribute__((unused)), vproc_flags_t flags)
-{
- /* Ignore the two port parameters. This SPI isn't long for this world, and
- * all the current clients just leak them anyway.
- */
- kern_return_t kr = vproc_mig_kickstart(bootstrap_port, (char *)label, out_pid, flags);
- if (kr == KERN_SUCCESS) {
- return NULL;
- }
-
- return (vproc_err_t)_vproc_kickstart_by_label;
-}
-
-vproc_err_t
-_vproc_set_global_on_demand(bool state)
-{
- int64_t val = state ? ~0 : 0;
-
- if (vproc_swap_integer(NULL, VPROC_GSK_GLOBAL_ON_DEMAND, &val, NULL) == 0) {
- return NULL;
- }
-
- return (vproc_err_t)_vproc_set_global_on_demand;
-}
-
-void
-_vproc_logv(int pri, int err, const char *msg, va_list ap)
-{
- char flat_msg[3000];
-
- vsnprintf(flat_msg, sizeof(flat_msg), msg, ap);
-
- vproc_mig_log(bootstrap_port, pri, err, flat_msg);
-}
-
-void
-_vproc_log(int pri, const char *msg, ...)
-{
- va_list ap;
-
- va_start(ap, msg);
- _vproc_logv(pri, 0, msg, ap);
- va_end(ap);
-}
-
-void
-_vproc_log_error(int pri, const char *msg, ...)
-{
- int saved_errno = errno;
- va_list ap;
-
- va_start(ap, msg);
- _vproc_logv(pri, saved_errno, msg, ap);
- va_end(ap);
-}
-
-bool
-vprocmgr_helper_check_in(const char *name, mach_port_t rp, launch_data_t *events, uint64_t *tokens)
-{
- vm_offset_t events_packed = 0;
- mach_msg_type_number_t sz = 0;
- size_t data_off = 0;
-
- kern_return_t kr = vproc_mig_event_source_check_in(bootstrap_port, (char *)name, rp, &events_packed, &sz, tokens);
- if (kr == 0) {
- launch_data_t _events = launch_data_unpack((void *)events_packed, sz, NULL, 0, &data_off, 0);
- *events = launch_data_copy(_events);
- if (!*events) {
- kr = 1;
- }
-
- mig_deallocate(events_packed, sz);
- }
-
- return (kr == 0);
-}
-
-bool
-vprocmgr_helper_event_set_state(const char *sysname, uint64_t token, bool state)
-{
- kern_return_t kr = vproc_mig_event_set_state(bootstrap_port, (char *)sysname, token, state);
- return (kr == 0);
-}
-
-void
-vprocmgr_helper_register(vproc_helper_recv_ping_t callout)
-{
- vprocmgr_helper_callout = callout;
-}
-
-/* The type naming convention is as follows:
- * For requests...
- * union __RequestUnion__<userprefix><subsystem>_subsystem
- * For replies...
- * union __ReplyUnion__<userprefix><subsystem>_subsystem
- */
-union maxmsgsz {
- union __RequestUnion__helper_downcall_launchd_helper_subsystem req;
- union __ReplyUnion__helper_downcall_launchd_helper_subsystem rep;
-};
-
-size_t vprocmgr_helper_maxmsgsz = sizeof(union maxmsgsz);
-
-kern_return_t
-helper_recv_ping(mach_port_t p, audit_token_t autok)
-{
- return vprocmgr_helper_callout(p, autok);
-}
-
-boolean_t
-vprocmgr_helper_server_routine_for_dispatch(mach_msg_header_t *message, mach_msg_header_t *reply)
-{
- return launchd_helper_server(message, reply);
-}
-
-kern_return_t
-helper_recv_wait(mach_port_t p, int status)
-{
- /* Total hack. */
- return (errno = mach_port_set_context(mach_task_self(), p, (mach_vm_address_t)status));
-}
-
-int
-launch_wait(mach_port_t port)
-{
- int status = -1;
- errno = mach_msg_server_once(launchd_helper_server, vprocmgr_helper_maxmsgsz, port, 0);
- if (errno == MACH_MSG_SUCCESS) {
- mach_vm_address_t ctx = 0;
- if ((errno = mach_port_get_context(mach_task_self(), port, &ctx)) == KERN_SUCCESS) {
- status = ctx;
- }
- }
-
- return status;
-}
+++ /dev/null
-/*
- * Copyright (c) 2007 Apple 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@
- */
-
-subsystem job_forward 400;
-
-#include <mach/std_types.defs>
-#include <mach/mach_types.defs>
-#include "launchd_mig_types.defs"
-import "vproc.h";
-import "vproc_priv.h";
-import "vproc_internal.h";
-
-userprefix vproc_mig_;
-serverprefix job_mig_;
-
-skip; /* create_server */
-
-skip; /* reboot2 */
-
-skip; /* check_in */
-
-skip; /* register2 */
-
-simpleroutine
-look_up2_forward(
- j : job_t;
-replyport rp : mach_port_move_send_once_t;
- servicename : name_t;
- targetpid : pid_t;
- instanceid : uuid_t;
- flags : uint64_t
-);
-
-skip; /* send_signal */
-
-simpleroutine
-parent_forward(
- j : job_t;
-replyport rp : mach_port_move_send_once_t
-);
-
+++ /dev/null
-/*
- * Copyright (c) 2006 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@
- */
-
-subsystem job_reply 500;
-
-#include <mach/std_types.defs>
-#include <mach/mach_types.defs>
-#include "launchd_mig_types.defs"
-import "bootstrap.h";
-import "vproc.h";
-import "vproc_internal.h";
-
-skip; /* create_server */
-
-skip; /* unprivileged */
-
-skip; /* check_in */
-
-skip; /* register */
-
-skip; /* look_up */
-
-simpleroutine
-job_mig_send_signal_reply(
- rp : mach_port_move_send_once_t;
- kr : kern_return_t, RetCode
-);
-
-skip; /* parent */
-
-skip; /* post_fork_ping */
-
-skip; /* info */
-
-skip; /* subset */
-
-skip; /* create_service */
-
-skip; /* transfer_subset */
-
-skip; /* getsocket */
-
-skip; /* spawn */
-
-skip; /* wait */
-
-skip; /* uncork_fork */
-
-skip; /* swap_integer */
-
-skip; /* log */
-
-skip; /* lookup_per_user_context */
-
-skip; /* move_subset */
-
-skip; /* swap_complex */
-
-simpleroutine
-job_mig_log_drain_reply(
- rp : mach_port_move_send_once_t;
- kr : kern_return_t, RetCode;
- outval : pointer_t
-);
-
-skip; /* log_forward */
-
-skip; /* kickstart */
-
-skip; /* embedded_wait */
-
-skip; /* lookup_children */
-
-skip; /* switch_to_session */
-
-skip; /* transaction_count_for_pid */
-
-skip; /* pid_is_managed */
-
-skip; /* port_for_label */
-
-skip; /* init_session */
-
-skip; /* set_security_session */
-
-skip; /* wait2 */
-
-skip; /* event_source_check_in */
-
-skip; /* event_set_state */
-
-simpleroutine
-job_mig_spawn2_reply(
- rp : mach_port_move_send_once_t;
- kr : kern_return_t, RetCode;
- pid : pid_t;
- obsrvp : mach_port_move_receive_t
-);
-
-skip; /* get_root_bootstrap */
+++ /dev/null
-/*
- * Copyright (c) 1999-2004 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@
- */
-/*
- * bootstrap -- fundamental service initiator and port server
- * Mike DeMoney, NeXT, Inc.
- * Copyright, 1990. All rights reserved.
- */
-
-subsystem bootstrap 400;
-
-#include <mach/std_types.defs>
-#include <mach/mach_types.defs>
-#include "launchd_mig_types.defs"
-import "bootstrap_public.h";
-import "bootstrap_private.h";
-
-userprefix vproc_mig_;
-serverprefix job_mig_;
-
-routine create_server(
- __bs_port : job_t;
- __server_cmd : cmd_t;
- __server_uid : natural_t;
- __on_demand : boolean_t;
- ServerAuditToken __token : audit_token_t;
- out __server_port : mach_port_make_send_t);
-
-skip; /* Last used in 10.4. Was bootstrap_unprivileged() */
-
-routine check_in(
- __bs_port : job_t;
- __service_name : name_t;
- ServerAuditToken __token : audit_token_t;
- out __service_port : mach_port_move_receive_t);
-
-routine register(
- __bs_port : job_t;
- ServerAuditToken __token : audit_token_t;
- __service_name : name_t;
- __service_port : mach_port_t);
-
-routine look_up(
- __bs_port : job_t;
- ServerAuditToken __token : audit_token_t;
- __service_name : name_t;
- out __service_port : mach_port_send_t);
-
-skip; /* last used in 10.4 */
-
-routine parent(
- __bs_port : job_t;
- out __parent_port : mach_port_send_t);
-
-skip; /* last used in 10.4 */
-
-routine info(
- __bs_port : job_t;
- out __service_names : name_array_t, dealloc;
- out __service_active : bootstrap_status_array_t, dealloc);
-
-routine subset(
- __bs_port : job_t;
- __requestor_port: mach_port_t;
- out __subset_port : mach_port_make_send_t);
-
-routine create_service(
- __bs_port : job_t;
- __service_name : name_t;
- out __service_port : mach_port_t);
-
-routine transfer_subset(
- __bs_port : job_t;
- out __bs_reqport : mach_port_t;
- out __bs_rcvright : mach_port_move_receive_t;
- out __service_names : name_array_t, dealloc;
- out __service_pids : pointer_t, dealloc;
- out __service_ports : mach_port_array_t, dealloc);
-
-routine getsocket(
- __bs_port : job_t;
- out __sockpath : name_t);
-
-routine spawn(
- __bs_port : job_t;
- ServerAuditToken __token : audit_token_t;
- __chars : _internal_string_t;
- __argc : uint32_t;
- __envc : uint32_t;
- __flags : uint64_t;
- __umask : uint16_t;
- out __pid : pid_t;
- out __obsvr_port : mach_port_make_send_t);
-
-routine wait(
- __bs_port : job_t;
- sreplyport __rport : mach_port_make_send_once_t;
- ServerAuditToken __token : audit_token_t;
- out __waitval : integer_t);
-
-routine uncork_fork(
- __bs_port : job_t;
- ServerAuditToken __token : audit_token_t);
-
-/* Essentially the inverse of bootstrap_unprivileged() */
-routine get_self(
- __bs_port : job_t;
- ServerAuditToken __token : audit_token_t;
- out __job_port : mach_port_make_send_t);
+++ /dev/null
-/*
- * Copyright (c) 1999-2004 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@
- */
-/*
- * bootstrap -- fundamental service initiator and port server
- * Mike DeMoney, NeXT, Inc.
- * Copyright, 1990. All rights reserved.
- */
-
-subsystem protocol_vproc 400;
-
-#include <mach/std_types.defs>
-#include <mach/mach_types.defs>
-#include "launchd_mig_types.defs"
-import "vproc.h";
-import "vproc_priv.h";
-import "vproc_internal.h";
-
-type mach_port_move_send_array_t = array[] of mach_port_move_send_t
- ctype: mach_port_array_t;
-
-userprefix vproc_mig_;
-serverprefix job_mig_;
-
-routine
-create_server(
- j : job_t;
- servercmd : cmd_t;
- serveruid : uid_t;
- ondemand : boolean_t;
-out serverport : mach_port_make_send_t
-);
-
-routine
-reboot2(
- j : job_t;
- flags : uint64_t
-);
-
-routine
-check_in2(
- j : job_t;
- servicename : name_t;
-out serviceport : mach_port_move_receive_t;
-out instanceid : uuid_t;
- flags : uint64_t
-);
-
-routine
-register2(
- j : job_t;
- servicename : name_t;
- serviceport : mach_port_t;
- flags : uint64_t
-);
-
-routine
-look_up2(
- j : job_t;
-sreplyport rp : mach_port_make_send_once_t;
- servicename : name_t;
-out serviceport : mach_port_t;
-UserAuditToken servercreds : audit_token_t;
- targetpid : pid_t;
- instanceid : uuid_t;
- flags : uint64_t
-);
-
-routine
-send_signal(
- j : job_t;
-sreplyport rp : mach_port_make_send_once_t;
- label : name_t;
- sig : integer_t
-);
-
-routine
-parent(
- j : job_t;
-sreplyport rp : mach_port_make_send_once_t;
-out parentport : mach_port_make_send_t
-);
-
-routine
-post_fork_ping(
- j : job_t;
- taskport : task_t;
-out asport : mach_port_t
-);
-
-routine
-info(
- j : job_t;
-out names : name_array_t, dealloc;
-out jobs : name_array_t, dealloc;
-out actives : bootstrap_status_array_t, dealloc;
- flags : uint64_t
-);
-
-routine
-subset(
- j : job_t;
- reqport : mach_port_t;
-out subsetport : mach_port_make_send_t
-);
-
-routine
-setup_shmem(
- j : job_t;
-out shmemport : mach_port_move_send_t
-);
-
-routine
-take_subset(
- j : job_t;
-out reqport : mach_port_move_send_t;
-out recvport : mach_port_move_receive_t;
-out jobs : pointer_t, dealloc;
-out ports : mach_port_move_send_array_t, dealloc
-);
-
-routine
-getsocket(
- j : job_t;
-out sockpath : name_t
-);
-
-skip; /* Formerly spawn. */
-
-skip; /* Formerly wait. */
-
-skip; /* Formerly uncork_fork. */
-
-routine
-swap_integer(
- j : job_t;
- inkey : vproc_gsk_t;
- outkey : vproc_gsk_t;
- inval : int64_t;
-out outval : int64_t
-);
-
-routine
-log(
- j : job_t;
- pri : integer_t;
- err : integer_t;
- message : logmsg_t
-);
-
-routine
-lookup_per_user_context(
- j : job_t;
- uid : uid_t;
-out userbport : mach_port_t
-);
-
-routine
-move_subset(
- j : job_t;
- targetport : mach_port_t;
- session : name_t;
- asport : mach_port_t;
- flags : uint64_t
-);
-
-routine
-swap_complex(
- j : job_t;
- inkey : vproc_gsk_t;
- outkey : vproc_gsk_t;
- inval : pointer_t;
-out outval : pointer_t, dealloc
-);
-
-routine
-log_drain(
- j : job_t;
-sreplyport rp : mach_port_make_send_once_t;
-out outval : pointer_t, dealloc
-);
-
-routine
-log_forward(
- j : job_t;
- inval : pointer_t
-);
-
-routine
-kickstart(
- j : job_t;
- label : name_t;
-out pid : pid_t;
- flags : natural_t
-);
-
-skip;
-
-routine
-lookup_children(
- j : job_t;
-out childports : mach_port_move_send_array_t, dealloc;
-out childnames : name_array_t, dealloc;
-out childprops : bootstrap_property_array_t, dealloc
-);
-
-routine
-switch_to_session(
- j : job_t;
- reqport : mach_port_t;
- session : name_t;
- asport : mach_port_t;
-out newbsport : mach_port_make_send_t
-);
-
-routine
-transaction_count_for_pid(
- j : job_t;
- pid : pid_t;
-out count : integer_t;
-out condemned : boolean_t
-);
-
-routine
-pid_is_managed(
- j : job_t;
- pid : pid_t;
-out managed : boolean_t
-);
-
-routine
-port_for_label(
- j : job_t;
- label : name_t;
-out jport : mach_port_make_send_t
-);
-
-routine
-init_session(
- j : job_t;
- session : name_t;
- asport : mach_port_t
-);
-
-routine
-set_security_session(
- j : job_t;
- uuid : uuid_t;
- asport : mach_port_t
-);
-
-skip; /* Formerly wait2. */
-
-routine
-event_source_check_in(
- j : job_t;
- stream : name_t;
- pingport : mach_port_make_send_once_t;
-out events : pointer_t, dealloc;
-out tokens : event_token_array_t
-);
-
-routine
-event_set_state(
- j : job_t;
- stream : name_t;
- token : uint64_t;
- state : boolean_t
-);
-
-routine
-spawn2(
- j : job_t;
-sreplyport rp : mach_port_make_send_once_t;
- job : pointer_t;
- asport : mach_port_t;
-out outpid : pid_t;
-out obsrvport : mach_port_move_receive_t
-);
-
-routine
-get_root_bootstrap(
- j : job_t;
-out rootbs : mach_port_move_send_t
-);
+++ /dev/null
-.Dd 1 May, 2009
-.Dt RC 8
-.Os Darwin
-.Sh NAME
-.Nm rc
-.Nd command script for boot
-.Sh SYNOPSIS
-.Nm rc
-.Nm rc.local
-.Sh DESCRIPTION
-.Nm rc.local
-is now unsupported and has been replaced with
-.Xr launchd 8 ,
-which bootstraps itself via the
-.Xr launchctl 1
-.Ar bootstrap
-subcommand to read in
-.Xr launchd 8
-jobs from the standard locations.
-.Sh SEE ALSO
-.Xr launchd 8 ,
-.Xr launchctl 1
+++ /dev/null
-##
-# Common setup for startup scripts.
-##
-# Copyright 1998-2002 Apple Computer, Inc.
-##
-
-#######################
-# Configure the shell #
-#######################
-
-##
-# Be strict
-##
-#set -e
-set -u
-
-##
-# Set command search path
-##
-PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/libexec:/System/Library/CoreServices; export PATH
-
-##
-# Set the terminal mode
-##
-#if [ -x /usr/bin/tset ] && [ -f /usr/share/misc/termcap ]; then
-# TERM=$(tset - -Q); export TERM
-#fi
-
-####################
-# Useful functions #
-####################
-
-##
-# Determine if the network is up by looking for any non-loopback
-# internet network interfaces.
-##
-CheckForNetwork()
-{
- local test
-
- if [ -z "${NETWORKUP:=}" ]; then
- test=$(ifconfig -a inet 2>/dev/null | sed -n -e '/127.0.0.1/d' -e '/0.0.0.0/d' -e '/inet/p' | wc -l)
- if [ "${test}" -gt 0 ]; then
- NETWORKUP="-YES-"
- else
- NETWORKUP="-NO-"
- fi
- fi
-}
-
-alias ConsoleMessage=echo
-
-##
-# Process management
-##
-GetPID ()
-{
- local program="$1"
- local pidfile="${PIDFILE:=/var/run/${program}.pid}"
- local pid=""
-
- if [ -f "${pidfile}" ]; then
- pid=$(head -1 "${pidfile}")
- if ! kill -0 "${pid}" 2> /dev/null; then
- echo "Bad pid file $pidfile; deleting."
- pid=""
- rm -f "${pidfile}"
- fi
- fi
-
- if [ -n "${pid}" ]; then
- echo "${pid}"
- return 0
- else
- return 1
- fi
-}
-
-##
-# Generic action handler
-##
-RunService ()
-{
- case $1 in
- start ) StartService ;;
- stop ) StopService ;;
- restart) RestartService ;;
- * ) echo "$0: unknown argument: $1";;
- esac
-}
-
-##########################
-# Get host configuration #
-##########################
-. /etc/hostconfig
+++ /dev/null
-#!/bin/sh
-##
-# Copyright 2002-2009 Apple Inc.
-#
-# This script configures NetBoot
-##
-
-. /etc/rc.common
-
-#
-# Define: NETBOOT_SHADOW
-# Purpose:
-# To change the behavior of the system when choosing a netboot shadow
-# to use.
-# Values:
-# -NETWORK- Try to use the network for the shadow file, if
-# that fails, use the local drive
-# -NETWORK_ONLY- Only use the network, fail if not available
-# -LOCAL- Use the local drive for the shadow file, if that
-# fails, use the network
-# -LOCAL_ONLY- Only use the local drive for the shadow, fail if
-# not available
-
-NETBOOT_MOUNT=/var/netboot
-NETBOOT_SHADOW=${NETBOOT_SHADOW:-NETWORK-}
-
-Failed()
-{
- echo rc.netboot: $1
- echo rc.netboot: $1 > /dev/console
- sleep 5
- exit 1
-}
-
-common_start()
-{
- netboot_dir=$1
- netboot_shadow=$2
- if [ "${netboot_dir}" = "" ] ; then
- Failed "netboot_dir is empty"
- fi
- if [ "${netboot_shadow}" = "" ] ; then
- Failed "netboot_shadow is empty"
- fi
- netboot_shadow="${netboot_dir}/${netboot_shadow}"
- if ! mkdir -p "${netboot_dir}" ; then
- Failed "create ${netboot_dir} failed"
- fi
- chmod 700 "${netboot_dir}"
- mount -u -o ro /
- root_device=$(mount | sed -n 's:/dev/\(.*\) on / .*:\1:p')
- case "${root_device}" in
- vn*)
- if ! touch "${netboot_shadow}" ; then
- Failed "create ${netboot_shadow} failed"
- fi
- chmod 600 "${netboot_shadow}"
- if ! /usr/libexec/vndevice shadow "/dev/r${root_device}" "${netboot_shadow}" ; then
- Failed "vndevice shadow failed"
- fi
- ;;
- "")
- Failed "root device unknown"
- ;;
- *)
- if ! touch "${netboot_shadow}" ; then
- Failed "failed to create shadow ${netboot_shadow}"
- fi
- chmod 600 "${netboot_shadow}"
- if ! /usr/bin/nbdst -recycle "${root_device}" "${netboot_shadow}" ; then
- Failed "nbdst failed"
- fi
- ;;
- esac
-}
-
-local_mount()
-{
- tries=0
- limit=11
- while [ $tries -lt $limit ]; do
- tries=$(( tries + 1 ))
- volinfo=`autodiskmount -F 2>/dev/null`
- if [ $? -ne 0 ]; then
- if [ $tries -lt $limit ]; then
- echo "Waiting for local drives..."
- echo "Waiting for local drives (retry ${tries}/$(( limit - 1 )))..." > /dev/console
- sleep 5
- else
- echo "autodiskmount -F found no local drives"
- return 1
- fi
- else
- tries=$limit
- fi
- done
- set ${volinfo}
- devname=$1
- fstype=$2
-
- mount -t "${fstype}" -o nosuid,nodev "/dev/${devname}" "${NETBOOT_MOUNT}" 2>&1
- if [ $? -ne 0 ]; then
- echo "mount of ${devname} failed"
- return 1
- fi
- common_start "${NETBOOT_MOUNT}/.com.apple.NetBootX" shadowfile
- return 0
-}
-
-network_mount()
-{
- mount_from=$(ipconfig netbootoption shadow_mount_path 2>&1)
- if [ $? -ne 0 ]; then
- echo "no network shadow mount path available"
- return 1
- fi
- shadow_path=$(ipconfig netbootoption shadow_file_path 2>&1)
- if [ $? -ne 0 ]; then
- echo "no network shadow file path available"
- return 1
- fi
- case "${mount_from}" in
- afp:*)
- fstype=afp
- kextload -v 0 /System/Library/Filesystems/AppleShare/asp_tcp.kext
- kextload -v 0 /System/Library/Filesystems/AppleShare/afpfs.kext
- ;;
- nfs:*) fstype=nfs;;
- *) echo "unknown network filesystem mount from ${mount_from}"
- return 1
- ;;
- esac
- mount -t "${fstype}" -o nobrowse "${mount_from}" "${NETBOOT_MOUNT}"
- if [ $? -ne 0 ]; then
- echo "mount -t ${fstype} -o nobrowse ${mount_from} ${NETBOOT_MOUNT} failed"
- return 1
- fi
- common_start "${NETBOOT_MOUNT}" "${shadow_path}"
- return 0
-}
-
-do_start()
-{
- case "${NETBOOT_SHADOW}" in
- -LOCAL_ONLY-)
- err=$(local_mount)
- if [ $? -ne 0 ]; then
- Failed "${err}"
- fi
- ;;
- -LOCAL-)
- err=$(local_mount)
- if [ $? -ne 0 ]; then
- err=$(network_mount)
- if [ $? -ne 0 ]; then
- Failed "Could not find a local or network drive"
- fi
- fi
- ;;
- -NETWORK_ONLY-)
- err=$(network_mount)
- if [ $? -ne 0 ]; then
- Failed "${err}"
- fi
- ;;
-
- *)
- err=$(network_mount)
- if [ $? -ne 0 ]; then
- err=$(local_mount)
- if [ $? -ne 0 ]; then
- Failed "Could not find a network or local drive"
- fi
- fi
- ;;
- esac
-
-}
-
-do_init()
-{
- # attach the shadow file to the root disk image
- do_start
-
- # make sure the root filesystem is clean
- fsck -p || fsck -fy || Failed "Could not clean root filesystem"
-
- # make it writable
- mount -uw /
-
- # adjust /private/var/vm to point to the writable area (if not diskless)
- swapdir=/private/var/vm
- mounted_from=$(mount | sed -n 's:\(.*\) on .*/var/netboot.*:\1:p')
- case "${mounted_from}" in
- /dev/*)
- netboot_dir="${NETBOOT_MOUNT}/.com.apple.NetBootX"
- if [ -d "${netboot_dir}" ]; then
- rm -rf "${swapdir}"
- ln -s "${netboot_dir}" "${swapdir}"
- fi
- ;;
- *)
- ;;
- esac
-
- # set the ComputerName based on what the NetBoot server told us it was
- machine_name=$(ipconfig netbootoption machine_name 2>&1)
- if [ $? -ne 0 ]; then
- echo "no machine name option available"
- else
- echo "Setting ComputerName to ${machine_name}"
- scutil --set ComputerName "${machine_name}"
- fi
-}
-
-
-if [ $# -lt 1 ] ; then
- exit 0
-fi
-
-command=$1
-
-shift
-
-case "${command}" in
- init)
- do_init $@
- ;;
-esac
-
-##
-# Exit
-##
-exit 0
+++ /dev/null
-#ifndef _REBOOT2_H_
-#define _REBOOT2_H_
-/*
- * Copyright (c) 2007 Apple 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 <sys/cdefs.h>
-#include <sys/reboot.h>
-#include <stdint.h>
-
-__BEGIN_DECLS
-
-#define RB2_FULLREBOOT 0x8000000000000000llu
-
-/* Returns NULL on success. Not NULL on failure */
-
-__attribute__((visibility("default"))) void *reboot2(uint64_t flags);
-
-__END_DECLS
-
-#endif
+++ /dev/null
-#ifndef _VPROC_H_
-#define _VPROC_H_
-/*
- * Copyright (c) 2006 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 <sys/cdefs.h>
-#include <sys/types.h>
-#include <Availability.h>
-
-#ifndef VPROC_HAS_TRANSACTIONS
- #define VPROC_HAS_TRANSACTIONS
-#endif
-
-__BEGIN_DECLS
-
-#pragma GCC visibility push(default)
-
-typedef void * vproc_err_t;
-
-typedef struct vproc_s * vproc_t;
-typedef void * vprocmgr_t;
-
-const char *vproc_strerror(vproc_err_t r);
-
-/*!
- * @header vproc
- *
- * Processes have two reference counts associated with them:
- *
- * Transactions Tracks unfinished work. For example: saving a modified
- * document.
- * Standby Tracks outstanding callbacks from external subsystems.
- *
- * Descriptive aliases:
- *
- * A process with no outstanding transactions is called "clean."
- * A process with outstanding transactions is called "dirty."
- * A process with no standby work is called "idle."
- *
- * Sometimes, the operating system needs processes to exit. Unix has two
- * primary signals to kill applications:
- *
- * SIGKILL Not catchable by the application.
- * SIGTERM Catchable by the application.
- *
- * If a process is clean, the operating system is free to SIGKILL it at
- * shutdown or logout. This behavior is opt in.
- *
- * If a process is clean and idle, the operating system may send SIGKILL after
- * a application specified timeout. This behavior is opt in.
- *
- * If a process is dirty and idle, the operating system may send SIGTERM after
- * a application specified timeout. This behavior is opt in.
- *
- *
- * launchd jobs should update their property lists accordingly.
- *
- * We plan to have LaunchServices use private methods to coordinate
- * whether GUI applications have opted into this design.
- */
-
-/*!
- * @typedef vproc_transaction_t
- *
- * @abstract
- * An opaque handle used to track outstanding transactions.
- */
-typedef struct vproc_transaction_s *vproc_transaction_t;
-
-/*!
- * @function vproc_transaction_begin
- *
- * @param virtual_proc
- * This is meant for future API improvements. Pass NULL for now.
- *
- * @result
- * Returns an opaque handle to be passed to vproc_transaction_end().
- *
- * @abstract
- * Call this API before creating data that needs to be saved via I/O later.
- */
-vproc_transaction_t
-vproc_transaction_begin(vproc_t virtual_proc) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_5_0);
-
-/*!
- * @function vproc_transaction_end
- *
- * @param virtual_proc
- * This is meant for future API improvements. Pass NULL for now.
- *
- * @param handle
- * The handle previously created with vproc_transaction_begin().
- *
- * @abstract
- * Call this API after the data has either been flushed or otherwise resolved.
- *
- * @discussion
- * Calling this API with the same handle more than once is undefined.
- */
-void
-vproc_transaction_end(vproc_t virtual_proc, vproc_transaction_t handle) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_5_0);
-
-/*!
- * @typedef vproc_standby_t
- *
- * @abstract
- * An opaque handle used to track outstanding standby requests.
- */
-typedef struct vproc_standby_s *vproc_standby_t;
-
-/*!
- * @function vproc_standby_begin
- *
- * @param virtual_proc
- * This is meant for future API improvements. Pass NULL for now.
- *
- * @result
- * Returns an opaque handle to be passed to vproc_standby_end().
- *
- * @abstract
- * Call this API before registering notifications. For example: timers network
- * state change, or when monitoring keyboard/mouse events.
- *
- * @discussion
- * This API is undefined and is currently a no-op.
- */
-vproc_standby_t
-vproc_standby_begin(vproc_t virtual_proc) __OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_NA);
-
-/*!
- * @function vproc_standby_end
- *
- * @param virtual_proc
- * This is meant for future API improvements. Pass NULL for now.
- *
- * @param handle
- * The handle previously created with vproc_standby_begin().
- *
- * @abstract
- * Call this API when deregistering notifications.
- *
- * @discussion
- * Calling this API with the same handle more than once is undefined.
- * This API is undefined and is currently a no-op.
- */
-void
-vproc_standby_end(vproc_t virtual_proc, vproc_standby_t handle) __OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_NA);
-
-#pragma GCC visibility pop
-
-__END_DECLS
-
-#endif
+++ /dev/null
-#ifndef _VPROC_INTERNAL_H_
-#define _VPROC_INTERNAL_H_
-/*
- * Copyright (c) 2006-2007 Apple 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 <mach/mach.h>
-#include <sys/queue.h>
-#include <sys/time.h>
-#include <stdarg.h>
-#include <sys/syscall.h>
-#include <bsm/audit.h>
-#include "launch.h"
-#include "bootstrap_priv.h"
-#include "vproc.h"
-
-typedef char * _internal_string_t;
-typedef char * logmsg_t;
-typedef pid_t * pid_array_t;
-typedef mach_port_t vproc_mig_t;
-typedef uint64_t event_token_array_t[1024];
-
-#define VPROC_SHMEM_EXITING 0x1
-
-struct vproc_shmem_s {
- int32_t vp_shmem_transaction_cnt;
- int32_t vp_shmem_standby_cnt;
- uint32_t vp_shmem_standby_timeout;
- int32_t vp_shmem_flags;
-};
-
-#if defined(protocol_vproc_MSG_COUNT) || defined (xpc_domain_MSG_COUNT) || defined (xpc_events_MSG_COUNT)
-/* HACK */
-#include "launchd_core_logic.h"
-#endif
-
-#define VPROC_ERR_TRY_PER_USER 1099
-
-#pragma GCC visibility push(default)
-
-vproc_err_t _vprocmgr_init(const char *session_type);
-vproc_err_t _vproc_post_fork_ping(void);
-
-#if !TARGET_OS_EMBEDDED
- #define _audit_session_self(v) (mach_port_t)syscall(SYS_audit_session_self)
- #define _audit_session_join(s) (au_asid_t)syscall(SYS_audit_session_join, session)
-#else
- #define _audit_session_self(v) MACH_PORT_NULL
- #define _audit_session_join(s) 0
-#endif
-
-#define SPAWN_HAS_PATH 0x0001
-#define SPAWN_HAS_WDIR 0x0002
-#define SPAWN_HAS_UMASK 0x0004
-#define SPAWN_WANTS_WAIT4DEBUGGER 0x0008
-
-kern_return_t
-_vproc_grab_subset(mach_port_t bp, mach_port_t *reqport, mach_port_t *rcvright, launch_data_t *outval,
- mach_port_array_t *ports, mach_msg_type_number_t *portCnt);
-
-kern_return_t _vprocmgr_getsocket(name_t);
-
-struct logmsg_s {
- union {
- STAILQ_ENTRY(logmsg_s) sqe;
- uint64_t __pad;
- };
- int64_t when;
- pid_t from_pid;
- pid_t about_pid;
- uid_t sender_uid;
- gid_t sender_gid;
- int err_num;
- int pri;
- union {
- const char *from_name;
- uint64_t from_name_offset;
- };
- union {
- const char *about_name;
- uint64_t about_name_offset;
- };
- union {
- const char *session_name;
- uint64_t session_name_offset;
- };
- union {
- const char *msg;
- uint64_t msg_offset;
- };
- uint64_t obj_sz;
- char data[0];
-};
-
-
-vproc_err_t _vprocmgr_log_forward(mach_port_t mp, void *data, size_t len);
-
-
-kern_return_t
-bootstrap_info(mach_port_t bp,
- name_array_t *service_names,
- mach_msg_type_number_t *service_namesCnt,
- name_array_t *service_jobs,
- mach_msg_type_number_t *service_jobsCnt,
- bootstrap_status_array_t *service_active,
- mach_msg_type_number_t *service_activeCnt,
- uint64_t flags);
-
-#pragma GCC visibility pop
-
-#endif
+++ /dev/null
-#ifndef _VPROC_PRIVATE_H_
-#define _VPROC_PRIVATE_H_
-/*
- * Copyright (c) 2006 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 <Availability.h>
-#include <sys/types.h>
-#include <sys/cdefs.h>
-#include <sys/syslog.h>
-#include <sys/time.h>
-#include <stdbool.h>
-#include <launch.h>
-#include <vproc.h>
-#include <uuid/uuid.h>
-#include <servers/bootstrap.h>
-
-#ifndef VPROC_HAS_TRANSACTIONS
- #define VPROC_HAS_TRANSACTIONS
-#endif
-
-__BEGIN_DECLS
-
-#pragma GCC visibility push(default)
-
-/* DO NOT use this. This is a hack for 'launchctl' */
-#define VPROC_MAGIC_UNLOAD_SIGNAL 0x4141504C
-/* DO NOT use this. This is a hack for 'loginwindow' */
-#define VPROC_MAGIC_TRYKILL_SIGNAL 0x6161706C
-
-typedef void (*_vproc_transaction_callout)(void);
-
-typedef enum {
- VPROC_GSK_LAST_EXIT_STATUS = 1,
- VPROC_GSK_GLOBAL_ON_DEMAND,
- VPROC_GSK_MGR_UID,
- VPROC_GSK_MGR_PID,
- VPROC_GSK_IS_MANAGED,
- VPROC_GSK_MGR_NAME,
- VPROC_GSK_BASIC_KEEPALIVE,
- VPROC_GSK_START_INTERVAL,
- VPROC_GSK_IDLE_TIMEOUT,
- VPROC_GSK_EXIT_TIMEOUT,
- VPROC_GSK_ENVIRONMENT,
- VPROC_GSK_ALLJOBS,
- VPROC_GSK_GLOBAL_LOG_MASK,
- VPROC_GSK_GLOBAL_UMASK,
- VPROC_GSK_ABANDON_PROCESS_GROUP,
- VPROC_GSK_TRANSACTIONS_ENABLED,
- VPROC_GSK_WEIRD_BOOTSTRAP,
- VPROC_GSK_WAITFORDEBUGGER,
- VPROC_GSK_SECURITYSESSION,
- VPROC_GSK_SHUTDOWN_DEBUGGING,
- VPROC_GSK_VERBOSE_BOOT,
- VPROC_GSK_PERUSER_SUSPEND,
- VPROC_GSK_PERUSER_RESUME,
- VPROC_GSK_JOB_OVERRIDES_DB,
- VPROC_GSK_JOB_CACHE_DB,
- VPROC_GSK_EMBEDDEDROOTEQUIVALENT,
-} vproc_gsk_t;
-
-typedef kern_return_t (*vproc_helper_recv_ping_t)(mach_port_t, audit_token_t);
-
-/* Give to dispatch_mig_server(). */
-boolean_t vprocmgr_helper_server_routine_for_dispatch(mach_msg_header_t *message, mach_msg_header_t *reply);
-
-/* Give to dispatch_mig_server(). */
-extern size_t vprocmgr_helper_maxmsgsz;
-
-typedef unsigned int vproc_flags_t;
-/* For _vproc_kickstart_by_label() -- instructs launchd to kickstart the job to stall before exec(2). */
-#define VPROCFLAG_STALL_JOB_EXEC 1 << 1
-
-vproc_t vprocmgr_lookup_vproc(const char *label);
-vproc_t vproc_retain(vproc_t vp);
-void vproc_release(vproc_t vp);
-
-vproc_err_t vproc_swap_integer(vproc_t vp, vproc_gsk_t key, int64_t *inval, int64_t *outval);
-vproc_err_t vproc_swap_complex(vproc_t vp, vproc_gsk_t key, launch_data_t inval, launch_data_t *outval);
-vproc_err_t vproc_swap_string(vproc_t vp, vproc_gsk_t key, const char *instr, char **outstr);
-
-vproc_err_t _vproc_get_last_exit_status(int *wstatus);
-vproc_err_t _vproc_set_global_on_demand(bool val);
-
-typedef void (*_vprocmgr_log_drain_callback_t)(struct timeval *when, pid_t from_pid, pid_t about_pid, uid_t sender_uid, gid_t sender_gid, int priority, const char *from_name, const char *about_name, const char *session_name, const char *msg);
-
-vproc_err_t _vprocmgr_log_drain(vproc_t vp, pthread_mutex_t *optional_mutex_around_callback, _vprocmgr_log_drain_callback_t func);
-
-vproc_err_t _vproc_send_signal_by_label(const char *label, int sig);
-vproc_err_t _vproc_kickstart_by_label(const char *label, pid_t *out_pid, mach_port_t *out_port_name, mach_port_t *out_obsrvr_port, vproc_flags_t flags);
-
-void _vproc_log(int pri, const char *msg, ...) __attribute__((format(printf, 2, 3)));
-void _vproc_log_error(int pri, const char *msg, ...) __attribute__((format(printf, 2, 3)));
-void _vproc_logv(int pri, int err, const char *msg, va_list ap) __attribute__((format(printf, 3, 0)));
-
-#define VPROCMGR_SESSION_LOGINWINDOW "LoginWindow"
-#define VPROCMGR_SESSION_BACKGROUND "Background"
-#define VPROCMGR_SESSION_AQUA "Aqua"
-#define VPROCMGR_SESSION_STANDARDIO "StandardIO"
-#define VPROCMGR_SESSION_SYSTEM "System"
-
-#define XPC_DOMAIN_TYPE_SYSTEM "XPCSystem"
-#define XPC_DOMAIN_TYPE_PERUSER "XPCPerUser"
-#define XPC_DOMAIN_TYPE_PERSESSION "XPCPerSession"
-#define XPC_DOMAIN_TYPE_PERAPPLICATION "XPCPerApplication"
-
-vproc_err_t _vprocmgr_move_subset_to_user(uid_t target_user, const char *session_type, uint64_t flags);
-vproc_err_t _vprocmgr_switch_to_session(const char *target_session, vproc_flags_t flags);
-vproc_err_t _vprocmgr_detach_from_console(vproc_flags_t flags);
-
-__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
-void
-_vproc_standby_begin(void);
-
-__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
-void
-_vproc_standby_end(void);
-
-__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
-size_t
-_vproc_standby_count(void);
-
-__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
-size_t
-_vproc_standby_timeout(void);
-
-__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
-kern_return_t
-_vproc_transaction_count_for_pid(pid_t p, int32_t *count, bool *condemned);
-
-__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
-bool
-_vproc_pid_is_managed(pid_t p);
-
-__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
-void
-_vproc_transaction_try_exit(int status);
-
-__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_5_0)
-void
-_vproc_transaction_begin(void);
-
-__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_5_0)
-void
-_vproc_transaction_end(void);
-
-__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
-size_t
-_vproc_transaction_count(void);
-
-int32_t *
-_vproc_transaction_ptr(void);
-
-void
-_vproc_transaction_set_callouts(_vproc_transaction_callout gone2zero, _vproc_transaction_callout gonenonzero);
-
-bool
-vprocmgr_helper_check_in(const char *sysname, mach_port_t rp, launch_data_t *events, uint64_t *tokens);
-
-bool
-vprocmgr_helper_event_set_state(const char *sysname, uint64_t token, bool state);
-
-void
-vprocmgr_helper_register(vproc_helper_recv_ping_t callout);
-
-#pragma GCC visibility pop
-
-__END_DECLS
-
-#endif
+++ /dev/null
-.Dd April 6, 2004
-.Dt WAIT4PATH 1
-.Os "Mac OS X" 10.4
-.Sh NAME
-.Nm wait4path
-.Nd wait for given path to show up in the namespace
-.Sh SYNOPSIS
-.Nm
-.Ao Ar path Ac
-.Sh DESCRIPTION
-The
-.Nm
-program simply checks to see if the given path exists, and if so, it exits. Otherwise, it sleeps until the mount table is updated and checks again. The program will loop indefinitely until the path shows up in the file system namespace.
+++ /dev/null
-/*
- * Copyright (c) 2005 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 <sys/types.h>
-#include <sys/stat.h>
-#include <sys/event.h>
-#include <sys/time.h>
-#include <sys/param.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <errno.h>
-#include <string.h>
-
-int main(int argc, char *argv[])
-{
- int kq = kqueue();
- struct kevent kev;
- struct stat sb;
-
- if (argc != 2) {
- fprintf(stderr, "usage: %s <object on mount point>\n", argv[0]);
- exit(EXIT_FAILURE);
- }
-
- EV_SET(&kev, 0, EVFILT_FS, EV_ADD, 0, 0, 0);
-
- if (kevent(kq, &kev, 1, NULL, 0, NULL) == -1) {
- fprintf(stderr, "adding EVFILT_FS to kqueue failed: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- if (stat(argv[1], &sb) == 0) {
- exit(EXIT_SUCCESS);
- }
-
- for (;;) {
- kevent(kq, NULL, 0, &kev, 1, NULL);
- if (stat(argv[1], &sb) == 0) {
- break;
- }
- }
-
- exit(EXIT_SUCCESS);
-}
+++ /dev/null
-#include <sys/event.h>
-#include <sys/time.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <assert.h>
-
-static int kq = -1;
-
-time_t
-now_plus_n(int n)
-{
- time_t later = time(NULL);
-
- return later + n;
-}
-
-void
-add_abs_timer(int n)
-{
- struct kevent kev;
-
- EV_SET(&kev, n * 100000, EVFILT_TIMER, EV_ADD, NOTE_SECONDS | NOTE_ABSOLUTE, now_plus_n(n), (void *)n);
-
- assert(kevent(kq, &kev, 1, NULL, 0, NULL) == 0);
-}
-
-int
-main(void)
-{
- struct kevent kev;
-
- assert((kq = kqueue()) != -1);
-
- add_abs_timer(2);
- add_abs_timer(3);
- add_abs_timer(4);
- add_abs_timer(5);
- add_abs_timer(6);
-
- for (;;) {
- assert(kevent(kq, NULL, 0, &kev, 1, NULL) == 1);
- fprintf(stdout, "kev.ident == %ld kev.udata == %p\n", kev.ident, kev.udata);
- add_abs_timer((int)kev.udata);
- }
-
- exit(EXIT_SUCCESS);
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>com.apple.launchd.test.StartCalendarInterval</string>
- <key>ProgramArguments</key>
- <array>
- <string>echo</string>
- <string>StartCalendarInterval</string>
- </array>
- <key>StartCalendarInterval</key>
- <dict>
- </dict>
-</dict>
-</plist>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>com.apple.launchd.test.StartInterval</string>
- <key>ProgramArguments</key>
- <array>
- <string>echo</string>
- <string>StartInterval</string>
- </array>
- <key>StartInterval</key>
- <integer>3</integer>
-</dict>
-</plist>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>com.apple.launchd.test.badexec</string>
- <key>ProgramArguments</key>
- <array>
- <string>/</string>
- </array>
- <key>OnDemand</key>
- <false/>
-</dict>
-</plist>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>com.apple.launchd.test.badexit</string>
- <key>ProgramArguments</key>
- <array>
- <string>false</string>
- </array>
- <key>OnDemand</key>
- <false/>
-</dict>
-</plist>
+++ /dev/null
-#include <mach/port.h>
-#include <servers/bootstrap.h>
-#include <unistd.h>
-#include <launch.h>
-#include <launch_priv.h>
-#include <string.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-
-int main(void)
-{
- mach_port_t oldbsport = bootstrap_port;
- pid_t p;
-
- if (getuid() != 0) {
- fprintf(stderr, "This test tool must be run as root.\n");
- exit(EXIT_FAILURE);
- }
-
- p = create_and_switch_to_per_session_launchd("www");
-
- if (p > 0) {
- fprintf(stdout, "Our PID: %d\n", getpid());
- fprintf(stdout, "Per session launchd PID: %d\n", p);
- fprintf(stdout, "Old bootstrap port: 0x%x\n", oldbsport);
- fprintf(stdout, "New bootstrap port: 0x%x\n", bootstrap_port);
- for (;;) {
- pause();
- }
- } else if (p == -1) {
- fprintf(stderr, "create_and_switch_to_per_session_launchd() failed: %s\n", strerror(errno));
- } else if (p == 0) {
- fprintf(stderr, "create_and_switch_to_per_session_launchd() returned zero?!?\n");
- }
- exit(EXIT_FAILURE);
-}
+++ /dev/null
-#include <launch.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-void print_mach_service(launch_data_t obj, const char *key, void *context)
-{
- if (launch_data_get_type(obj) == LAUNCH_DATA_MACHPORT) {
- fprintf(stdout, "%s: %d\n", key, launch_data_get_machport(obj));
- mach_port_deallocate(mach_task_self(), launch_data_get_machport(obj));
- mach_port_mod_refs(mach_task_self(), launch_data_get_machport(obj), MACH_PORT_RIGHT_RECEIVE, -1);
- } else {
- fprintf(stdout, "%s: not a mach port\n", key);
- }
-}
-
-int main(void)
-{
- launch_data_t resp, tmp, msg = launch_data_new_string(LAUNCH_KEY_CHECKIN);
-
- resp = launch_msg(msg);
-
- if (resp == NULL) {
- fprintf(stderr, "launch_msg(): %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO) {
- errno = launch_data_get_errno(resp);
- fprintf(stderr, "launch_msg() response: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_MACHSERVICES);
-
- if (tmp == NULL) {
- fprintf(stderr, "no mach services found!\n");
- exit(EXIT_FAILURE);
- }
-
- launch_data_dict_iterate(tmp, print_mach_service, NULL);
-
- sleep(1);
- exit(EXIT_SUCCESS);
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>launchd.testing.mach_via_launchd</string>
- <key>ProgramArguments</key>
- <array>
- <string>./mach_via_launchd</string>
- </array>
- <key>OnDemand</key>
- <false/>
- <key>MachServices</key>
- <dict>
- <key>com.apple.launchd.test.service</key>
- <true/>
- </dict>
-</dict>
-</plist>
+++ /dev/null
-/*
- * <rdar://problem/4437060> EVFILT_READ doesn't fire reliably
- */
-
-#include <sys/types.h>
-#include <sys/time.h>
-#include <sys/event.h>
-#include <sys/socket.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-#include <errno.h>
-
-static void do_parent(int thefd);
-static void do_child(int thefd);
-
-int main(void)
-{
- int sp[2];
-
- if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, sp)) {
- fprintf(stderr, "socketpair(): %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- switch (fork()) {
- case -1:
- fprintf(stderr, "fork(): %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- case 0:
- close(sp[0]);
- do_child(sp[1]);
- break;
- default:
- close(sp[1]);
- do_parent(sp[0]);
- break;
- }
-
- exit(EXIT_SUCCESS);
-}
-
-void
-do_child(int thefd)
-{
- char junk = '\0';
-
- for (;;) {
- if (-1 == write(thefd, &junk, sizeof(junk))) {
- fprintf(stderr, "%d: write(): %s\n", __LINE__, strerror(errno));
- exit(EXIT_FAILURE);
- }
- if (-1 == read(thefd, &junk, sizeof(junk))) {
- fprintf(stderr, "%d: read(): %s\n", __LINE__, strerror(errno));
- exit(EXIT_FAILURE);
- }
- }
-}
-
-void
-do_parent(int thefd)
-{
- struct timespec timeout = { 5, 0 };
- int iter = 0, kq;
- struct kevent kev;
- char junk = '\0';
-
- if (-1 == (kq = kqueue())) {
- fprintf(stderr, "kqueue(): %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- EV_SET(&kev, thefd, EVFILT_READ, EV_ADD, 0, 0, NULL);
-
- if (-1 == kevent(kq, &kev, 1, NULL, 0, NULL)) {
- fprintf(stderr, "%d: kevent(): %s\n", __LINE__, strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- for (;;) {
- switch (kevent(kq, NULL, 0, &kev, 1, &timeout)) {
- case -1:
- fprintf(stderr, "%d: kevent(): %s\n", __LINE__, strerror(errno));
- exit(EXIT_FAILURE);
- case 0:
- fprintf(stderr, "After %d iterations, 4437060 still exists!\n", iter);
- exit(EXIT_FAILURE);
- case 1:
- break;
- default:
- fprintf(stderr, "kevent should only return -1, 0 or 1 for this case!\n");
- exit(EXIT_FAILURE);
- }
-
- if (kev.filter != EVFILT_READ) {
- fprintf(stderr, "kevent should return EVFILT_READ!\n");
- exit(EXIT_FAILURE);
- }
-
- if (-1 == read(thefd, &junk, sizeof(junk))) {
- fprintf(stderr, "read(): %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
- if (-1 == write(thefd, &junk, sizeof(junk))) {
- fprintf(stderr, "%d: write(): %s\n", __LINE__, strerror(errno));
- exit(EXIT_FAILURE);
- }
- iter++;
- }
-}
+++ /dev/null
-#include <sys/types.h>
-#include <sys/event.h>
-#include <sys/time.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-void
-test5225889(int first, int second)
-{
- struct timeval tvs, tve, tvd;
- struct timespec timeout = { 30, 0 };
- struct kevent kev;
- int r, kq = kqueue();
-
- fprintf(stdout, "First timer %i being updated to %i.\n", first, second);
-
- assert(kq != -1);
-
- EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, first, NULL);
- r = kevent(kq, &kev, 1, NULL, 0, NULL);
- assert(r != -1);
-
- EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, second, NULL);
- r = kevent(kq, &kev, 1, NULL, 0, NULL);
- assert(r != -1);
-
- gettimeofday(&tvs, NULL);
- r = kevent(kq, NULL, 0, &kev, 1, &timeout);
- gettimeofday(&tve, NULL);
-
- timersub(&tve, &tvs, &tvd);
-
- fprintf(stdout, "Waited %lu seconds for kevent() to return.\n", tvd.tv_sec);
-
- switch (r) {
- case 1:
- assert(kev.data == second);
- assert(tvd.tv_sec >= second);
- break;
- case -1:
- case 0:
- default:
- fprintf(stderr, "Bug 5225889 still exists!\n");
- exit(EXIT_FAILURE);
- }
-}
-
-int
-main(void)
-{
- test5225889(5, 10);
- test5225889(10, 5);
-
- fprintf(stdout, "Finished.\n");
-
- exit(EXIT_SUCCESS);
-}
+++ /dev/null
-/*
- * <rdar://problem/4038866> Lots of hangs in GetLaunchDaemonService state
- */
-
-#include <sys/types.h>
-#include <sys/time.h>
-#include <sys/event.h>
-#include <sys/socket.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-#include <errno.h>
-
-static void do_parent(int thefd);
-static void do_child(int thefd);
-
-int main(void)
-{
- int sp[2];
-
- if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, sp)) {
- fprintf(stderr, "socketpair(): %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- switch (fork()) {
- case -1:
- fprintf(stderr, "fork(): %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- case 0:
- close(sp[0]);
- do_child(sp[1]);
- break;
- default:
- close(sp[1]);
- do_parent(sp[0]);
- break;
- }
-
- exit(EXIT_SUCCESS);
-}
-
-static void do_child(int thefd)
-{
- char buf[500000];
- fd_set rfds;
- int r, readhunks = 2;
-
- if (-1 == fcntl(thefd, F_SETFL, O_NONBLOCK)) {
- fprintf(stderr, "fcntl(): %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- for (;;) {
- if (2 == readhunks) {
- if (-1 == write(thefd, buf, 1)) {
- fprintf(stderr, "%d: write(): %s\n", __LINE__, strerror(errno));
- exit(EXIT_FAILURE);
- }
- readhunks = 0;
- }
- r = read(thefd, buf, sizeof(buf));
-
- if (-1 == r && errno != EAGAIN) {
- fprintf(stderr, "%d: read(): %s\n", __LINE__, strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- if (r > 0)
- readhunks++;
-
- if (readhunks == 2)
- continue;
-
- FD_ZERO(&rfds);
- FD_SET(thefd, &rfds);
-
- select(thefd + 1, &rfds, NULL, NULL, NULL);
- }
-}
-
-static void do_parent(int thefd)
-{
- struct timespec timeout = { 1, 0 };
- char buf[500000];
- struct kevent kev;
- int iter = 0, kq = kqueue();
-
- if (-1 == (kq = kqueue())) {
- fprintf(stderr, "kqueue(): %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- if (-1 == fcntl(thefd, F_SETFL, O_NONBLOCK)) {
- fprintf(stderr, "fcntl(): %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- EV_SET(&kev, thefd, EVFILT_READ, EV_ADD, 0, 0, NULL);
-
- if (-1 == kevent(kq, &kev, 1, NULL, 0, NULL)) {
- fprintf(stderr, "%d: kevent(): %s\n", __LINE__, strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- for (;;) {
- switch (kevent(kq, NULL, 0, &kev, 1, &timeout)) {
- case -1:
- fprintf(stderr, "%d: kevent(): %s\n", __LINE__, strerror(errno));
- exit(EXIT_FAILURE);
- case 0:
- fprintf(stderr, "After %d iterations, 4038866 still exists!\n", iter);
- exit(EXIT_FAILURE);
- case 1:
- break;
- default:
- fprintf(stderr, "kevent should only return -1, 0 or 1 for this case!\n");
- exit(EXIT_FAILURE);
- }
-
- switch (kev.filter) {
- case EVFILT_READ:
- if (-1 == read(thefd, buf, sizeof(buf))) {
- fprintf(stderr, "read(): %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
- if (-1 == write(thefd, buf, sizeof(buf))) {
- fprintf(stderr, "%d: write(): %s\n", __LINE__, strerror(errno));
- exit(EXIT_FAILURE);
- }
- EV_SET(&kev, thefd, EVFILT_WRITE, EV_ADD, 0, 0, NULL);
- if (-1 == kevent(kq, &kev, 1, NULL, 0, NULL)) {
- fprintf(stderr, "%d: kevent(): %s\n", __LINE__, strerror(errno));
- exit(EXIT_FAILURE);
- }
- break;
- case EVFILT_WRITE:
- if (-1 == write(thefd, buf, 1)) {
- fprintf(stderr, "%d: write(): %s\n", __LINE__, strerror(errno));
- exit(EXIT_FAILURE);
- }
- EV_SET(&kev, thefd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
- if (-1 == kevent(kq, &kev, 1, NULL, 0, NULL)) {
- fprintf(stderr, "%d: kevent(): %s\n", __LINE__, strerror(errno));
- exit(EXIT_FAILURE);
- }
- break;
- default:
- fprintf(stderr, "kevent filter isn't EVFILT_READ or EVFILT_WRITE!\n");
- exit(EXIT_FAILURE);
- }
- iter++;
- }
-}
+++ /dev/null
-/*
- * <rdar://problem/4389914> 8G1153: Cannot SSH into machine despite Remote Login being checked
- */
-
-#include <sys/types.h>
-#include <sys/time.h>
-#include <sys/event.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-#include <errno.h>
-#include <assert.h>
-
-static void do_parent(int thefd);
-static void do_child(int thefd);
-
-int main(void)
-{
- int sp[2];
- pid_t p;
-
- assert(socketpair(AF_UNIX, SOCK_STREAM, 0, sp) != -1);
-
- assert((p = fork()) != -1);
-
- if (p == 0) {
- assert(close(sp[0]) != -1);
- do_child(sp[1]);
- } else {
- assert(close(sp[1]) != -1);
- do_parent(sp[0]);
- }
-
- exit(EXIT_SUCCESS);
-}
-
-static int
-alloc_random_fd(void)
-{
- struct sockaddr_in ina;
- int fd;
-
- memset(&ina, 0, sizeof(ina));
- ina.sin_family = AF_INET;
- assert((fd = socket(PF_INET, SOCK_STREAM, 0)) != -1);
- assert(bind(fd, (struct sockaddr *)&ina, sizeof(ina)) != -1);
- assert(listen(fd, SOMAXCONN) != -1);
-
- return fd;
-}
-
-static int total_fds_sent = 0;
-
-void
-send_fds(int thefd)
-{
- struct cmsghdr *cm = NULL;
- struct msghdr mh;
- struct iovec iov;
- size_t sentctrllen = 0;
- int fdcnt = (rand() % 223) + 1; /* 223 is prime */
- int r, i, fds[fdcnt];
-
- memset(&mh, 0, sizeof(mh));
-
- iov.iov_base = &fdcnt;
- iov.iov_len = sizeof(fdcnt);
- mh.msg_iov = &iov;
- mh.msg_iovlen = 1;
-
- for (i = 0; i < fdcnt; i++) {
- fds[i] = alloc_random_fd();
- }
-
- sentctrllen = mh.msg_controllen = CMSG_SPACE(fdcnt * sizeof(int));
-
- mh.msg_control = cm = alloca(mh.msg_controllen);
-
- memset(cm, 0, mh.msg_controllen);
-
- cm->cmsg_len = CMSG_LEN(fdcnt * sizeof(int));
- cm->cmsg_level = SOL_SOCKET;
- cm->cmsg_type = SCM_RIGHTS;
-
- memcpy(CMSG_DATA(cm), fds, fdcnt * sizeof(int));
-
- if (sendmsg(thefd, &mh, 0) == -1) {
- fprintf(stderr, "Child: sendmsg(): %s\n", strerror(errno));
- fprintf(stderr, "Child: Tried to send %d fds\n", fdcnt);
- fprintf(stderr, "Child: Total FDs sent: %d\n", total_fds_sent);
- sleep(1);
- exit(EXIT_FAILURE);
- }
- total_fds_sent += fdcnt;
-
- assert(sentctrllen == mh.msg_controllen);
-
- r = read(thefd, &i, sizeof(i));
- assert(r != -1);
- assert(r != 0);
-
- for (i = 0; i < fdcnt; i++) {
- assert(close(fds[i]) != -1);
- }
-}
-
-void
-do_child(int thefd)
-{
- for (;;) {
- send_fds(thefd);
- }
-}
-
-static int total_fds_received = 0;
-
-static bool
-fetch_and_check_fds(int thefd)
-{
- struct cmsghdr *cm = alloca(4096);
- struct msghdr mh;
- struct iovec iov;
- int r, i, *fds, fdcnt = 0, sentfds;
-
- memset(&mh, 0, sizeof(mh));
-
- iov.iov_base = &fdcnt;
- iov.iov_len = sizeof(fdcnt);
- mh.msg_iov = &iov;
- mh.msg_iovlen = 1;
- mh.msg_control = cm;
- mh.msg_controllen = 4096;
-
- r = recvmsg(thefd, &mh, 0);
- assert(r != -1);
- assert(r != 0);
- assert(!(mh.msg_flags & MSG_CTRUNC));
- assert(mh.msg_controllen > 0);
-
- fds = (int *)CMSG_DATA(cm);
- sentfds = (mh.msg_controllen - sizeof(struct cmsghdr)) / sizeof(int);
-
- if (sentfds != fdcnt) {
- fprintf(stderr, "%d FDs sent, %d actually received.\n", fdcnt, sentfds);
- return false;
- }
-
- total_fds_received += fdcnt;
-
- for (i = 0; i < fdcnt; i++) {
- assert(close(fds[i]) != -1);
- }
-
- r = write(thefd, &fdcnt, sizeof(fdcnt));
- assert(r != -1);
- assert(r != 0);
-
- return true;
-}
-
-void
-do_parent(int thefd)
-{
- struct kevent kev;
- int kq, iter = 0;
-
- EV_SET(&kev, thefd, EVFILT_READ, EV_ADD, 0, 0, NULL);
-
- assert((kq = kqueue()) != -1);
- assert(kevent(kq, &kev, 1, NULL, 0, NULL) != -1);
-
- for (iter = 0; ; iter++) {
- assert(kevent(kq, NULL, 0, &kev, 1, NULL) == 1);
- assert(kev.filter == EVFILT_READ);
- if (!fetch_and_check_fds(thefd))
- break;
- }
-
- fprintf(stderr, "After %d iterations and %d FDs received, bug 4389914 still exists!\n", iter, total_fds_received);
- exit(EXIT_FAILURE);
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-</dict>
-</plist>
+++ /dev/null
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-#include <assert.h>
-#include <errno.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <signal.h>
-
-int
-main(void)
-{
- int wstatus;
- pid_t p, wr;
- int kill_r, killpg_r, r;
- int kill_e, killpg_e;
-
- p = fork();
- assert(p != -1);
-
- if (p == 0) {
- r = setsid();
- assert(r != -1);
- _exit(EXIT_SUCCESS);
- }
-
- sleep(1);
-
- errno = 0;
- kill_r = kill(p, SIGTERM);
- kill_e = errno;
-
- errno = 0;
- killpg_r = kill(-(p), SIGTERM);
- killpg_e = errno;
-
- if (kill_r != killpg_r) {
- fprintf(stderr, "Bug. kill() is inconsistent.\n");
- fprintf(stderr, "kill( p, SIGTERM) returned %d and errno == %d\n", kill_r, kill_e);
- fprintf(stderr, "kill(-(p), SIGTERM) returned %d and errno == %d\n", killpg_r, killpg_e);
- if (kill_e == EPERM || killpg_e == EPERM) {
- fprintf(stderr, "The kernel lied. We should have the right to kill 'p' and it returned EPERM.\n");
- }
- if (kill_e == ESRCH || killpg_e == ESRCH) {
- fprintf(stderr, "The kernel is confused. PID 'p' exists, but the kernel couldn't find it.\n");
- }
-
- exit(EXIT_FAILURE);
- }
-
- wr = waitpid(p, &wstatus, 0);
- assert(wr == p);
-
- exit(EXIT_SUCCESS);
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>com.apple.launchd.test.secsock</string>
- <key>ProgramArguments</key>
- <array>
- <string>sleep</string>
- <string>10000</string>
- </array>
- <key>Sockets</key>
- <dict>
- <key>AuthSock</key>
- <dict>
- <key>SecureSocketWithKey</key>
- <string>SSH_AUTH_SOCK</string>
- </dict>
- </dict>
-</dict>
-</plist>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>com.apple.launchd.test.signaldeath</string>
- <key>ProgramArguments</key>
- <array>
- <string>kill</string>
- <string>-1</string>
- <string>0</string>
- </array>
- <key>OnDemand</key>
- <false/>
-</dict>
-</plist>
+++ /dev/null
-#include <mach/mach.h>
-#include <servers/bootstrap.h>
-#include <sys/types.h>
-#include <sys/time.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <unistd.h>
-#include <errno.h>
-#include <string.h>
-#include <signal.h>
-#include <assert.h>
-#include <launch.h>
-#include <launch_priv.h>
-
-int
-main(int argc, const char *const *argv)
-{
- struct timeval tvs, tve, tvd;
- mach_port_t mpo = MACH_PORT_NULL;
- int wstatus;
- pid_t p;
-
- struct spawn_via_launchd_attr attrs;
- memset(&attrs, 0, sizeof(attrs));
- attrs.spawn_flags = SPAWN_VIA_LAUNCHD_STOPPED; // | SPAWN_VIA_LAUNCHD_FORCE_PPC;
- attrs.spawn_observer_port = &mpo;
-#if 0
- const char *const env[] = { "FOO=bar", "ROCK=roll", NULL };
- attrs.spawn_path = "/bin/booger";
- attrs.spawn_chdir = "/Users/me";
- attrs.spawn_env = env;
-#endif
-
- gettimeofday(&tvs, NULL);
- p = spawn_via_launchd("com.example.booger", argv + 1, &attrs);
-
- if (p == -1) {
- fprintf(stderr, "spawn_via_launchd(): %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- kill(p, SIGCONT);
- gettimeofday(&tve, NULL);
- timersub(&tve, &tvs, &tvd);
- fprintf(stdout, "p == %d mpo = 0x%x in %ld usec\n", p, mpo, tvd.tv_sec * 1000000 + tvd.tv_usec);
-
- assert(mpm_wait(mpo, &wstatus) == 0);
-
- fprintf(stdout, "wstatus == %d\n", wstatus);
-
- exit(EXIT_SUCCESS);
-}
+++ /dev/null
-#include <stdio.h>
-#include <stdbool.h>
-#include <assert.h>
-#include <vproc.h>
-
-int main(void)
-{
- int64_t val;
- bool is_native;
- pid_t p;
- uid_t u;
-
- /* we assign val to p or u due to 64 bit to 32 bit trucation */
-
- assert(vproc_swap_integer(NULL, VPROC_GSK_MGR_PID, NULL, &val) == NULL);
- p = val;
-
- assert(vproc_swap_integer(NULL, VPROC_GSK_MGR_UID, NULL, &val) == NULL);
- u = val;
-
- assert(vproc_swap_integer(NULL, VPROC_GSK_IS_NATIVE, NULL, &val) == NULL);
- is_native = val;
-
- fprintf(stdout, "UID = %u PID = %u Native = %u\n", u, p, is_native);
-
- return 0;
-}
+++ /dev/null
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <unistd.h>
-#include <assert.h>
-#include <vproc.h>
-#include <vproc_priv.h>
-
-static void my_callback(const launch_data_t obj, const char *key, void *context);
-
-int main(void)
-{
- launch_data_t output_obj = NULL;
-
- assert(vproc_swap_complex(NULL, VPROC_GSK_ENVIRONMENT, NULL, &output_obj) == 0);
-
- assert(launch_data_get_type(output_obj) == LAUNCH_DATA_DICTIONARY);
-
- launch_data_dict_iterate(output_obj, my_callback, stdout);
-
- return 0;
-}
-
-void
-my_callback(const launch_data_t obj, const char *key, void *context)
-{
- fprintf(context, "%s == %s\n", key, launch_data_get_string(obj));
-}
--- /dev/null
+#ifndef __BOOTSTRAP_H__
+#define __BOOTSTRAP_H__
+/*
+ * Copyright (c) 1999-2005 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@
+ */
+
+/*
+ * bootstrap -- fundamental service initiator and port server
+ * Mike DeMoney, NeXT, Inc.
+ * Copyright, 1990. All rights reserved.
+ */
+
+/*
+ * Interface: Bootstrap server
+ *
+ * The bootstrap server is the first user-mode task initiated by the Mach
+ * kernel at system boot time. The bootstrap server provides two services,
+ * it initiates other system tasks, and manages a table of name-port bindings
+ * for fundamental system services (e.g. lookupd, Window Manager, etc...).
+ *
+ * Name-port bindings can be established with the bootstrap server by either
+ * of two mechanisms:
+ *
+ * 1. The binding can be indicated, in advance of the service that backs it
+ * being available, via a "service create" request. In this case, bootstrap
+ * will immediately create a port and bind the indicated name with that port.
+ * At a later time, a service may "checkin" for the name-port
+ * binding and will be returned receive rights for the bound port. Lookup's
+ * on bindings created by this mechanism will return send rights to the port,
+ * even if no service has "checked-in". In this case, requests sent to the
+ * bound port will be queued until a server has checked-in and can satisfy the
+ * request.
+ *
+ * 2. Bindings can be established dynamically via a "register" request. In
+ * this case, the register request provides bootstrap with a name and send
+ * rights for a port. Bootstrap will provide send rights for the bound port
+ * to any requestor via the lookup request.
+ *
+ * Bootstrap provides its service port to descendant tasks via the Mach
+ * "bootstrap" special task port. All direct descendants of bootstrap receive
+ * a "privileged" bootstrap service port. System services that initiate
+ * untrusted tasks should replace the Mach bootstrap task special port with
+ * a subset bootstrap port to prevent them from infecting the namespace.
+ *
+ * The bootstrap server creates a "backup" port for each service that it
+ * creates. This is used to detect when a checked out service is no longer
+ * being served. The bootstrap server regains all rights to the port and
+ * it is marked available for check-out again. This allows crashed servers to
+ * resume service to previous clients. Lookup's on this named port will
+ * continue to be serviced by bootstrap while holding receive rights for the
+ * bound port. A client may detect that the service is inactive via the
+ * bootstrap status request. If an inactive service re-registers rather
+ * than "checking-in" the original bound port is destroyed.
+ *
+ * The status of a named service may be obtained via the "status" request.
+ * A service is "active" if a name-port binding exists and receive rights
+ * to the bound port are held by a task other than bootstrap.
+ *
+ * The bootstrap server may also (re)start server processes associated with
+ * with a set of services. The definition of the server process is done
+ * through the "create server" request. The server will be launched in the
+ * same bootstrap context in which it was registered.
+ */
+#include <AvailabilityMacros.h>
+#include <mach/std_types.h>
+#include <mach/message.h>
+#include <sys/types.h>
+#include <sys/cdefs.h>
+#include <stdbool.h>
+
+__BEGIN_DECLS
+
+#pragma GCC visibility push(default)
+
+#define BOOTSTRAP_MAX_NAME_LEN 128
+#define BOOTSTRAP_MAX_CMD_LEN 512
+
+typedef char name_t[BOOTSTRAP_MAX_NAME_LEN];
+typedef char cmd_t[BOOTSTRAP_MAX_CMD_LEN];
+typedef name_t *name_array_t;
+typedef int bootstrap_status_t;
+typedef bootstrap_status_t *bootstrap_status_array_t;
+typedef unsigned int bootstrap_property_t;
+typedef bootstrap_property_t * bootstrap_property_array_t;
+
+typedef boolean_t *bool_array_t;
+
+#define BOOTSTRAP_MAX_LOOKUP_COUNT 20
+
+#define BOOTSTRAP_SUCCESS 0
+#define BOOTSTRAP_NOT_PRIVILEGED 1100
+#define BOOTSTRAP_NAME_IN_USE 1101
+#define BOOTSTRAP_UNKNOWN_SERVICE 1102
+#define BOOTSTRAP_SERVICE_ACTIVE 1103
+#define BOOTSTRAP_BAD_COUNT 1104
+#define BOOTSTRAP_NO_MEMORY 1105
+#define BOOTSTRAP_NO_CHILDREN 1106
+
+#define BOOTSTRAP_STATUS_INACTIVE 0
+#define BOOTSTRAP_STATUS_ACTIVE 1
+#define BOOTSTRAP_STATUS_ON_DEMAND 2
+
+/*
+ * After main() starts, it is safe to assume that this variable is always set.
+ */
+extern mach_port_t bootstrap_port;
+
+/*
+ * bootstrap_create_server()
+ *
+ * Declares a server that mach_init will re-spawn within the specified
+ * bootstrap context. The server is considered already "active"
+ * (i.e. will not be re-spawned) until the returned server_port is
+ * deallocated.
+ *
+ * In the meantime, services can be declared against the server,
+ * by using the server_port as the privileged bootstrap target of
+ * subsequent bootstrap_create_service() calls.
+ *
+ * When mach_init re-spawns the server, its task bootstrap port
+ * is set to the privileged sever_port. Through this special
+ * bootstrap port, it can access all of parent bootstrap's context
+ * (and all services are created in the parent's namespace). But
+ * all additional service declarations (and declaration removals)
+ * will be associated with this particular server.
+ *
+ * Only a holder of the server_port privilege bootstrap port can
+ * check in or register over those services.
+ *
+ * When all services associated with a server are deleted, and the server
+ * exits, it will automatically be deleted itself.
+ *
+ * If the server is declared "on_demand," then a non-running server
+ * will be re-launched on first use of one of the service ports
+ * registered against it. Otherwise, it will be re-launched
+ * immediately upon exiting (whether any client is actively using
+ * any of the service ports or not).
+ *
+ * Errors: Returns appropriate kernel errors on rpc failure.
+ * Returns BOOTSTRAP_NOT_PRIVILEGED, bootstrap or uid invalid.
+ */
+kern_return_t bootstrap_create_server(
+ mach_port_t bp,
+ cmd_t server_cmd,
+ uid_t server_uid,
+ boolean_t on_demand,
+ mach_port_t *server_port);
+
+/*
+ * bootstrap_subset()
+ *
+ * Returns a new port to use as a bootstrap port. This port behaves
+ * exactly like the previous bootstrap_port, except that ports dynamically
+ * registered via bootstrap_register() are available only to users of this
+ * specific subset_port. Lookups on the subset_port will return ports
+ * registered with this port specifically, and ports registered with
+ * ancestors of this subset_port. Duplications of services already
+ * registered with an ancestor port may be registered with the subset port
+ * are allowed. Services already advertised may then be effectively removed
+ * by registering PORT_NULL for the service.
+ * When it is detected that the requestor_port is destroyed the subset
+ * port and all services advertized by it are destroyed as well.
+ *
+ * Errors: Returns appropriate kernel errors on rpc failure.
+ */
+kern_return_t bootstrap_subset(
+ mach_port_t bp,
+ mach_port_t requestor_port,
+ mach_port_t *subset_port);
+
+/*
+ * bootstrap_unprivileged()
+ *
+ * Given a bootstrap port, return its unprivileged equivalent. If
+ * the port is already unprivileged, another reference to the same
+ * port is returned.
+ *
+ * This is most often used by servers, which are launched with their
+ * bootstrap port set to the privileged port for the server, to get
+ * an unprivileged version of the same port for use by its unprivileged
+ * children (or any offspring that it does not want to count as part
+ * of the "server" for mach_init registration and re-launch purposes).
+ *
+ * Native launchd jobs are always started with an unprivileged port.
+ */
+kern_return_t bootstrap_unprivileged(
+ mach_port_t bp,
+ mach_port_t *unpriv_port)
+ AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5;
+
+/*
+ * bootstrap_parent()
+ *
+ * Given a bootstrap subset port, return the parent bootstrap port.
+ * If the specified bootstrap port is already the root subset,
+ * the same port will be returned. Much like "." and ".." are the same
+ * in the file system name space for the root directory ("/").
+ *
+ * Errors:
+ * Returns BOOTSTRAP_NOT_PRIVILEGED if the caller is not running
+ * with an effective user id of root (as determined by the security
+ * token in the message trailer).
+ */
+kern_return_t bootstrap_parent(
+ mach_port_t bp,
+ mach_port_t *parent_port);
+
+/*
+ * bootstrap_register()
+ *
+ * Registers a send right for service_port with the service identified by
+ * service_name. Attempts to register a service where an active binding
+ * already exists are rejected.
+ *
+ * If the service was previously declared with bootstrap_create_service(),
+ * but is not currently active, this call can be used to undeclare the
+ * service. The bootstrap port used must have sufficient privilege to
+ * do so. (Registering MACH_PORT_NULL is especially useful for shutting
+ * down declared services).
+ *
+ * This API is deprecated. Old scenarios and recommendations:
+ *
+ * 1) Code that used to call bootstrap_check_in() and then bootstrap_register()
+ * can now always call bootstrap_check_in().
+ *
+ * 2) If the code was registering a well known name, please switch to launchd.
+ *
+ * 3) If the code was registering a dynamically generated string and passing
+ * the string to other applications, please rewrite the code to send a Mach
+ * send-right directly.
+ *
+ * 4) If the launchd job maintained an optional Mach service, please reserve
+ * the name with launchd and control the presense of the service through
+ * ownership of the Mach receive right like so.
+ *
+ * <key>MachServices</key>
+ * <dict>
+ * <key>com.apple.windowserver</key>
+ * <true/>
+ * <key>com.apple.windowserver.active</key>
+ * <dict>
+ * <key>HideUntilCheckIn</key>
+ * <true/>
+ * </dict>
+ * </dict>
+ *
+ *
+ * Errors: Returns appropriate kernel errors on rpc failure.
+ * Returns BOOTSTRAP_NOT_PRIVILEGED, if request directed to
+ * bootstrap port without privilege.
+ * Returns BOOTSTRAP_NAME_IN_USE, if service has already been
+ * register or checked-in.
+ */
+AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5
+kern_return_t
+bootstrap_register(mach_port_t bp, name_t service_name, mach_port_t sp);
+
+/*
+ * bootstrap_create_service()
+ *
+ * Creates a service named "service_name" and returns a send right to that
+ * port in "service_port." The port may later be checked in as if this
+ * port were configured in the bootstrap configuration file.
+ *
+ * This API is deprecated. Please call bootstrap_check_in() instead.
+ *
+ * Errors: Returns appropriate kernel errors on rpc failure.
+ * Returns BOOTSTRAP_SERVICE_ACTIVE, if service already exists.
+ */
+#ifdef AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6
+AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6
+#endif
+kern_return_t
+bootstrap_create_service(mach_port_t bp, name_t service_name, mach_port_t *sp);
+
+/*
+ * bootstrap_check_in()
+ *
+ * Returns the receive right for the service named by service_name. The
+ * service must have been declared in the launchd.plist(5) file associated
+ * with the job. Attempts to check_in a service which is already active
+ * are not allowed.
+ *
+ * If the service was declared as being associated with a server, the
+ * check_in must come from the server's privileged port (server_port).
+ *
+ * Errors: Returns appropriate kernel errors on rpc failure.
+ * Returns BOOTSTRAP_UNKNOWN_SERVICE, if service does not exist.
+ * Returns BOOTSTRAP_NOT_PRIVILEGED, if request directed to
+ * bootstrap port without privilege.
+ * Returns BOOTSTRAP_SERVICE_ACTIVE, if service has already been
+ * registered or checked-in.
+ */
+kern_return_t bootstrap_check_in(
+ mach_port_t bp,
+ const name_t service_name,
+ mach_port_t *sp);
+
+/*
+ * bootstrap_look_up()
+ *
+ * Returns a send right for the service port declared/registered under the
+ * name service_name. The service is not guaranteed to be active. Use the
+ * bootstrap_status call to determine the status of the service.
+ *
+ * Errors: Returns appropriate kernel errors on rpc failure.
+ * Returns BOOTSTRAP_UNKNOWN_SERVICE, if service does not exist.
+ */
+kern_return_t bootstrap_look_up(
+ mach_port_t bp,
+ const name_t service_name,
+ mach_port_t *sp);
+
+/*
+ * bootstrap_status()
+ *
+ * In practice, this call was used to preflight whether the following two
+ * APIs would succeed.
+ *
+ * bootstrap_look_up()
+ * bootstrap_check_in()
+ *
+ * Please don't bother. Just call the above two APIs directly and check
+ * for failure.
+ */
+kern_return_t bootstrap_status(
+ mach_port_t bp,
+ name_t service_name,
+ bootstrap_status_t *service_active)
+ AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5;
+
+/* bootstrap_strerror()
+ *
+ * Translate a return value from the bootstrap_*() APIs to a string.
+ */
+const char *bootstrap_strerror(kern_return_t r) __attribute__((__nothrow__, __pure__, __warn_unused_result__));
+
+#pragma GCC visibility pop
+
+__END_DECLS
+
+#endif /* __BOOTSTRAP_H__ */
--- /dev/null
+/*
+ * Copyright (c) 2007 Apple 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@
+ */
+
+#ifndef __BOOTSTRAP_PRIVATE_H__
+#define __BOOTSTRAP_PRIVATE_H__
+
+#include <servers/bootstrap.h>
+#include <sys/types.h>
+#include <uuid/uuid.h>
+
+__BEGIN_DECLS
+
+#pragma GCC visibility push(default)
+
+#define BOOTSTRAP_PER_PID_SERVICE (1 << 0)
+#define BOOTSTRAP_ALLOW_LOOKUP (1 << 1)
+#define BOOTSTRAP_DENY_JOB_CREATION (1 << 2)
+#define BOOTSTRAP_PRIVILEGED_SERVER (1 << 3)
+#define BOOTSTRAP_FORCE_LOCAL (1 << 4)
+#define BOOTSTRAP_SPECIFIC_INSTANCE (1 << 5)
+#define BOOTSTRAP_STRICT_CHECKIN (1 << 6)
+#define BOOTSTRAP_STRICT_LOOKUP (1 << 7)
+
+#define BOOTSTRAP_PROPERTY_EXPLICITSUBSET (1 << 0) /* Created via bootstrap_subset(). */
+#define BOOTSTRAP_PROPERTY_IMPLICITSUBSET (1 << 1) /* Created via _vprocmgr_switch_to_session(). */
+#define BOOTSTRAP_PROPERTY_MOVEDSUBSET (1 << 2) /* Created via _vprocmgr_move_subset_to_user(). */
+#define BOOTSTRAP_PROPERTY_PERUSER (1 << 3) /* A per-user launchd's root bootstrap. */
+#define BOOTSTRAP_PROPERTY_XPC_DOMAIN (1 << 4) /* An XPC domain. Duh. */
+#define BOOTSTRAP_PROPERTY_XPC_SINGLETON (1 << 5) /* A singleton XPC domain. */
+
+void bootstrap_init(void);
+
+kern_return_t bootstrap_register2(mach_port_t bp, name_t service_name, mach_port_t sp, uint64_t flags);
+
+kern_return_t bootstrap_look_up2(mach_port_t bp, const name_t service_name, mach_port_t *sp, pid_t target_pid, uint64_t flags);
+
+kern_return_t bootstrap_check_in2(mach_port_t bp, const name_t service_name, mach_port_t *sp, uint64_t flags);
+
+kern_return_t bootstrap_look_up_per_user(mach_port_t bp, const name_t service_name, uid_t target_user, mach_port_t *sp);
+
+kern_return_t bootstrap_lookup_children(mach_port_t bp, mach_port_array_t *children, name_array_t *names, bootstrap_property_array_t *properties, mach_msg_type_number_t *n_children);
+
+kern_return_t bootstrap_look_up3(mach_port_t bp, const name_t service_name, mach_port_t *sp, pid_t target_pid, const uuid_t instance_id, uint64_t flags);
+
+kern_return_t bootstrap_check_in3(mach_port_t bp, const name_t service_name, mach_port_t *sp, uuid_t instance_id, uint64_t flags);
+
+kern_return_t bootstrap_get_root(mach_port_t bp, mach_port_t *root);
+
+#pragma GCC visibility pop
+
+__END_DECLS
+
+#endif /* __BOOTSTRAP_PRIVATE_H__ */
--- /dev/null
+/*
+ * Copyright (c) 2005 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@
+ */
+
+#ifndef __LAUNCH_H__
+#define __LAUNCH_H__
+
+#include <mach/mach.h>
+#include <sys/cdefs.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+#pragma GCC visibility push(default)
+
+__BEGIN_DECLS
+
+#ifdef __GNUC__
+#define __ld_normal __attribute__((__nothrow__))
+#define __ld_setter __attribute__((__nothrow__, __nonnull__))
+#define __ld_getter __attribute__((__nothrow__, __nonnull__, __pure__, __warn_unused_result__))
+#define __ld_iterator(x, y) __attribute__((__nonnull__(x, y)))
+#define __ld_allocator __attribute__((__nothrow__, __malloc__, __nonnull__, __warn_unused_result__))
+#else
+#define __ld_normal
+#define __ld_setter
+#define __ld_getter
+#define __ld_iterator(x, y)
+#define __ld_allocator
+#endif
+
+#define LAUNCH_KEY_SUBMITJOB "SubmitJob"
+#define LAUNCH_KEY_REMOVEJOB "RemoveJob"
+#define LAUNCH_KEY_STARTJOB "StartJob"
+#define LAUNCH_KEY_STOPJOB "StopJob"
+#define LAUNCH_KEY_GETJOB "GetJob"
+#define LAUNCH_KEY_GETJOBS "GetJobs"
+#define LAUNCH_KEY_CHECKIN "CheckIn"
+
+#define LAUNCH_JOBKEY_LABEL "Label"
+#define LAUNCH_JOBKEY_DISABLED "Disabled"
+#define LAUNCH_JOBKEY_USERNAME "UserName"
+#define LAUNCH_JOBKEY_GROUPNAME "GroupName"
+#define LAUNCH_JOBKEY_TIMEOUT "TimeOut"
+#define LAUNCH_JOBKEY_EXITTIMEOUT "ExitTimeOut"
+#define LAUNCH_JOBKEY_INITGROUPS "InitGroups"
+#define LAUNCH_JOBKEY_SOCKETS "Sockets"
+#define LAUNCH_JOBKEY_MACHSERVICES "MachServices"
+#define LAUNCH_JOBKEY_MACHSERVICELOOKUPPOLICIES "MachServiceLookupPolicies"
+#define LAUNCH_JOBKEY_INETDCOMPATIBILITY "inetdCompatibility"
+#define LAUNCH_JOBKEY_ENABLEGLOBBING "EnableGlobbing"
+#define LAUNCH_JOBKEY_PROGRAMARGUMENTS "ProgramArguments"
+#define LAUNCH_JOBKEY_PROGRAM "Program"
+#define LAUNCH_JOBKEY_ONDEMAND "OnDemand"
+#define LAUNCH_JOBKEY_KEEPALIVE "KeepAlive"
+#define LAUNCH_JOBKEY_LIMITLOADTOHOSTS "LimitLoadToHosts"
+#define LAUNCH_JOBKEY_LIMITLOADFROMHOSTS "LimitLoadFromHosts"
+#define LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE "LimitLoadToSessionType"
+#define LAUNCH_JOBKEY_LIMITLOADTOHARDWARE "LimitLoadToHardware"
+#define LAUNCH_JOBKEY_LIMITLOADFROMHARDWARE "LimitLoadFromHardware"
+#define LAUNCH_JOBKEY_RUNATLOAD "RunAtLoad"
+#define LAUNCH_JOBKEY_ROOTDIRECTORY "RootDirectory"
+#define LAUNCH_JOBKEY_WORKINGDIRECTORY "WorkingDirectory"
+#define LAUNCH_JOBKEY_ENVIRONMENTVARIABLES "EnvironmentVariables"
+#define LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES "UserEnvironmentVariables"
+#define LAUNCH_JOBKEY_UMASK "Umask"
+#define LAUNCH_JOBKEY_NICE "Nice"
+#define LAUNCH_JOBKEY_HOPEFULLYEXITSFIRST "HopefullyExitsFirst"
+#define LAUNCH_JOBKEY_HOPEFULLYEXITSLAST "HopefullyExitsLast"
+#define LAUNCH_JOBKEY_LOWPRIORITYIO "LowPriorityIO"
+#define LAUNCH_JOBKEY_SESSIONCREATE "SessionCreate"
+#define LAUNCH_JOBKEY_STARTONMOUNT "StartOnMount"
+#define LAUNCH_JOBKEY_SOFTRESOURCELIMITS "SoftResourceLimits"
+#define LAUNCH_JOBKEY_HARDRESOURCELIMITS "HardResourceLimits"
+#define LAUNCH_JOBKEY_STANDARDINPATH "StandardInPath"
+#define LAUNCH_JOBKEY_STANDARDOUTPATH "StandardOutPath"
+#define LAUNCH_JOBKEY_STANDARDERRORPATH "StandardErrorPath"
+#define LAUNCH_JOBKEY_DEBUG "Debug"
+#define LAUNCH_JOBKEY_WAITFORDEBUGGER "WaitForDebugger"
+#define LAUNCH_JOBKEY_QUEUEDIRECTORIES "QueueDirectories"
+#define LAUNCH_JOBKEY_WATCHPATHS "WatchPaths"
+#define LAUNCH_JOBKEY_STARTINTERVAL "StartInterval"
+#define LAUNCH_JOBKEY_STARTCALENDARINTERVAL "StartCalendarInterval"
+#define LAUNCH_JOBKEY_BONJOURFDS "BonjourFDs"
+#define LAUNCH_JOBKEY_LASTEXITSTATUS "LastExitStatus"
+#define LAUNCH_JOBKEY_PID "PID"
+#define LAUNCH_JOBKEY_THROTTLEINTERVAL "ThrottleInterval"
+#define LAUNCH_JOBKEY_LAUNCHONLYONCE "LaunchOnlyOnce"
+#define LAUNCH_JOBKEY_ABANDONPROCESSGROUP "AbandonProcessGroup"
+#define LAUNCH_JOBKEY_IGNOREPROCESSGROUPATSHUTDOWN "IgnoreProcessGroupAtShutdown"
+#define LAUNCH_JOBKEY_POLICIES "Policies"
+#define LAUNCH_JOBKEY_ENABLETRANSACTIONS "EnableTransactions"
+
+#define LAUNCH_JOBPOLICY_DENYCREATINGOTHERJOBS "DenyCreatingOtherJobs"
+
+#define LAUNCH_JOBINETDCOMPATIBILITY_WAIT "Wait"
+
+#define LAUNCH_JOBKEY_MACH_RESETATCLOSE "ResetAtClose"
+#define LAUNCH_JOBKEY_MACH_HIDEUNTILCHECKIN "HideUntilCheckIn"
+#define LAUNCH_JOBKEY_MACH_DRAINMESSAGESONCRASH "DrainMessagesOnCrash"
+#define LAUNCH_JOBKEY_MACH_PINGEVENTUPDATES "PingEventUpdates"
+
+#define LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT "SuccessfulExit"
+#define LAUNCH_JOBKEY_KEEPALIVE_NETWORKSTATE "NetworkState"
+#define LAUNCH_JOBKEY_KEEPALIVE_PATHSTATE "PathState"
+#define LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBACTIVE "OtherJobActive"
+#define LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBENABLED "OtherJobEnabled"
+#define LAUNCH_JOBKEY_KEEPALIVE_AFTERINITIALDEMAND "AfterInitialDemand"
+#define LAUNCH_JOBKEY_KEEPALIVE_CRASHED "Crashed"
+
+#define LAUNCH_JOBKEY_LAUNCHEVENTS "LaunchEvents"
+
+#define LAUNCH_JOBKEY_CAL_MINUTE "Minute"
+#define LAUNCH_JOBKEY_CAL_HOUR "Hour"
+#define LAUNCH_JOBKEY_CAL_DAY "Day"
+#define LAUNCH_JOBKEY_CAL_WEEKDAY "Weekday"
+#define LAUNCH_JOBKEY_CAL_MONTH "Month"
+
+#define LAUNCH_JOBKEY_RESOURCELIMIT_CORE "Core"
+#define LAUNCH_JOBKEY_RESOURCELIMIT_CPU "CPU"
+#define LAUNCH_JOBKEY_RESOURCELIMIT_DATA "Data"
+#define LAUNCH_JOBKEY_RESOURCELIMIT_FSIZE "FileSize"
+#define LAUNCH_JOBKEY_RESOURCELIMIT_MEMLOCK "MemoryLock"
+#define LAUNCH_JOBKEY_RESOURCELIMIT_NOFILE "NumberOfFiles"
+#define LAUNCH_JOBKEY_RESOURCELIMIT_NPROC "NumberOfProcesses"
+#define LAUNCH_JOBKEY_RESOURCELIMIT_RSS "ResidentSetSize"
+#define LAUNCH_JOBKEY_RESOURCELIMIT_STACK "Stack"
+
+#define LAUNCH_JOBKEY_DISABLED_MACHINETYPE "MachineType"
+#define LAUNCH_JOBKEY_DISABLED_MODELNAME "ModelName"
+
+#define LAUNCH_JOBSOCKETKEY_TYPE "SockType"
+#define LAUNCH_JOBSOCKETKEY_PASSIVE "SockPassive"
+#define LAUNCH_JOBSOCKETKEY_BONJOUR "Bonjour"
+#define LAUNCH_JOBSOCKETKEY_SECUREWITHKEY "SecureSocketWithKey"
+#define LAUNCH_JOBSOCKETKEY_PATHNAME "SockPathName"
+#define LAUNCH_JOBSOCKETKEY_PATHMODE "SockPathMode"
+#define LAUNCH_JOBSOCKETKEY_NODENAME "SockNodeName"
+#define LAUNCH_JOBSOCKETKEY_SERVICENAME "SockServiceName"
+#define LAUNCH_JOBSOCKETKEY_FAMILY "SockFamily"
+#define LAUNCH_JOBSOCKETKEY_PROTOCOL "SockProtocol"
+#define LAUNCH_JOBSOCKETKEY_MULTICASTGROUP "MulticastGroup"
+
+/* These APIs are NOT suitable for general use. Their use should be constrained
+ * to checking into launchd to obtain socket file descriptors using the
+ * LAUNCH_CHECK_IN message type.
+ */
+typedef struct _launch_data *launch_data_t;
+
+typedef enum {
+ LAUNCH_DATA_DICTIONARY = 1,
+ LAUNCH_DATA_ARRAY,
+ LAUNCH_DATA_FD,
+ LAUNCH_DATA_INTEGER,
+ LAUNCH_DATA_REAL,
+ LAUNCH_DATA_BOOL,
+ LAUNCH_DATA_STRING,
+ LAUNCH_DATA_OPAQUE,
+ LAUNCH_DATA_ERRNO,
+ LAUNCH_DATA_MACHPORT,
+} launch_data_type_t;
+
+__ld_allocator
+launch_data_t
+launch_data_alloc(launch_data_type_t);
+
+__ld_allocator
+launch_data_t
+launch_data_copy(launch_data_t);
+
+__ld_getter
+launch_data_type_t
+launch_data_get_type(const launch_data_t);
+
+__ld_setter
+void
+launch_data_free(launch_data_t);
+
+__ld_setter
+bool
+launch_data_dict_insert(launch_data_t, const launch_data_t, const char *);
+
+__ld_getter
+launch_data_t
+launch_data_dict_lookup(const launch_data_t, const char *);
+
+__ld_setter
+bool
+launch_data_dict_remove(launch_data_t, const char *);
+
+__ld_iterator(1, 2)
+void
+launch_data_dict_iterate(const launch_data_t,
+ void (*)(const launch_data_t, const char *, void *), void *);
+
+__ld_getter
+size_t
+launch_data_dict_get_count(const launch_data_t);
+
+__ld_setter
+bool
+launch_data_array_set_index(launch_data_t, const launch_data_t, size_t);
+
+__ld_getter
+launch_data_t
+launch_data_array_get_index(const launch_data_t, size_t);
+
+__ld_getter
+size_t
+launch_data_array_get_count(const launch_data_t);
+
+__ld_allocator
+launch_data_t
+launch_data_new_fd(int);
+
+__ld_allocator
+launch_data_t
+launch_data_new_machport(mach_port_t);
+
+__ld_allocator
+launch_data_t
+launch_data_new_integer(long long);
+
+__ld_allocator
+launch_data_t
+launch_data_new_bool(bool);
+
+__ld_allocator
+launch_data_t
+launch_data_new_real(double);
+
+__ld_allocator
+launch_data_t
+launch_data_new_string(const char *);
+
+__ld_allocator
+launch_data_t
+launch_data_new_opaque(const void *, size_t);
+
+ __ld_setter
+bool
+launch_data_set_fd(launch_data_t, int);
+
+ __ld_setter
+bool
+launch_data_set_machport(launch_data_t, mach_port_t);
+
+ __ld_setter
+bool
+launch_data_set_integer(launch_data_t, long long);
+
+ __ld_setter
+bool
+launch_data_set_bool(launch_data_t, bool);
+
+ __ld_setter
+bool
+launch_data_set_real(launch_data_t, double);
+
+ __ld_setter
+bool
+launch_data_set_string(launch_data_t, const char *);
+
+ __ld_setter
+bool
+launch_data_set_opaque(launch_data_t, const void *, size_t);
+
+__ld_getter
+int
+launch_data_get_fd(const launch_data_t);
+
+__ld_getter
+mach_port_t
+launch_data_get_machport(const launch_data_t);
+
+__ld_getter
+long long
+launch_data_get_integer(const launch_data_t);
+
+__ld_getter
+bool
+launch_data_get_bool(const launch_data_t);
+
+__ld_getter
+double
+launch_data_get_real(const launch_data_t);
+
+__ld_getter
+const char *
+launch_data_get_string(const launch_data_t);
+
+__ld_getter
+void *
+launch_data_get_opaque(const launch_data_t);
+
+__ld_getter
+size_t
+launch_data_get_opaque_size(const launch_data_t);
+
+__ld_getter
+int
+launch_data_get_errno(const launch_data_t);
+
+
+/* launch_get_fd()
+ *
+ * Use this to get the FD if you're doing asynchronous I/O with select(),
+ * poll() or kevent().
+ */
+__ld_normal
+int
+launch_get_fd(void);
+
+/* launch_msg()
+ *
+ * Use this API to check in. Nothing else.
+ */
+__ld_normal
+launch_data_t
+launch_msg(const launch_data_t);
+
+__END_DECLS
+
+#pragma GCC visibility pop
+
+#endif /* __LAUNCH_H__ */
--- /dev/null
+/*
+ * Copyright (c) 2007 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@
+ */
+
+#ifndef __LAUNCH_INTERNAL_H__
+#define __LAUNCH_INTERNAL_H__
+
+#include <paths.h>
+
+#pragma GCC visibility push(default)
+
+#define LAUNCHD_DB_PREFIX "/private/var/db/launchd.db"
+#define LAUNCHD_LOG_PREFIX "/private/var/log"
+
+typedef struct _launch *launch_t;
+
+launch_t launchd_fdopen(int, int);
+int launchd_getfd(launch_t);
+void launchd_close(launch_t, __typeof__(close) closefunc);
+
+launch_data_t launch_data_new_errno(int);
+bool launch_data_set_errno(launch_data_t, int);
+
+int launchd_msg_send(launch_t, launch_data_t);
+int launchd_msg_recv(launch_t, void (*)(launch_data_t, void *), void *);
+
+size_t launch_data_pack(launch_data_t d, void *where, size_t len, int *fd_where, size_t *fdslotsleft);
+launch_data_t launch_data_unpack(void *data, size_t data_size, int *fds, size_t fd_cnt, size_t *data_offset, size_t *fdoffset);
+
+#pragma GCC visibility pop
+
+#endif /* __LAUNCH_INTERNAL_H__*/
--- /dev/null
+/*
+ * Copyright (c) 2005 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@
+ */
+
+#ifndef __LAUNCH_PRIVATE_H__
+#define __LAUNCH_PRIVATE_H__
+
+#include <mach/mach.h>
+#include <sys/types.h>
+#include <launch.h>
+#include <unistd.h>
+#include <paths.h>
+#include <uuid/uuid.h>
+
+#pragma GCC visibility push(default)
+
+__BEGIN_DECLS
+
+#define LAUNCH_KEY_SETUSERENVIRONMENT "SetUserEnvironment"
+#define LAUNCH_KEY_UNSETUSERENVIRONMENT "UnsetUserEnvironment"
+#define LAUNCH_KEY_SHUTDOWN "Shutdown"
+#define LAUNCH_KEY_SINGLEUSER "SingleUser"
+#define LAUNCH_KEY_GETRESOURCELIMITS "GetResourceLimits"
+#define LAUNCH_KEY_SETRESOURCELIMITS "SetResourceLimits"
+#define LAUNCH_KEY_GETRUSAGESELF "GetResourceUsageSelf"
+#define LAUNCH_KEY_GETRUSAGECHILDREN "GetResourceUsageChildren"
+#define LAUNCH_KEY_SETPRIORITYLIST "SetPriorityList"
+
+#define LAUNCHD_SOCKET_ENV "LAUNCHD_SOCKET"
+#define LAUNCHD_SOCK_PREFIX _PATH_VARTMP "launchd"
+#define LAUNCHD_TRUSTED_FD_ENV "__LAUNCHD_FD"
+#define LAUNCHD_ASYNC_MSG_KEY "_AsyncMessage"
+#define LAUNCH_KEY_BATCHCONTROL "BatchControl"
+#define LAUNCH_KEY_BATCHQUERY "BatchQuery"
+
+#define LAUNCH_JOBKEY_TRANSACTIONCOUNT "TransactionCount"
+#define LAUNCH_JOBKEY_QUARANTINEDATA "QuarantineData"
+#define LAUNCH_JOBKEY_SANDBOXPROFILE "SandboxProfile"
+#define LAUNCH_JOBKEY_SANDBOXFLAGS "SandboxFlags"
+#define LAUNCH_JOBKEY_SANDBOX_NAMED "Named"
+#define LAUNCH_JOBKEY_JETSAMPROPERTIES "JetsamProperties"
+#define LAUNCH_JOBKEY_JETSAMPRIORITY "JetsamPriority"
+#define LAUNCH_JOBKEY_JETSAMMEMORYLIMIT "JetsamMemoryLimit"
+#define LAUNCH_JOBKEY_SECURITYSESSIONUUID "SecuritySessionUUID"
+#define LAUNCH_JOBKEY_DISABLEASLR "DisableASLR"
+#define LAUNCH_JOBKEY_XPCDOMAIN "XPCDomain"
+#define LAUNCH_JOBKEY_POSIXSPAWNTYPE "POSIXSpawnType"
+
+#define LAUNCH_KEY_JETSAMLABEL "JetsamLabel"
+#define LAUNCH_KEY_JETSAMFRONTMOST "JetsamFrontmost"
+#define LAUNCH_KEY_JETSAMACTIVE "JetsamActive"
+#define LAUNCH_KEY_JETSAMPRIORITY LAUNCH_JOBKEY_JETSAMPRIORITY
+#define LAUNCH_KEY_JETSAMMEMORYLIMIT LAUNCH_JOBKEY_JETSAMMEMORYLIMIT
+
+#define LAUNCH_KEY_POSIXSPAWNTYPE_TALAPP "TALApp"
+#define LAUNCH_KEY_POSIXSPAWNTYPE_WIDGET "Widget"
+#define LAUNCH_KEY_POSIXSPAWNTYPE_IOSAPP "iOSApp"
+#define LAUNCH_KEY_POSIXSPAWNTYPE_BACKGROUND "Background"
+#define LAUNCH_KEY_POSIXSPAWNTYPE_INTERACTIVE "Interactive"
+
+#define LAUNCH_JOBKEY_EMBEDDEDPRIVILEGEDISPENSATION "EmbeddedPrivilegeDispensation"
+#define LAUNCH_JOBKEY_EMBEDDEDMAINTHREADPRIORITY "EmbeddedMainThreadPriority"
+
+#define LAUNCH_JOBKEY_ENTERKERNELDEBUGGERBEFOREKILL "EnterKernelDebuggerBeforeKill"
+#define LAUNCH_JOBKEY_PERJOBMACHSERVICES "PerJobMachServices"
+#define LAUNCH_JOBKEY_SERVICEIPC "ServiceIPC"
+#define LAUNCH_JOBKEY_BINARYORDERPREFERENCE "BinaryOrderPreference"
+#define LAUNCH_JOBKEY_MACHEXCEPTIONHANDLER "MachExceptionHandler"
+#define LAUNCH_JOBKEY_MULTIPLEINSTANCES "MultipleInstances"
+#define LAUNCH_JOBKEY_EVENTMONITOR "EventMonitor"
+#define LAUNCH_JOBKEY_SHUTDOWNMONITOR "ShutdownMonitor"
+#define LAUNCH_JOBKEY_BEGINTRANSACTIONATSHUTDOWN "BeginTransactionAtShutdown"
+#define LAUNCH_JOBKEY_XPCDOMAINBOOTSTRAPPER "XPCDomainBootstrapper"
+
+#define LAUNCH_JOBKEY_MACH_KUNCSERVER "kUNCServer"
+#define LAUNCH_JOBKEY_MACH_EXCEPTIONSERVER "ExceptionServer"
+#define LAUNCH_JOBKEY_MACH_TASKSPECIALPORT "TaskSpecialPort"
+#define LAUNCH_JOBKEY_MACH_HOSTSPECIALPORT "HostSpecialPort"
+#define LAUNCH_JOBKEY_MACH_ENTERKERNELDEBUGGERONCLOSE "EnterKernelDebuggerOnClose"
+
+#define LAUNCH_ENV_INSTANCEID "LaunchInstanceID"
+
+/* For LoginWindow.
+ *
+ * After this call, the task's bootstrap port is set to the per session launchd.
+ *
+ * This returns 1 on success (it used to return otherwise), and -1 on failure.
+ */
+#define LOAD_ONLY_SAFEMODE_LAUNCHAGENTS (1 << 0)
+#define LAUNCH_GLOBAL_ON_DEMAND (1 << 1)
+pid_t
+create_and_switch_to_per_session_launchd(const char *, int flags, ...);
+
+/* Also for LoginWindow.
+ *
+ * This is will load jobs at the LoginWindow prompt.
+ */
+void
+load_launchd_jobs_at_loginwindow_prompt(int flags, ...);
+
+/* For CoreProcesses */
+#define SPAWN_VIA_LAUNCHD_STOPPED 0x0001
+#define SPAWN_VIA_LAUNCHD_TALAPP 0x0002
+#define SPAWN_VIA_LAUNCHD_WIDGET 0x0004
+#define SPAWN_VIA_LAUNCHD_DISABLE_ASLR 0x0008
+
+struct spawn_via_launchd_attr {
+ uint64_t spawn_flags;
+ const char *spawn_path;
+ const char *spawn_chdir;
+ const char * const * spawn_env;
+ const mode_t *spawn_umask;
+ mach_port_t *spawn_observer_port;
+ const cpu_type_t *spawn_binpref;
+ size_t spawn_binpref_cnt;
+ void * spawn_quarantine;
+ const char *spawn_seatbelt_profile;
+ const uint64_t *spawn_seatbelt_flags;
+};
+
+#define spawn_via_launchd(a, b, c) _spawn_via_launchd(a, b, c, 3)
+pid_t
+_spawn_via_launchd(const char *label, const char * const *argv,
+ const struct spawn_via_launchd_attr *spawn_attrs, int struct_version);
+
+int
+launch_wait(mach_port_t port);
+
+/* The mpm_*() APIs no longer do anything. */
+kern_return_t
+mpm_wait(mach_port_t ajob, int *wstatus);
+
+kern_return_t
+mpm_uncork_fork(mach_port_t ajob);
+
+launch_data_t
+launch_socket_service_check_in(void);
+
+__END_DECLS
+
+#pragma GCC visibility pop
+
+
+#endif /* __LAUNCH_PRIVATE_H__ */
--- /dev/null
+< mach* >
+ mach-per-user-lookup
--- /dev/null
+/*
+ * Copyright (c) 1999-2005 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 "config.h"
+#include "launch.h"
+#include "launch_priv.h"
+#include "bootstrap.h"
+#include "bootstrap_priv.h"
+#include "vproc.h"
+#include "vproc_priv.h"
+
+#include <mach/mach.h>
+#include <mach/vm_map.h>
+#include <sys/types.h>
+#include <sys/syslog.h>
+#include <sys/stat.h>
+#include <pthread.h>
+#include <stdlib.h>
+
+#include "job.h"
+
+mach_port_t bootstrap_port = MACH_PORT_NULL;
+
+void
+bootstrap_init(void)
+{
+ kern_return_t kr = task_get_special_port(task_self_trap(), TASK_BOOTSTRAP_PORT, &bootstrap_port);
+ if (kr != KERN_SUCCESS) {
+ abort();
+ }
+}
+
+kern_return_t
+bootstrap_create_server(mach_port_t bp, cmd_t server_cmd, uid_t server_uid, boolean_t on_demand, mach_port_t *server_port)
+{
+ kern_return_t kr;
+
+ kr = vproc_mig_create_server(bp, server_cmd, server_uid, on_demand, server_port);
+
+ if (kr == VPROC_ERR_TRY_PER_USER) {
+ mach_port_t puc;
+
+ if (vproc_mig_lookup_per_user_context(bp, 0, &puc) == 0) {
+ kr = vproc_mig_create_server(puc, server_cmd, server_uid, on_demand, server_port);
+ mach_port_deallocate(mach_task_self(), puc);
+ }
+ }
+
+ return kr;
+}
+
+kern_return_t
+bootstrap_subset(mach_port_t bp, mach_port_t requestor_port, mach_port_t *subset_port)
+{
+ return vproc_mig_subset(bp, requestor_port, subset_port);
+}
+
+kern_return_t
+bootstrap_unprivileged(mach_port_t bp, mach_port_t *unpriv_port)
+{
+ kern_return_t kr;
+
+ *unpriv_port = MACH_PORT_NULL;
+
+ kr = mach_port_mod_refs(mach_task_self(), bp, MACH_PORT_RIGHT_SEND, 1);
+
+ if (kr == KERN_SUCCESS) {
+ *unpriv_port = bp;
+ }
+
+ return kr;
+}
+
+kern_return_t
+bootstrap_parent(mach_port_t bp, mach_port_t *parent_port)
+{
+ return vproc_mig_parent(bp, parent_port);
+}
+
+kern_return_t
+bootstrap_register(mach_port_t bp, name_t service_name, mach_port_t sp)
+{
+ return bootstrap_register2(bp, service_name, sp, 0);
+}
+
+kern_return_t
+bootstrap_register2(mach_port_t bp, name_t service_name, mach_port_t sp, uint64_t flags)
+{
+ kern_return_t kr = vproc_mig_register2(bp, service_name, sp, flags);
+
+ if (kr == VPROC_ERR_TRY_PER_USER) {
+ mach_port_t puc;
+
+ if (vproc_mig_lookup_per_user_context(bp, 0, &puc) == 0) {
+ kr = vproc_mig_register2(puc, service_name, sp, flags);
+ mach_port_deallocate(mach_task_self(), puc);
+ }
+ }
+
+ return kr;
+}
+
+kern_return_t
+bootstrap_create_service(mach_port_t bp, name_t service_name, mach_port_t *sp)
+{
+ kern_return_t kr;
+
+ if ((kr = bootstrap_check_in(bp, service_name, sp))) {
+ return kr;
+ }
+
+ if ((kr = mach_port_mod_refs(mach_task_self(), *sp, MACH_PORT_RIGHT_RECEIVE, -1))) {
+ return kr;
+ }
+
+ return bootstrap_look_up(bp, service_name, sp);
+}
+
+kern_return_t
+bootstrap_check_in(mach_port_t bp, const name_t service_name, mach_port_t *sp)
+{
+ uuid_t junk;
+ return vproc_mig_check_in2(bp, (char *)service_name, sp, junk, 0);
+}
+
+kern_return_t
+bootstrap_check_in2(mach_port_t bp, const name_t service_name, mach_port_t *sp, uint64_t flags)
+{
+ uuid_t junk;
+ return vproc_mig_check_in2(bp, (char *)service_name, sp, junk, flags);
+}
+
+kern_return_t
+bootstrap_look_up_per_user(mach_port_t bp, const name_t service_name, uid_t target_user, mach_port_t *sp)
+{
+ audit_token_t au_tok;
+ kern_return_t kr;
+ mach_port_t puc;
+
+ /* See rdar://problem/4890134. */
+
+ if ((kr = vproc_mig_lookup_per_user_context(bp, target_user, &puc)) != 0) {
+ return kr;
+ }
+
+ if (!service_name) {
+ *sp = puc;
+ } else {
+ uuid_t junk;
+ kr = vproc_mig_look_up2(puc, (char *)service_name, sp, &au_tok, 0, junk, 0);
+ mach_port_deallocate(mach_task_self(), puc);
+ }
+
+ return kr;
+}
+
+kern_return_t
+bootstrap_lookup_children(mach_port_t bp, mach_port_array_t *children, name_array_t *names, bootstrap_property_array_t *properties, mach_msg_type_number_t *n_children)
+{
+ mach_msg_type_number_t junk = 0;
+ return vproc_mig_lookup_children(bp, children, &junk, names, n_children, properties, &junk);
+}
+
+kern_return_t
+bootstrap_look_up(mach_port_t bp, const name_t service_name, mach_port_t *sp)
+{
+ return bootstrap_look_up2(bp, service_name, sp, 0, 0);
+}
+
+kern_return_t
+bootstrap_look_up2(mach_port_t bp, const name_t service_name, mach_port_t *sp, pid_t target_pid, uint64_t flags)
+{
+ uuid_t instance_id;
+ return bootstrap_look_up3(bp, service_name, sp, target_pid, instance_id, flags);
+}
+
+kern_return_t
+bootstrap_look_up3(mach_port_t bp, const name_t service_name, mach_port_t *sp, pid_t target_pid, const uuid_t instance_id, uint64_t flags)
+{
+ audit_token_t au_tok;
+ bool privileged_server_lookup = flags & BOOTSTRAP_PRIVILEGED_SERVER;
+ kern_return_t kr = 0;
+ mach_port_t puc;
+
+ // We have to cast instance_id here because the MIG-generated method
+ // doesn't expect a const parameter.
+ if ((kr = vproc_mig_look_up2(bp, (char *)service_name, sp, &au_tok, target_pid, (unsigned char*)instance_id, flags)) != VPROC_ERR_TRY_PER_USER) {
+ goto out;
+ }
+
+ if ((kr = vproc_mig_lookup_per_user_context(bp, 0, &puc)) != 0) {
+ goto out;
+ }
+
+ kr = vproc_mig_look_up2(puc, (char *)service_name, sp, &au_tok, target_pid, (unsigned char*)instance_id, flags);
+ mach_port_deallocate(mach_task_self(), puc);
+
+out:
+ if ((kr == 0) && privileged_server_lookup) {
+ uid_t server_euid;
+
+ /*
+ * The audit token magic is dependent on the per-user launchd
+ * forwarding MIG requests to the root launchd when it cannot
+ * find the answer locally.
+ */
+
+ /* This API should be in Libsystem, but is not */
+ //audit_token_to_au32(au_tok, NULL, &server_euid, NULL, NULL, NULL, NULL, NULL, NULL);
+
+ server_euid = au_tok.val[1];
+
+ if (server_euid) {
+ mach_port_deallocate(mach_task_self(), *sp);
+ *sp = MACH_PORT_NULL;
+ kr = BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ }
+
+ return kr;
+}
+
+kern_return_t
+bootstrap_check_in3(mach_port_t bp, const name_t service_name, mach_port_t *sp, uuid_t instance_id, uint64_t flags)
+{
+ return vproc_mig_check_in2(bp, (char *)service_name, sp, instance_id, flags);
+}
+
+kern_return_t
+bootstrap_get_root(mach_port_t bp, mach_port_t *root)
+{
+ return vproc_mig_get_root_bootstrap(bp, root);
+}
+
+kern_return_t
+bootstrap_status(mach_port_t bp, name_t service_name, bootstrap_status_t *service_active)
+{
+ kern_return_t kr;
+ mach_port_t p;
+
+ if ((kr = bootstrap_look_up(bp, service_name, &p))) {
+ return kr;
+ }
+
+ mach_port_deallocate(mach_task_self(), p);
+ *service_active = BOOTSTRAP_STATUS_ACTIVE;
+
+ if (bootstrap_check_in(bp, service_name, &p) == BOOTSTRAP_SUCCESS) {
+ mach_port_mod_refs(mach_task_self(), p, MACH_PORT_RIGHT_RECEIVE, -1);
+ *service_active = BOOTSTRAP_STATUS_ON_DEMAND;
+ }
+
+ return BOOTSTRAP_SUCCESS;
+}
+
+kern_return_t
+bootstrap_info(mach_port_t bp,
+ name_array_t *service_names, mach_msg_type_number_t *service_namesCnt,
+ name_array_t *service_jobs, mach_msg_type_number_t *service_jobsCnt,
+ bootstrap_status_array_t *service_active, mach_msg_type_number_t *service_activeCnt,
+ uint64_t flags)
+{
+ return vproc_mig_info(bp, service_names, service_namesCnt, service_jobs, service_jobsCnt, service_active, service_activeCnt, flags);
+}
+
+const char *
+bootstrap_strerror(kern_return_t r)
+{
+ switch (r) {
+ case BOOTSTRAP_SUCCESS:
+ return "Success";
+ case BOOTSTRAP_NOT_PRIVILEGED:
+ return "Permission denied";
+ case BOOTSTRAP_NAME_IN_USE:
+ case BOOTSTRAP_SERVICE_ACTIVE:
+ return "Service name already exists";
+ case BOOTSTRAP_UNKNOWN_SERVICE:
+ return "Unknown service name";
+ case BOOTSTRAP_BAD_COUNT:
+ return "Too many lookups were requested in one request";
+ case BOOTSTRAP_NO_MEMORY:
+ return "Out of memory";
+ default:
+ return mach_error_string(r);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2005 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 "config.h"
+#include "launch.h"
+#include "launch_priv.h"
+#include "launch_internal.h"
+#include "ktrace.h"
+
+#include <mach/mach.h>
+#include <libkern/OSByteOrder.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/fcntl.h>
+#include <sys/un.h>
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <errno.h>
+#include <pwd.h>
+#include <assert.h>
+#include <uuid/uuid.h>
+#include <sys/syscall.h>
+#include <dlfcn.h>
+
+#ifdef __LP64__
+/* workaround: 5723161 */
+#ifndef __DARWIN_ALIGN32
+#define __DARWIN_ALIGN32(x) (((size_t)(x) + 3) & ~3)
+#endif
+#undef CMSG_DATA
+#define CMSG_DATA(cmsg) \
+ ((uint8_t *)(cmsg) + __DARWIN_ALIGN32(sizeof(struct cmsghdr)))
+#undef CMSG_SPACE
+#define CMSG_SPACE(l) \
+ (__DARWIN_ALIGN32(sizeof(struct cmsghdr)) + __DARWIN_ALIGN32(l))
+#undef CMSG_LEN
+#define CMSG_LEN(l) \
+ (__DARWIN_ALIGN32(sizeof(struct cmsghdr)) + (l))
+#endif
+
+struct _launch_data {
+ uint64_t type;
+ union {
+ struct {
+ union {
+ launch_data_t *_array;
+ char *string;
+ void *opaque;
+ int64_t __junk;
+ };
+ union {
+ uint64_t _array_cnt;
+ uint64_t string_len;
+ uint64_t opaque_size;
+ };
+ };
+ int64_t fd;
+ uint64_t mp;
+ uint64_t err;
+ int64_t number;
+ uint64_t boolean; /* We'd use 'bool' but this struct needs to be used under Rosetta, and sizeof(bool) is different between PowerPC and Intel */
+ double float_num;
+ };
+};
+
+#include "bootstrap.h"
+#include "vproc.h"
+#include "vproc_priv.h"
+#include "vproc_internal.h"
+
+/* __OSBogusByteSwap__() must not really exist in the symbol namespace
+ * in order for the following to generate an error at build time.
+ */
+extern void __OSBogusByteSwap__(void);
+
+#define host2wire(x) \
+ ({ typeof (x) _X, _x = (x); \
+ switch (sizeof(_x)) { \
+ case 8: \
+ _X = OSSwapHostToLittleInt64(_x); \
+ break; \
+ case 4: \
+ _X = OSSwapHostToLittleInt32(_x); \
+ break; \
+ case 2: \
+ _X = OSSwapHostToLittleInt16(_x); \
+ break; \
+ case 1: \
+ _X = _x; \
+ break; \
+ default: \
+ __OSBogusByteSwap__(); \
+ break; \
+ } \
+ _X; \
+ })
+
+
+#define big2wire(x) \
+ ({ typeof (x) _X, _x = (x); \
+ switch (sizeof(_x)) { \
+ case 8: \
+ _X = OSSwapLittleToHostInt64(_x); \
+ break; \
+ case 4: \
+ _X = OSSwapLittleToHostInt32(_x); \
+ break; \
+ case 2: \
+ _X = OSSwapLittleToHostInt16(_x); \
+ break; \
+ case 1: \
+ _X = _x; \
+ break; \
+ default: \
+ __OSBogusByteSwap__(); \
+ break; \
+ } \
+ _X; \
+ })
+
+union _launch_double_u {
+ uint64_t iv;
+ double dv;
+};
+
+#define host2wire_f(x) ({ \
+ typeof(x) _F, _f = (x); \
+ union _launch_double_u s; \
+ s.dv = _f; \
+ s.iv = host2wire(s.iv); \
+ _F = s.dv; \
+ _F; \
+})
+
+#define big2wire_f(x) ({ \
+ typeof(x) _F, _f = (x); \
+ union _launch_double_u s; \
+ s.dv = _f; \
+ s.iv = big2wire(s.iv); \
+ _F = s.dv; \
+ _F; \
+})
+
+
+struct launch_msg_header {
+ uint64_t magic;
+ uint64_t len;
+};
+
+#define LAUNCH_MSG_HEADER_MAGIC 0xD2FEA02366B39A41ull
+
+enum {
+ LAUNCHD_USE_CHECKIN_FD,
+ LAUNCHD_USE_OTHER_FD,
+};
+struct _launch {
+ void *sendbuf;
+ int *sendfds;
+ void *recvbuf;
+ int *recvfds;
+ size_t sendlen;
+ size_t sendfdcnt;
+ size_t recvlen;
+ size_t recvfdcnt;
+ int which;
+ int cifd;
+ int fd;
+};
+
+static launch_data_t launch_data_array_pop_first(launch_data_t where);
+static int _fd(int fd);
+static void launch_client_init(void);
+static void launch_msg_getmsgs(launch_data_t m, void *context);
+static launch_data_t launch_msg_internal(launch_data_t d);
+static void launch_mach_checkin_service(launch_data_t obj, const char *key, void *context);
+
+static int64_t s_am_embedded_god = false;
+static launch_t in_flight_msg_recv_client;
+static pthread_once_t _lc_once = PTHREAD_ONCE_INIT;
+
+bool launchd_apple_internal = false;
+
+static struct _launch_client {
+ pthread_mutex_t mtx;
+ launch_t l;
+ launch_data_t async_resp;
+} *_lc = NULL;
+
+void
+launch_client_init(void)
+{
+ struct sockaddr_un sun;
+ char *where = getenv(LAUNCHD_SOCKET_ENV);
+ char *_launchd_fd = getenv(LAUNCHD_TRUSTED_FD_ENV);
+ int dfd, lfd = -1, cifd = -1;
+ name_t spath;
+
+ _lc = calloc(1, sizeof(struct _launch_client));
+
+ if (!_lc)
+ return;
+
+ pthread_mutex_init(&_lc->mtx, NULL);
+
+ if (_launchd_fd) {
+ cifd = strtol(_launchd_fd, NULL, 10);
+ if ((dfd = dup(cifd)) >= 0) {
+ close(dfd);
+ _fd(cifd);
+ } else {
+ cifd = -1;
+ }
+ unsetenv(LAUNCHD_TRUSTED_FD_ENV);
+ }
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+
+ /* The rules are as follows.
+ * - All users (including root) talk to their per-user launchd's by default.
+ * - If we have been invoked under sudo, talk to the system launchd.
+ * - If we're the root user and the __USE_SYSTEM_LAUNCHD environment variable is set, then
+ * talk to the system launchd.
+ */
+ if (where && where[0] != '\0') {
+ strncpy(sun.sun_path, where, sizeof(sun.sun_path));
+ } else {
+ if (_vprocmgr_getsocket(spath) == 0) {
+ if ((getenv("SUDO_COMMAND") || getenv("__USE_SYSTEM_LAUNCHD")) && geteuid() == 0) {
+ /* Talk to the system launchd. */
+ strncpy(sun.sun_path, LAUNCHD_SOCK_PREFIX "/sock", sizeof(sun.sun_path));
+ } else {
+ /* Talk to our per-user launchd. */
+ size_t min_len;
+
+ min_len = sizeof(sun.sun_path) < sizeof(spath) ? sizeof(sun.sun_path) : sizeof(spath);
+
+ strncpy(sun.sun_path, spath, min_len);
+ }
+ }
+ }
+
+ if ((lfd = _fd(socket(AF_UNIX, SOCK_STREAM, 0))) == -1) {
+ goto out_bad;
+ }
+
+#if TARGET_OS_EMBEDDED
+ (void)vproc_swap_integer(NULL, VPROC_GSK_EMBEDDEDROOTEQUIVALENT, NULL, &s_am_embedded_god);
+#endif
+ if (-1 == connect(lfd, (struct sockaddr *)&sun, sizeof(sun))) {
+ if (cifd != -1 || s_am_embedded_god) {
+ /* There is NO security enforced by this check. This is just a hint to our
+ * library that we shouldn't error out due to failing to open this socket. If
+ * we inherited a trusted file descriptor, we shouldn't fail. This should be
+ * adequate for clients' expectations.
+ */
+ close(lfd);
+ lfd = -1;
+ } else {
+ goto out_bad;
+ }
+ }
+
+ if (!(_lc->l = launchd_fdopen(lfd, cifd))) {
+ goto out_bad;
+ }
+
+ if (!(_lc->async_resp = launch_data_alloc(LAUNCH_DATA_ARRAY))) {
+ goto out_bad;
+ }
+
+ return;
+out_bad:
+ if (_lc->l)
+ launchd_close(_lc->l, close);
+ else if (lfd != -1)
+ close(lfd);
+ if (cifd != -1) {
+ close(cifd);
+ }
+ if (_lc)
+ free(_lc);
+ _lc = NULL;
+}
+
+launch_data_t
+launch_data_alloc(launch_data_type_t t)
+{
+ launch_data_t d = calloc(1, sizeof(struct _launch_data));
+
+ if (d) {
+ d->type = t;
+ switch (t) {
+ case LAUNCH_DATA_DICTIONARY:
+ case LAUNCH_DATA_ARRAY:
+ d->_array = malloc(0);
+ break;
+ case LAUNCH_DATA_OPAQUE:
+ d->opaque = malloc(0);
+ default:
+ break;
+ }
+ }
+
+ return d;
+}
+
+launch_data_type_t
+launch_data_get_type(launch_data_t d)
+{
+ return d->type;
+}
+
+void
+launch_data_free(launch_data_t d)
+{
+ size_t i;
+
+ switch (d->type) {
+ case LAUNCH_DATA_DICTIONARY:
+ case LAUNCH_DATA_ARRAY:
+ for (i = 0; i < d->_array_cnt; i++) {
+ if (d->_array[i]) {
+ launch_data_free(d->_array[i]);
+ }
+ }
+ free(d->_array);
+ break;
+ case LAUNCH_DATA_STRING:
+ if (d->string)
+ free(d->string);
+ break;
+ case LAUNCH_DATA_OPAQUE:
+ if (d->opaque)
+ free(d->opaque);
+ break;
+ default:
+ break;
+ }
+ free(d);
+}
+
+size_t
+launch_data_dict_get_count(launch_data_t dict)
+{
+ return dict->_array_cnt / 2;
+}
+
+bool
+launch_data_dict_insert(launch_data_t dict, launch_data_t what, const char *key)
+{
+ size_t i;
+ launch_data_t thekey = launch_data_alloc(LAUNCH_DATA_STRING);
+
+ launch_data_set_string(thekey, key);
+
+ for (i = 0; i < dict->_array_cnt; i += 2) {
+ if (!strcasecmp(key, dict->_array[i]->string)) {
+ launch_data_array_set_index(dict, thekey, i);
+ launch_data_array_set_index(dict, what, i + 1);
+ return true;
+ }
+ }
+ launch_data_array_set_index(dict, thekey, i);
+ launch_data_array_set_index(dict, what, i + 1);
+ return true;
+}
+
+launch_data_t
+launch_data_dict_lookup(launch_data_t dict, const char *key)
+{
+ size_t i;
+
+ if (LAUNCH_DATA_DICTIONARY != dict->type)
+ return NULL;
+
+ for (i = 0; i < dict->_array_cnt; i += 2) {
+ if (!strcasecmp(key, dict->_array[i]->string))
+ return dict->_array[i + 1];
+ }
+
+ return NULL;
+}
+
+bool
+launch_data_dict_remove(launch_data_t dict, const char *key)
+{
+ size_t i;
+
+ for (i = 0; i < dict->_array_cnt; i += 2) {
+ if (!strcasecmp(key, dict->_array[i]->string))
+ break;
+ }
+ if (i == dict->_array_cnt)
+ return false;
+ launch_data_free(dict->_array[i]);
+ launch_data_free(dict->_array[i + 1]);
+ memmove(dict->_array + i, dict->_array + i + 2, (dict->_array_cnt - (i + 2)) * sizeof(launch_data_t));
+ dict->_array_cnt -= 2;
+ return true;
+}
+
+void
+launch_data_dict_iterate(launch_data_t dict, void (*cb)(launch_data_t, const char *, void *), void *context)
+{
+ size_t i;
+
+ if (LAUNCH_DATA_DICTIONARY != dict->type) {
+ return;
+ }
+
+ for (i = 0; i < dict->_array_cnt; i += 2) {
+ cb(dict->_array[i + 1], dict->_array[i]->string, context);
+ }
+}
+
+bool
+launch_data_array_set_index(launch_data_t where, launch_data_t what, size_t ind)
+{
+ if ((ind + 1) >= where->_array_cnt) {
+ where->_array = reallocf(where->_array, (ind + 1) * sizeof(launch_data_t));
+ memset(where->_array + where->_array_cnt, 0, (ind + 1 - where->_array_cnt) * sizeof(launch_data_t));
+ where->_array_cnt = ind + 1;
+ }
+
+ if (where->_array[ind]) {
+ launch_data_free(where->_array[ind]);
+ }
+
+ where->_array[ind] = what;
+ return true;
+}
+
+launch_data_t
+launch_data_array_get_index(launch_data_t where, size_t ind)
+{
+ if (LAUNCH_DATA_ARRAY != where->type || ind >= where->_array_cnt) {
+ return NULL;
+ } else {
+ return where->_array[ind];
+ }
+}
+
+launch_data_t
+launch_data_array_pop_first(launch_data_t where)
+{
+ launch_data_t r = NULL;
+
+ if (where->_array_cnt > 0) {
+ r = where->_array[0];
+ memmove(where->_array, where->_array + 1, (where->_array_cnt - 1) * sizeof(launch_data_t));
+ where->_array_cnt--;
+ }
+ return r;
+}
+
+size_t
+launch_data_array_get_count(launch_data_t where)
+{
+ if (LAUNCH_DATA_ARRAY != where->type)
+ return 0;
+ return where->_array_cnt;
+}
+
+bool
+launch_data_set_errno(launch_data_t d, int e)
+{
+ d->err = e;
+ return true;
+}
+
+bool
+launch_data_set_fd(launch_data_t d, int fd)
+{
+ d->fd = fd;
+ return true;
+}
+
+bool
+launch_data_set_machport(launch_data_t d, mach_port_t p)
+{
+ d->mp = p;
+ return true;
+}
+
+bool
+launch_data_set_integer(launch_data_t d, long long n)
+{
+ d->number = n;
+ return true;
+}
+
+bool
+launch_data_set_bool(launch_data_t d, bool b)
+{
+ d->boolean = b;
+ return true;
+}
+
+bool
+launch_data_set_real(launch_data_t d, double n)
+{
+ d->float_num = n;
+ return true;
+}
+
+bool
+launch_data_set_string(launch_data_t d, const char *s)
+{
+ if (d->string)
+ free(d->string);
+ d->string = strdup(s);
+ if (d->string) {
+ d->string_len = strlen(d->string);
+ return true;
+ }
+ return false;
+}
+
+bool
+launch_data_set_opaque(launch_data_t d, const void *o, size_t os)
+{
+ d->opaque_size = os;
+ if (d->opaque)
+ free(d->opaque);
+ d->opaque = malloc(os);
+ if (d->opaque) {
+ memcpy(d->opaque, o, os);
+ return true;
+ }
+ return false;
+}
+
+int
+launch_data_get_errno(launch_data_t d)
+{
+ return d->err;
+}
+
+int
+launch_data_get_fd(launch_data_t d)
+{
+ return d->fd;
+}
+
+mach_port_t
+launch_data_get_machport(launch_data_t d)
+{
+ return d->mp;
+}
+
+long long
+launch_data_get_integer(launch_data_t d)
+{
+ return d->number;
+}
+
+bool
+launch_data_get_bool(launch_data_t d)
+{
+ return d->boolean;
+}
+
+double
+launch_data_get_real(launch_data_t d)
+{
+ return d->float_num;
+}
+
+const char *
+launch_data_get_string(launch_data_t d)
+{
+ if (LAUNCH_DATA_STRING != d->type)
+ return NULL;
+ return d->string;
+}
+
+void *
+launch_data_get_opaque(launch_data_t d)
+{
+ if (LAUNCH_DATA_OPAQUE != d->type)
+ return NULL;
+ return d->opaque;
+}
+
+size_t
+launch_data_get_opaque_size(launch_data_t d)
+{
+ return d->opaque_size;
+}
+
+int
+launchd_getfd(launch_t l)
+{
+ return (l->which == LAUNCHD_USE_CHECKIN_FD) ? l->cifd : l->fd;
+}
+
+launch_t
+launchd_fdopen(int fd, int cifd)
+{
+ launch_t c;
+
+ c = calloc(1, sizeof(struct _launch));
+ if (!c)
+ return NULL;
+
+ c->fd = fd;
+ c->cifd = cifd;
+
+ if (c->fd == -1 || (c->fd != -1 && c->cifd != -1)) {
+ c->which = LAUNCHD_USE_CHECKIN_FD;
+ } else if (c->cifd == -1) {
+ c->which = LAUNCHD_USE_OTHER_FD;
+ }
+
+ fcntl(fd, F_SETFL, O_NONBLOCK);
+ fcntl(cifd, F_SETFL, O_NONBLOCK);
+
+ if ((c->sendbuf = malloc(0)) == NULL)
+ goto out_bad;
+ if ((c->sendfds = malloc(0)) == NULL)
+ goto out_bad;
+ if ((c->recvbuf = malloc(0)) == NULL)
+ goto out_bad;
+ if ((c->recvfds = malloc(0)) == NULL)
+ goto out_bad;
+
+ return c;
+
+out_bad:
+ if (c->sendbuf)
+ free(c->sendbuf);
+ if (c->sendfds)
+ free(c->sendfds);
+ if (c->recvbuf)
+ free(c->recvbuf);
+ if (c->recvfds)
+ free(c->recvfds);
+ free(c);
+ return NULL;
+}
+
+void
+launchd_close(launch_t lh, typeof(close) closefunc)
+{
+ if (in_flight_msg_recv_client == lh) {
+ in_flight_msg_recv_client = NULL;
+ }
+
+ if (lh->sendbuf)
+ free(lh->sendbuf);
+ if (lh->sendfds)
+ free(lh->sendfds);
+ if (lh->recvbuf)
+ free(lh->recvbuf);
+ if (lh->recvfds)
+ free(lh->recvfds);
+ closefunc(lh->fd);
+ closefunc(lh->cifd);
+ free(lh);
+}
+
+#define ROUND_TO_64BIT_WORD_SIZE(x) ((x + 7) & ~7)
+
+size_t
+launch_data_pack(launch_data_t d, void *where, size_t len, int *fd_where, size_t *fd_cnt)
+{
+ launch_data_t o_in_w = where;
+ size_t i, rsz, node_data_len = sizeof(struct _launch_data);
+
+ if (node_data_len > len) {
+ return 0;
+ }
+
+ where += node_data_len;
+
+ o_in_w->type = host2wire(d->type);
+
+ size_t pad_len = 0;
+ switch (d->type) {
+ case LAUNCH_DATA_INTEGER:
+ o_in_w->number = host2wire(d->number);
+ break;
+ case LAUNCH_DATA_REAL:
+ o_in_w->float_num = host2wire_f(d->float_num);
+ break;
+ case LAUNCH_DATA_BOOL:
+ o_in_w->boolean = host2wire(d->boolean);
+ break;
+ case LAUNCH_DATA_ERRNO:
+ o_in_w->err = host2wire(d->err);
+ break;
+ case LAUNCH_DATA_FD:
+ o_in_w->fd = host2wire(d->fd);
+ if (fd_where && d->fd != -1) {
+ fd_where[*fd_cnt] = d->fd;
+ (*fd_cnt)++;
+ }
+ break;
+ case LAUNCH_DATA_STRING:
+ o_in_w->string_len = host2wire(d->string_len);
+ node_data_len += ROUND_TO_64BIT_WORD_SIZE(d->string_len + 1);
+
+ if (node_data_len > len) {
+ return 0;
+ }
+ memcpy(where, d->string, d->string_len + 1);
+
+ /* Zero padded data. */
+ pad_len = ROUND_TO_64BIT_WORD_SIZE(d->string_len + 1) - (d->string_len + 1);
+ bzero(where + d->string_len + 1, pad_len);
+
+ break;
+ case LAUNCH_DATA_OPAQUE:
+ o_in_w->opaque_size = host2wire(d->opaque_size);
+ node_data_len += ROUND_TO_64BIT_WORD_SIZE(d->opaque_size);
+ if (node_data_len > len) {
+ return 0;
+ }
+ memcpy(where, d->opaque, d->opaque_size);
+
+ /* Zero padded data. */
+ pad_len = ROUND_TO_64BIT_WORD_SIZE(d->opaque_size) - d->opaque_size;
+ bzero(where + d->opaque_size, pad_len);
+
+ break;
+ case LAUNCH_DATA_DICTIONARY:
+ case LAUNCH_DATA_ARRAY:
+ o_in_w->_array_cnt = host2wire(d->_array_cnt);
+ node_data_len += d->_array_cnt * sizeof(uint64_t);
+ if (node_data_len > len) {
+ return 0;
+ }
+
+ where += d->_array_cnt * sizeof(uint64_t);
+
+ for (i = 0; i < d->_array_cnt; i++) {
+ rsz = launch_data_pack(d->_array[i], where, len - node_data_len, fd_where, fd_cnt);
+ if (rsz == 0) {
+ return 0;
+ }
+ where += rsz;
+ node_data_len += rsz;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return node_data_len;
+}
+
+launch_data_t
+launch_data_unpack(void *data, size_t data_size, int *fds, size_t fd_cnt, size_t *data_offset, size_t *fdoffset)
+{
+ launch_data_t r = data + *data_offset;
+ size_t i, tmpcnt;
+
+ if ((data_size - *data_offset) < sizeof(struct _launch_data))
+ return NULL;
+ *data_offset += sizeof(struct _launch_data);
+
+ switch (big2wire(r->type)) {
+ case LAUNCH_DATA_DICTIONARY:
+ case LAUNCH_DATA_ARRAY:
+ tmpcnt = big2wire(r->_array_cnt);
+ if ((data_size - *data_offset) < (tmpcnt * sizeof(uint64_t))) {
+ errno = EAGAIN;
+ return NULL;
+ }
+ r->_array = data + *data_offset;
+ *data_offset += tmpcnt * sizeof(uint64_t);
+ for (i = 0; i < tmpcnt; i++) {
+ r->_array[i] = launch_data_unpack(data, data_size, fds, fd_cnt, data_offset, fdoffset);
+ if (r->_array[i] == NULL)
+ return NULL;
+ }
+ r->_array_cnt = tmpcnt;
+ break;
+ case LAUNCH_DATA_STRING:
+ tmpcnt = big2wire(r->string_len);
+ if ((data_size - *data_offset) < (tmpcnt + 1)) {
+ errno = EAGAIN;
+ return NULL;
+ }
+ r->string = data + *data_offset;
+ r->string_len = tmpcnt;
+ *data_offset += ROUND_TO_64BIT_WORD_SIZE(tmpcnt + 1);
+ break;
+ case LAUNCH_DATA_OPAQUE:
+ tmpcnt = big2wire(r->opaque_size);
+ if ((data_size - *data_offset) < tmpcnt) {
+ errno = EAGAIN;
+ return NULL;
+ }
+ r->opaque = data + *data_offset;
+ r->opaque_size = tmpcnt;
+ *data_offset += ROUND_TO_64BIT_WORD_SIZE(tmpcnt);
+ break;
+ case LAUNCH_DATA_FD:
+ if (r->fd != -1 && fd_cnt > *fdoffset) {
+ r->fd = _fd(fds[*fdoffset]);
+ *fdoffset += 1;
+ }
+ break;
+ case LAUNCH_DATA_INTEGER:
+ r->number = big2wire(r->number);
+ break;
+ case LAUNCH_DATA_REAL:
+ r->float_num = big2wire_f(r->float_num);
+ break;
+ case LAUNCH_DATA_BOOL:
+ r->boolean = big2wire(r->boolean);
+ break;
+ case LAUNCH_DATA_ERRNO:
+ r->err = big2wire(r->err);
+ case LAUNCH_DATA_MACHPORT:
+ break;
+ default:
+ errno = EINVAL;
+ return NULL;
+ break;
+ }
+
+ r->type = big2wire(r->type);
+
+ return r;
+}
+
+int
+launchd_msg_send(launch_t lh, launch_data_t d)
+{
+ struct launch_msg_header lmh;
+ struct cmsghdr *cm = NULL;
+ struct msghdr mh;
+ struct iovec iov[2];
+ size_t sentctrllen = 0;
+ int r;
+
+ int fd2use = launchd_getfd(lh);
+ if (fd2use == -1) {
+ errno = EPERM;
+ return -1;
+ }
+
+ memset(&mh, 0, sizeof(mh));
+
+ /* confirm that the next hack works */
+ assert((d && lh->sendlen == 0) || (!d && lh->sendlen));
+
+ if (d) {
+ size_t fd_slots_used = 0;
+ size_t good_enough_size = 10 * 1024 * 1024;
+ uint64_t msglen;
+
+ /* hack, see the above assert to verify "correctness" */
+ free(lh->sendbuf);
+ lh->sendbuf = malloc(good_enough_size);
+ if (!lh->sendbuf) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ free(lh->sendfds);
+ lh->sendfds = malloc(4 * 1024);
+ if (!lh->sendfds) {
+ free(lh->sendbuf);
+ lh->sendbuf = NULL;
+ errno = ENOMEM;
+ return -1;
+ }
+
+ lh->sendlen = launch_data_pack(d, lh->sendbuf, good_enough_size, lh->sendfds, &fd_slots_used);
+
+ if (lh->sendlen == 0) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ lh->sendfdcnt = fd_slots_used;
+
+ msglen = lh->sendlen + sizeof(struct launch_msg_header); /* type promotion to make the host2wire() macro work right */
+ lmh.len = host2wire(msglen);
+ lmh.magic = host2wire(LAUNCH_MSG_HEADER_MAGIC);
+
+ iov[0].iov_base = &lmh;
+ iov[0].iov_len = sizeof(lmh);
+ mh.msg_iov = iov;
+ mh.msg_iovlen = 2;
+ } else {
+ mh.msg_iov = iov + 1;
+ mh.msg_iovlen = 1;
+ }
+
+ iov[1].iov_base = lh->sendbuf;
+ iov[1].iov_len = lh->sendlen;
+
+
+ if (lh->sendfdcnt > 0) {
+ sentctrllen = mh.msg_controllen = CMSG_SPACE(lh->sendfdcnt * sizeof(int));
+ cm = alloca(mh.msg_controllen);
+ mh.msg_control = cm;
+
+ memset(cm, 0, mh.msg_controllen);
+
+ cm->cmsg_len = CMSG_LEN(lh->sendfdcnt * sizeof(int));
+ cm->cmsg_level = SOL_SOCKET;
+ cm->cmsg_type = SCM_RIGHTS;
+
+ memcpy(CMSG_DATA(cm), lh->sendfds, lh->sendfdcnt * sizeof(int));
+ }
+
+ if ((r = sendmsg(fd2use, &mh, 0)) == -1) {
+ return -1;
+ } else if (r == 0) {
+ errno = ECONNRESET;
+ return -1;
+ } else if (sentctrllen != mh.msg_controllen) {
+ errno = ECONNRESET;
+ return -1;
+ }
+
+ if (d) {
+ r -= sizeof(struct launch_msg_header);
+ }
+
+ lh->sendlen -= r;
+ if (lh->sendlen > 0) {
+ memmove(lh->sendbuf, lh->sendbuf + r, lh->sendlen);
+ } else {
+ free(lh->sendbuf);
+ lh->sendbuf = malloc(0);
+ }
+
+ lh->sendfdcnt = 0;
+ free(lh->sendfds);
+ lh->sendfds = malloc(0);
+
+ if (lh->sendlen > 0) {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+launch_get_fd(void)
+{
+ pthread_once(&_lc_once, launch_client_init);
+
+ if (!_lc) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ return _lc->l->fd;
+}
+
+void
+launch_msg_getmsgs(launch_data_t m, void *context)
+{
+ launch_data_t async_resp, *sync_resp = context;
+
+ if ((LAUNCH_DATA_DICTIONARY == launch_data_get_type(m)) && (async_resp = launch_data_dict_lookup(m, LAUNCHD_ASYNC_MSG_KEY))) {
+ launch_data_array_set_index(_lc->async_resp, launch_data_copy(async_resp), launch_data_array_get_count(_lc->async_resp));
+ } else {
+ *sync_resp = launch_data_copy(m);
+ }
+}
+
+void
+launch_mach_checkin_service(launch_data_t obj, const char *key, void *context __attribute__((unused)))
+{
+ kern_return_t result;
+ mach_port_t p;
+ name_t srvnm;
+
+ strlcpy(srvnm, key, sizeof(srvnm));
+
+ result = bootstrap_check_in(bootstrap_port, srvnm, &p);
+
+ if (result == BOOTSTRAP_SUCCESS)
+ launch_data_set_machport(obj, p);
+}
+
+launch_data_t
+launch_msg(launch_data_t d)
+{
+ launch_data_t mps, r = launch_msg_internal(d);
+
+ if (launch_data_get_type(d) == LAUNCH_DATA_STRING) {
+ if (strcmp(launch_data_get_string(d), LAUNCH_KEY_CHECKIN) != 0)
+ return r;
+ if (r == NULL)
+ return r;
+ if (launch_data_get_type(r) != LAUNCH_DATA_DICTIONARY)
+ return r;
+ mps = launch_data_dict_lookup(r, LAUNCH_JOBKEY_MACHSERVICES);
+ if (mps == NULL)
+ return r;
+ launch_data_dict_iterate(mps, launch_mach_checkin_service, NULL);
+ }
+
+ return r;
+}
+
+extern kern_return_t vproc_mig_set_security_session(mach_port_t, uuid_t, mach_port_t);
+
+static inline bool
+uuid_data_is_null(launch_data_t d)
+{
+ bool result = false;
+ if (launch_data_get_type(d) == LAUNCH_DATA_OPAQUE && launch_data_get_opaque_size(d) == sizeof(uuid_t)) {
+ uuid_t existing_uuid;
+ memcpy(existing_uuid, launch_data_get_opaque(d), sizeof(uuid_t));
+
+ /* A NULL UUID tells us to keep the session inherited from the parent. */
+ result = (bool)uuid_is_null(existing_uuid);
+ }
+
+ return result;
+}
+
+launch_data_t
+launch_msg_internal(launch_data_t d)
+{
+ launch_data_t resp = NULL;
+
+ if (d && (launch_data_get_type(d) == LAUNCH_DATA_STRING)
+ && (strcmp(launch_data_get_string(d), LAUNCH_KEY_GETJOBS) == 0)
+ && vproc_swap_complex(NULL, VPROC_GSK_ALLJOBS, NULL, &resp) == NULL) {
+ return resp;
+ }
+
+ pthread_once(&_lc_once, launch_client_init);
+ if (!_lc) {
+ errno = ENOTCONN;
+ return NULL;
+ }
+
+ int fd2use = -1;
+ if ((launch_data_get_type(d) == LAUNCH_DATA_STRING && strcmp(launch_data_get_string(d), LAUNCH_KEY_CHECKIN) == 0) || s_am_embedded_god) {
+ _lc->l->which = LAUNCHD_USE_CHECKIN_FD;
+ } else {
+ _lc->l->which = LAUNCHD_USE_OTHER_FD;
+ }
+
+ fd2use = launchd_getfd(_lc->l);
+
+ if (fd2use == -1) {
+ errno = EPERM;
+ return NULL;
+ }
+
+#if !TARGET_OS_EMBEDDED
+ uuid_t uuid;
+ launch_data_t uuid_d = NULL;
+ size_t jobs_that_need_sessions = 0;
+ if (d && launch_data_get_type(d) == LAUNCH_DATA_DICTIONARY) {
+ launch_data_t v = launch_data_dict_lookup(d, LAUNCH_KEY_SUBMITJOB);
+
+ if (v && launch_data_get_type(v) == LAUNCH_DATA_ARRAY) {
+ size_t cnt = launch_data_array_get_count(v);
+ size_t i = 0;
+
+ uuid_generate(uuid);
+ for (i = 0; i < cnt; i++) {
+ launch_data_t ji = launch_data_array_get_index(v, i);
+ if (launch_data_get_type(ji) == LAUNCH_DATA_DICTIONARY) {
+ launch_data_t existing_v = launch_data_dict_lookup(ji, LAUNCH_JOBKEY_SECURITYSESSIONUUID);
+ if (!existing_v) {
+ /* I really wish these were reference-counted. Sigh... */
+ uuid_d = launch_data_new_opaque(uuid, sizeof(uuid));
+ launch_data_dict_insert(ji, uuid_d, LAUNCH_JOBKEY_SECURITYSESSIONUUID);
+ jobs_that_need_sessions++;
+ } else if (launch_data_get_type(existing_v) == LAUNCH_DATA_OPAQUE) {
+ jobs_that_need_sessions += uuid_data_is_null(existing_v) ? 0 : 1;
+ }
+ }
+ }
+ } else if (v && launch_data_get_type(v) == LAUNCH_DATA_DICTIONARY) {
+ launch_data_t existing_v = launch_data_dict_lookup(v, LAUNCH_JOBKEY_SECURITYSESSIONUUID);
+ if (!existing_v) {
+ uuid_generate(uuid);
+ uuid_d = launch_data_new_opaque(uuid, sizeof(uuid));
+ launch_data_dict_insert(v, uuid_d, LAUNCH_JOBKEY_SECURITYSESSIONUUID);
+ jobs_that_need_sessions++;
+ } else {
+ jobs_that_need_sessions += uuid_data_is_null(existing_v) ? 0 : 1;
+ }
+ }
+ }
+#endif
+
+ pthread_mutex_lock(&_lc->mtx);
+
+ if (d && launchd_msg_send(_lc->l, d) == -1) {
+ do {
+ if (errno != EAGAIN)
+ goto out;
+ } while (launchd_msg_send(_lc->l, NULL) == -1);
+ }
+
+ while (resp == NULL) {
+ if (d == NULL && launch_data_array_get_count(_lc->async_resp) > 0) {
+ resp = launch_data_array_pop_first(_lc->async_resp);
+ goto out;
+ }
+ if (launchd_msg_recv(_lc->l, launch_msg_getmsgs, &resp) == -1) {
+ if (errno != EAGAIN) {
+ goto out;
+ } else if (d == NULL) {
+ errno = 0;
+ goto out;
+ } else {
+ fd_set rfds;
+
+ FD_ZERO(&rfds);
+ FD_SET(fd2use, &rfds);
+
+ select(fd2use + 1, &rfds, NULL, NULL, NULL);
+ }
+ }
+ }
+
+out:
+#if !TARGET_OS_EMBEDDED
+ if (!uuid_is_null(uuid) && resp && jobs_that_need_sessions > 0) {
+ mach_port_t session_port = _audit_session_self();
+ launch_data_type_t resp_type = launch_data_get_type(resp);
+
+ bool set_session = false;
+ if (resp_type == LAUNCH_DATA_ERRNO) {
+ set_session = (launch_data_get_errno(resp) == ENEEDAUTH);
+ } else if (resp_type == LAUNCH_DATA_ARRAY) {
+ set_session = true;
+ }
+
+ kern_return_t kr = KERN_FAILURE;
+ if (set_session) {
+ kr = vproc_mig_set_security_session(bootstrap_port, uuid, session_port);
+ }
+
+ if (kr == KERN_SUCCESS) {
+ if (resp_type == LAUNCH_DATA_ERRNO) {
+ launch_data_set_errno(resp, 0);
+ } else {
+ size_t i = 0;
+ for (i = 0; i < launch_data_array_get_count(resp); i++) {
+ launch_data_t ri = launch_data_array_get_index(resp, i);
+
+ int recvd_err = 0;
+ if (launch_data_get_type(ri) == LAUNCH_DATA_ERRNO && (recvd_err = launch_data_get_errno(ri))) {
+ launch_data_set_errno(ri, recvd_err == ENEEDAUTH ? 0 : recvd_err);
+ }
+ }
+ }
+ }
+
+ mach_port_deallocate(mach_task_self(), session_port);
+ }
+#endif
+
+ pthread_mutex_unlock(&_lc->mtx);
+
+ return resp;
+}
+
+int
+launchd_msg_recv(launch_t lh, void (*cb)(launch_data_t, void *), void *context)
+{
+ struct cmsghdr *cm = alloca(4096);
+ launch_data_t rmsg = NULL;
+ size_t data_offset, fd_offset;
+ struct msghdr mh;
+ struct iovec iov;
+ int r;
+
+ int fd2use = launchd_getfd(lh);
+ if (fd2use == -1) {
+ errno = EPERM;
+ return -1;
+ }
+
+ memset(&mh, 0, sizeof(mh));
+ mh.msg_iov = &iov;
+ mh.msg_iovlen = 1;
+
+ lh->recvbuf = reallocf(lh->recvbuf, lh->recvlen + 8*1024);
+
+ iov.iov_base = lh->recvbuf + lh->recvlen;
+ iov.iov_len = 8*1024;
+ mh.msg_control = cm;
+ mh.msg_controllen = 4096;
+
+ if ((r = recvmsg(fd2use, &mh, 0)) == -1)
+ return -1;
+ if (r == 0) {
+ errno = ECONNRESET;
+ return -1;
+ }
+ if (mh.msg_flags & MSG_CTRUNC) {
+ errno = ECONNABORTED;
+ return -1;
+ }
+ lh->recvlen += r;
+ if (mh.msg_controllen > 0) {
+ lh->recvfds = reallocf(lh->recvfds, lh->recvfdcnt * sizeof(int) + mh.msg_controllen - sizeof(struct cmsghdr));
+ memcpy(lh->recvfds + lh->recvfdcnt, CMSG_DATA(cm), mh.msg_controllen - sizeof(struct cmsghdr));
+ lh->recvfdcnt += (mh.msg_controllen - sizeof(struct cmsghdr)) / sizeof(int);
+ }
+
+ r = 0;
+
+ while (lh->recvlen > 0) {
+ struct launch_msg_header *lmhp = lh->recvbuf;
+ uint64_t tmplen;
+ data_offset = sizeof(struct launch_msg_header);
+ fd_offset = 0;
+
+ if (lh->recvlen < sizeof(struct launch_msg_header))
+ goto need_more_data;
+
+ tmplen = big2wire(lmhp->len);
+
+ if (big2wire(lmhp->magic) != LAUNCH_MSG_HEADER_MAGIC || tmplen <= sizeof(struct launch_msg_header)) {
+ errno = EBADRPC;
+ goto out_bad;
+ }
+
+ if (lh->recvlen < tmplen) {
+ goto need_more_data;
+ }
+
+ if ((rmsg = launch_data_unpack(lh->recvbuf, lh->recvlen, lh->recvfds, lh->recvfdcnt, &data_offset, &fd_offset)) == NULL) {
+ errno = EBADRPC;
+ goto out_bad;
+ }
+
+ in_flight_msg_recv_client = lh;
+
+ cb(rmsg, context);
+
+ /* launchd and only launchd can call launchd_close() as a part of the callback */
+ if (in_flight_msg_recv_client == NULL) {
+ r = 0;
+ break;
+ }
+
+ lh->recvlen -= data_offset;
+ if (lh->recvlen > 0) {
+ memmove(lh->recvbuf, lh->recvbuf + data_offset, lh->recvlen);
+ } else {
+ free(lh->recvbuf);
+ lh->recvbuf = malloc(0);
+ }
+
+ lh->recvfdcnt -= fd_offset;
+ if (lh->recvfdcnt > 0) {
+ memmove(lh->recvfds, lh->recvfds + fd_offset, lh->recvfdcnt * sizeof(int));
+ } else {
+ free(lh->recvfds);
+ lh->recvfds = malloc(0);
+ }
+ }
+
+ return r;
+
+need_more_data:
+ errno = EAGAIN;
+out_bad:
+ return -1;
+}
+
+launch_data_t
+launch_data_copy(launch_data_t o)
+{
+ launch_data_t r = launch_data_alloc(o->type);
+ size_t i;
+
+ free(r->_array);
+ memcpy(r, o, sizeof(struct _launch_data));
+
+ switch (o->type) {
+ case LAUNCH_DATA_DICTIONARY:
+ case LAUNCH_DATA_ARRAY:
+ r->_array = calloc(1, o->_array_cnt * sizeof(launch_data_t));
+ for (i = 0; i < o->_array_cnt; i++) {
+ if (o->_array[i])
+ r->_array[i] = launch_data_copy(o->_array[i]);
+ }
+ break;
+ case LAUNCH_DATA_STRING:
+ r->string = strdup(o->string);
+ break;
+ case LAUNCH_DATA_OPAQUE:
+ r->opaque = malloc(o->opaque_size);
+ memcpy(r->opaque, o->opaque, o->opaque_size);
+ break;
+ default:
+ break;
+ }
+
+ return r;
+}
+
+int
+_fd(int fd)
+{
+ if (fd >= 0)
+ fcntl(fd, F_SETFD, 1);
+ return fd;
+}
+
+launch_data_t
+launch_data_new_errno(int e)
+{
+ launch_data_t r = launch_data_alloc(LAUNCH_DATA_ERRNO);
+
+ if (r)
+ launch_data_set_errno(r, e);
+
+ return r;
+}
+
+launch_data_t
+launch_data_new_fd(int fd)
+{
+ launch_data_t r = launch_data_alloc(LAUNCH_DATA_FD);
+
+ if (r)
+ launch_data_set_fd(r, fd);
+
+ return r;
+}
+
+launch_data_t
+launch_data_new_machport(mach_port_t p)
+{
+ launch_data_t r = launch_data_alloc(LAUNCH_DATA_MACHPORT);
+
+ if (r)
+ launch_data_set_machport(r, p);
+
+ return r;
+}
+
+launch_data_t
+launch_data_new_integer(long long n)
+{
+ launch_data_t r = launch_data_alloc(LAUNCH_DATA_INTEGER);
+
+ if (r)
+ launch_data_set_integer(r, n);
+
+ return r;
+}
+
+launch_data_t
+launch_data_new_bool(bool b)
+{
+ launch_data_t r = launch_data_alloc(LAUNCH_DATA_BOOL);
+
+ if (r)
+ launch_data_set_bool(r, b);
+
+ return r;
+}
+
+launch_data_t
+launch_data_new_real(double d)
+{
+ launch_data_t r = launch_data_alloc(LAUNCH_DATA_REAL);
+
+ if (r)
+ launch_data_set_real(r, d);
+
+ return r;
+}
+
+launch_data_t
+launch_data_new_string(const char *s)
+{
+ launch_data_t r = launch_data_alloc(LAUNCH_DATA_STRING);
+
+ if (r == NULL)
+ return NULL;
+
+ if (!launch_data_set_string(r, s)) {
+ launch_data_free(r);
+ return NULL;
+ }
+
+ return r;
+}
+
+launch_data_t
+launch_data_new_opaque(const void *o, size_t os)
+{
+ launch_data_t r = launch_data_alloc(LAUNCH_DATA_OPAQUE);
+
+ if (r == NULL)
+ return NULL;
+
+ if (!launch_data_set_opaque(r, o, os)) {
+ launch_data_free(r);
+ return NULL;
+ }
+
+ return r;
+}
+
+void
+load_launchd_jobs_at_loginwindow_prompt(int flags __attribute__((unused)), ...)
+{
+ _vprocmgr_init(VPROCMGR_SESSION_LOGINWINDOW);
+}
+
+pid_t
+create_and_switch_to_per_session_launchd(const char *login __attribute__((unused)), int flags, ...)
+{
+ uid_t target_user = geteuid() ? geteuid() : getuid();
+ if (_vprocmgr_move_subset_to_user(target_user, VPROCMGR_SESSION_AQUA, flags)) {
+ return -1;
+ }
+
+ return 1;
+}
--- /dev/null
+/*
+ * Copyright (c) 1999-2005 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 "config.h"
+#include "vproc.h"
+#include "vproc_priv.h"
+#include "vproc_internal.h"
+
+#include <dispatch/dispatch.h>
+#include <libproc.h>
+#include <mach/mach.h>
+#include <mach/vm_map.h>
+#include <sys/param.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <pthread.h>
+#include <signal.h>
+#include <assert.h>
+#include <libkern/OSAtomic.h>
+#include <sys/syscall.h>
+#include <sys/event.h>
+#include <System/sys/fileport.h>
+#include <assumes.h>
+
+#if HAVE_QUARANTINE
+#include <quarantine.h>
+#endif
+
+#include "launch.h"
+#include "launch_priv.h"
+#include "launch_internal.h"
+#include "ktrace.h"
+
+#include "job.h"
+
+#include "helper.h"
+#include "helperServer.h"
+
+#include "reboot2.h"
+
+#define likely(x) __builtin_expect((bool)(x), true)
+#define unlikely(x) __builtin_expect((bool)(x), false)
+
+#define _vproc_set_crash_log_message(x)
+
+void _vproc_transactions_enable_internal(void *arg);
+void _vproc_transaction_begin_internal(void *arg __unused);
+void _vproc_transaction_end_internal(void *arg __unused);
+
+static dispatch_once_t _vproc_transaction_once = 0;
+static uint64_t _vproc_transaction_enabled = 0;
+static dispatch_queue_t _vproc_transaction_queue = NULL;
+static int64_t _vproc_transaction_cnt = 0;
+
+#pragma mark vproc Object
+struct vproc_s {
+ int32_t refcount;
+ mach_port_t j_port;
+};
+
+vproc_t
+vprocmgr_lookup_vproc(const char *label)
+{
+ struct vproc_s *vp = NULL;
+
+ mach_port_t mp = MACH_PORT_NULL;
+ kern_return_t kr = vproc_mig_port_for_label(bootstrap_port, (char *)label, &mp);
+ if (kr == BOOTSTRAP_SUCCESS) {
+ vp = (struct vproc_s *)calloc(1, sizeof(struct vproc_s));
+ if (vp) {
+ vp->refcount = 1;
+ mach_port_mod_refs(mach_task_self(), mp, MACH_PORT_RIGHT_SEND, 1);
+ vp->j_port = mp;
+ }
+ (void)mach_port_deallocate(mach_task_self(), mp);
+ }
+
+ return vp;
+}
+
+vproc_t
+vproc_retain(vproc_t vp)
+{
+ int32_t orig = OSAtomicAdd32(1, &vp->refcount) - 1;
+ if (orig <= 0) {
+ _vproc_set_crash_log_message("Under-retain / over-release of vproc_t.");
+ abort();
+ }
+
+ return vp;
+}
+
+void
+vproc_release(vproc_t vp)
+{
+ int32_t newval = OSAtomicAdd32(-1, &vp->refcount);
+ if (newval < 0) {
+ _vproc_set_crash_log_message("Over-release of vproc_t.");
+ abort();
+ } else if (newval == 0) {
+ mach_port_deallocate(mach_task_self(), vp->j_port);
+ free(vp);
+ }
+}
+
+#pragma mark Transactions
+static void
+_vproc_transaction_init_once(void *arg __unused)
+{
+ int64_t enable_transactions = 0;
+ (void)vproc_swap_integer(NULL, VPROC_GSK_TRANSACTIONS_ENABLED, 0, &enable_transactions);
+ if (enable_transactions != 0) {
+ (void)osx_assumes_zero(proc_track_dirty(getpid(), PROC_DIRTY_TRACK));
+ _vproc_transaction_enabled = 1;
+ }
+ _vproc_transaction_queue = dispatch_queue_create("com.apple.idle-exit-queue", NULL);
+}
+
+void
+_vproc_transactions_enable_internal(void *arg __unused)
+{
+ (void)osx_assumes_zero(proc_track_dirty(getpid(), PROC_DIRTY_TRACK));
+ _vproc_transaction_enabled = 1;
+
+ if (_vproc_transaction_cnt > 0) {
+ (void)osx_assumes_zero(proc_set_dirty(getpid(), true));
+ }
+}
+
+void
+_vproc_transactions_enable(void)
+{
+ dispatch_once_f(&_vproc_transaction_once, NULL, _vproc_transaction_init_once);
+ dispatch_sync_f(_vproc_transaction_queue, NULL, _vproc_transactions_enable_internal);
+}
+
+void
+_vproc_transaction_begin_internal(void *ctx __unused)
+{
+ int64_t new = ++_vproc_transaction_cnt;
+ if (new == 1 && _vproc_transaction_enabled) {
+ (void)osx_assumes_zero(proc_set_dirty(getpid(), true));
+ }
+}
+
+void
+_vproc_transaction_begin(void)
+{
+ dispatch_once_f(&_vproc_transaction_once, NULL, _vproc_transaction_init_once);
+ dispatch_sync_f(_vproc_transaction_queue, NULL, _vproc_transaction_begin_internal);
+}
+
+vproc_transaction_t
+vproc_transaction_begin(vproc_t vp __unused)
+{
+ _vproc_transaction_begin();
+
+ /* Return non-NULL on success. Originally, there were dreams of returning
+ * an object or something, but those never panned out.
+ */
+ return (vproc_transaction_t)vproc_transaction_begin;;
+}
+
+void
+_vproc_transaction_end_internal(void *arg __unused)
+{
+ int64_t new = --_vproc_transaction_cnt;
+ if (new == 0 && _vproc_transaction_enabled) {
+ (void)osx_assumes_zero(proc_set_dirty(getpid(), false));
+ } else if (new < 0) {
+ _vproc_set_crash_log_message("Underflow of transaction count.");
+ }
+}
+
+void
+_vproc_transaction_end(void)
+{
+ dispatch_once_f(&_vproc_transaction_once, NULL, _vproc_transaction_init_once);
+ dispatch_sync_f(_vproc_transaction_queue, NULL, _vproc_transaction_end_internal);
+}
+
+void
+vproc_transaction_end(vproc_t vp __unused, vproc_transaction_t vpt __unused)
+{
+ _vproc_transaction_end();
+}
+
+size_t
+_vproc_transaction_count(void)
+{
+ return _vproc_transaction_cnt;
+}
+
+size_t
+_vproc_standby_count(void)
+{
+ return 0;
+}
+
+size_t
+_vproc_standby_timeout(void)
+{
+ return 0;
+}
+
+bool
+_vproc_pid_is_managed(pid_t p)
+{
+ boolean_t result = false;
+ vproc_mig_pid_is_managed(bootstrap_port, p, &result);
+
+ return result;
+}
+
+kern_return_t
+_vproc_transaction_count_for_pid(pid_t p, int32_t *count, bool *condemned)
+{
+ /* Activity Monitor relies on us returning this error code when the process
+ * is not opted into Instant Off.
+ */
+ kern_return_t error = BOOTSTRAP_NO_MEMORY;
+
+ if (condemned) {
+ *condemned = false;
+ }
+
+ if (count) {
+ uint32_t flags;
+ int ret = proc_get_dirty(p, &flags);
+ if (ret == 0) {
+ if (flags & PROC_DIRTY_TRACKED) {
+ *count = (flags & PROC_DIRTY_IS_DIRTY) ? 1 : 0;
+ error = BOOTSTRAP_SUCCESS;
+ } else {
+ error = BOOTSTRAP_NO_MEMORY;
+ }
+ } else if (ret == ENOTSUP) {
+ error = BOOTSTRAP_NO_MEMORY;
+ } else if (ret == ESRCH) {
+ error = BOOTSTRAP_UNKNOWN_SERVICE;
+ } else if (ret == EPERM) {
+ error = BOOTSTRAP_NOT_PRIVILEGED;
+ } else {
+ error = ret;
+ }
+ }
+
+ return error;
+}
+void
+_vproc_transaction_try_exit(int status)
+{
+#if !TARGET_OS_EMBEDDED
+ if (_vproc_transaction_cnt == 0) {
+ _exit(status);
+ }
+#else
+ _exit(status);
+#endif
+}
+
+void
+_vproc_standby_begin(void)
+{
+
+}
+
+vproc_standby_t
+vproc_standby_begin(vproc_t vp __unused)
+{
+ return (vproc_standby_t)vproc_standby_begin;
+}
+
+void
+_vproc_standby_end(void)
+{
+
+}
+
+/* TODO: obsoleted - remove post-build submission */
+
+int32_t *
+_vproc_transaction_ptr(void)
+{
+ static int32_t dummy = 1;
+ return &dummy;
+}
+
+void
+_vproc_transaction_set_callouts(_vproc_transaction_callout gone2zero __unused, _vproc_transaction_callout gonenonzero __unused)
+{
+}
+
+/* */
+
+void
+vproc_standby_end(vproc_t vp __unused, vproc_standby_t vpt __unused)
+{
+
+}
+
+#pragma mark Miscellaneous SPI
+kern_return_t
+_vproc_grab_subset(mach_port_t bp, mach_port_t *reqport, mach_port_t *rcvright,
+ launch_data_t *outval, mach_port_array_t *ports,
+ mach_msg_type_number_t *portCnt)
+{
+ mach_msg_type_number_t outdata_cnt;
+ vm_offset_t outdata = 0;
+ size_t data_offset = 0;
+ launch_data_t out_obj;
+ kern_return_t kr;
+
+ if ((kr = vproc_mig_take_subset(bp, reqport, rcvright, &outdata, &outdata_cnt, ports, portCnt))) {
+ goto out;
+ }
+
+ if ((out_obj = launch_data_unpack((void *)outdata, outdata_cnt, NULL, 0, &data_offset, NULL))) {
+ *outval = launch_data_copy(out_obj);
+ } else {
+ kr = 1;
+ }
+
+out:
+ if (outdata) {
+ mig_deallocate(outdata, outdata_cnt);
+ }
+
+ return kr;
+}
+
+vproc_err_t
+_vprocmgr_move_subset_to_user(uid_t target_user, const char *session_type, uint64_t flags)
+{
+ kern_return_t kr = 0;
+ bool is_bkgd = (strcmp(session_type, VPROCMGR_SESSION_BACKGROUND) == 0);
+ int64_t ldpid, lduid;
+
+ if (vproc_swap_integer(NULL, VPROC_GSK_MGR_PID, 0, &ldpid) != 0) {
+ return (vproc_err_t)_vprocmgr_move_subset_to_user;
+ }
+
+ if (vproc_swap_integer(NULL, VPROC_GSK_MGR_UID, 0, &lduid) != 0) {
+ return (vproc_err_t)_vprocmgr_move_subset_to_user;
+ }
+
+ if (!is_bkgd && ldpid != 1) {
+ if (lduid == getuid()) {
+ return NULL;
+ }
+ /*
+ * Not all sessions can be moved.
+ * We should clean up this mess someday.
+ */
+ return (vproc_err_t)_vprocmgr_move_subset_to_user;
+ }
+
+ mach_port_t puc = 0;
+ mach_port_t rootbs = MACH_PORT_NULL;
+ (void)bootstrap_get_root(bootstrap_port, &rootbs);
+
+ if (vproc_mig_lookup_per_user_context(rootbs, target_user, &puc) != 0) {
+ return (vproc_err_t)_vprocmgr_move_subset_to_user;
+ }
+
+ if (is_bkgd) {
+ task_set_bootstrap_port(mach_task_self(), puc);
+ mach_port_deallocate(mach_task_self(), bootstrap_port);
+ bootstrap_port = puc;
+ } else {
+ kr = vproc_mig_move_subset(puc, bootstrap_port, (char *)session_type, _audit_session_self(), flags);
+ mach_port_deallocate(mach_task_self(), puc);
+ }
+
+ if (kr) {
+ return (vproc_err_t)_vprocmgr_move_subset_to_user;
+ }
+
+ return _vproc_post_fork_ping();
+}
+
+vproc_err_t
+_vprocmgr_switch_to_session(const char *target_session, vproc_flags_t flags __attribute__((unused)))
+{
+ mach_port_t new_bsport = MACH_PORT_NULL;
+ kern_return_t kr = KERN_FAILURE;
+
+ mach_port_t tnp = MACH_PORT_NULL;
+ task_name_for_pid(mach_task_self(), getpid(), &tnp);
+ if ((kr = vproc_mig_switch_to_session(bootstrap_port, tnp, (char *)target_session, _audit_session_self(), &new_bsport)) != KERN_SUCCESS) {
+ _vproc_log(LOG_NOTICE, "_vprocmgr_switch_to_session(): kr = 0x%x", kr);
+ return (vproc_err_t)_vprocmgr_switch_to_session;
+ }
+
+ task_set_bootstrap_port(mach_task_self(), new_bsport);
+ mach_port_deallocate(mach_task_self(), bootstrap_port);
+ bootstrap_port = new_bsport;
+
+ return !issetugid() ? _vproc_post_fork_ping() : NULL;
+}
+
+vproc_err_t
+_vprocmgr_detach_from_console(vproc_flags_t flags __attribute__((unused)))
+{
+ return _vprocmgr_switch_to_session(VPROCMGR_SESSION_BACKGROUND, 0);
+}
+
+vproc_err_t
+_vproc_post_fork_ping(void)
+{
+#if !TARGET_OS_EMBEDDED
+ au_asid_t s = AU_DEFAUDITSID;
+ do {
+ mach_port_t session = MACH_PORT_NULL;
+ kern_return_t kr = vproc_mig_post_fork_ping(bootstrap_port, mach_task_self(), &session);
+ if (kr != KERN_SUCCESS) {
+ /* If this happens, our bootstrap port probably got hosed. */
+ _vproc_log(LOG_ERR, "Post-fork ping failed!");
+ break;
+ }
+
+ /* If we get back MACH_PORT_NULL, that means we just stick with the session
+ * we inherited across fork(2).
+ */
+ if (session == MACH_PORT_NULL) {
+ s = ~AU_DEFAUDITSID;
+ break;
+ }
+
+ s = _audit_session_join(session);
+ if (s == 0) {
+ _vproc_log_error(LOG_ERR, "Could not join security session!");
+ s = AU_DEFAUDITSID;
+ } else {
+ _vproc_log(LOG_DEBUG, "Joined session %d.", s);
+ }
+ } while (0);
+
+ return s != AU_DEFAUDITSID ? NULL : _vproc_post_fork_ping;
+#else
+ mach_port_t session = MACH_PORT_NULL;
+ return vproc_mig_post_fork_ping(bootstrap_port, mach_task_self(), &session) ? _vproc_post_fork_ping : NULL;
+#endif
+}
+
+vproc_err_t
+_vprocmgr_init(const char *session_type)
+{
+ if (vproc_mig_init_session(bootstrap_port, (char *)session_type, _audit_session_self()) == 0) {
+ return NULL;
+ }
+
+ return (vproc_err_t)_vprocmgr_init;
+}
+
+pid_t
+_spawn_via_launchd(const char *label, const char *const *argv, const struct spawn_via_launchd_attr *spawn_attrs, int struct_version)
+{
+ size_t i, good_enough_size = 10*1024*1024;
+ mach_msg_type_number_t indata_cnt = 0;
+ vm_offset_t indata = 0;
+ mach_port_t obsvr_port = MACH_PORT_NULL;
+ launch_data_t tmp, tmp_array, in_obj;
+ const char *const *tmpp;
+ kern_return_t kr = 1;
+ void *buf = NULL;
+ pid_t p = -1;
+
+ if ((in_obj = launch_data_alloc(LAUNCH_DATA_DICTIONARY)) == NULL) {
+ goto out;
+ }
+
+ if ((tmp = launch_data_new_string(label)) == NULL) {
+ goto out;
+ }
+
+ launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_LABEL);
+
+ if ((tmp_array = launch_data_alloc(LAUNCH_DATA_ARRAY)) == NULL) {
+ goto out;
+ }
+
+ for (i = 0; *argv; i++, argv++) {
+ tmp = launch_data_new_string(*argv);
+ if (tmp == NULL) {
+ goto out;
+ }
+
+ launch_data_array_set_index(tmp_array, tmp, i);
+ }
+
+ launch_data_dict_insert(in_obj, tmp_array, LAUNCH_JOBKEY_PROGRAMARGUMENTS);
+
+ if (spawn_attrs) switch (struct_version) {
+ case 3:
+ case 2:
+#if HAVE_QUARANTINE
+ if (spawn_attrs->spawn_quarantine) {
+ char qbuf[QTN_SERIALIZED_DATA_MAX];
+ size_t qbuf_sz = QTN_SERIALIZED_DATA_MAX;
+
+ if (qtn_proc_to_data(spawn_attrs->spawn_quarantine, qbuf, &qbuf_sz) == 0) {
+ tmp = launch_data_new_opaque(qbuf, qbuf_sz);
+ launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_QUARANTINEDATA);
+ }
+ }
+#endif
+
+ if (spawn_attrs->spawn_seatbelt_profile) {
+ tmp = launch_data_new_string(spawn_attrs->spawn_seatbelt_profile);
+ launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_SANDBOXPROFILE);
+ }
+
+ if (spawn_attrs->spawn_seatbelt_flags) {
+ tmp = launch_data_new_integer(*spawn_attrs->spawn_seatbelt_flags);
+ launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_SANDBOXFLAGS);
+ }
+
+ /* fall through */
+ case 1:
+ if (spawn_attrs->spawn_binpref) {
+ tmp_array = launch_data_alloc(LAUNCH_DATA_ARRAY);
+ for (i = 0; i < spawn_attrs->spawn_binpref_cnt; i++) {
+ tmp = launch_data_new_integer(spawn_attrs->spawn_binpref[i]);
+ launch_data_array_set_index(tmp_array, tmp, i);
+ }
+ launch_data_dict_insert(in_obj, tmp_array, LAUNCH_JOBKEY_BINARYORDERPREFERENCE);
+ }
+ /* fall through */
+ case 0:
+ if (spawn_attrs->spawn_flags & SPAWN_VIA_LAUNCHD_STOPPED) {
+ tmp = launch_data_new_bool(true);
+ launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_WAITFORDEBUGGER);
+ }
+ if (spawn_attrs->spawn_flags & SPAWN_VIA_LAUNCHD_TALAPP) {
+ tmp = launch_data_new_string(LAUNCH_KEY_POSIXSPAWNTYPE_TALAPP);
+ launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_POSIXSPAWNTYPE);
+ }
+ if (spawn_attrs->spawn_flags & SPAWN_VIA_LAUNCHD_WIDGET) {
+ tmp = launch_data_new_string(LAUNCH_KEY_POSIXSPAWNTYPE_WIDGET);
+ launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_POSIXSPAWNTYPE);
+ }
+ if (spawn_attrs->spawn_flags & SPAWN_VIA_LAUNCHD_DISABLE_ASLR) {
+ tmp = launch_data_new_bool(true);
+ launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_DISABLEASLR);
+ }
+
+ if (spawn_attrs->spawn_env) {
+ launch_data_t tmp_dict = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+
+ for (tmpp = spawn_attrs->spawn_env; *tmpp; tmpp++) {
+ char *eqoff, tmpstr[strlen(*tmpp) + 1];
+
+ strcpy(tmpstr, *tmpp);
+
+ eqoff = strchr(tmpstr, '=');
+
+ if (!eqoff) {
+ goto out;
+ }
+
+ *eqoff = '\0';
+
+ launch_data_dict_insert(tmp_dict, launch_data_new_string(eqoff + 1), tmpstr);
+ }
+
+ launch_data_dict_insert(in_obj, tmp_dict, LAUNCH_JOBKEY_ENVIRONMENTVARIABLES);
+ }
+
+ if (spawn_attrs->spawn_path) {
+ tmp = launch_data_new_string(spawn_attrs->spawn_path);
+ launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_PROGRAM);
+ }
+
+ if (spawn_attrs->spawn_chdir) {
+ tmp = launch_data_new_string(spawn_attrs->spawn_chdir);
+ launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_WORKINGDIRECTORY);
+ }
+
+ if (spawn_attrs->spawn_umask) {
+ tmp = launch_data_new_integer(*spawn_attrs->spawn_umask);
+ launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_UMASK);
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ if (!(buf = malloc(good_enough_size))) {
+ goto out;
+ }
+
+ if ((indata_cnt = launch_data_pack(in_obj, buf, good_enough_size, NULL, NULL)) == 0) {
+ goto out;
+ }
+
+ indata = (vm_offset_t)buf;
+
+ if (struct_version == 3) {
+ kr = vproc_mig_spawn2(bootstrap_port, indata, indata_cnt, _audit_session_self(), &p, &obsvr_port);
+ } else {
+ _vproc_set_crash_log_message("Bogus version passed to _spawn_via_launchd(). For this release, the only valid version is 3.");
+ }
+
+ if (kr == VPROC_ERR_TRY_PER_USER) {
+ mach_port_t puc;
+
+ if (vproc_mig_lookup_per_user_context(bootstrap_port, 0, &puc) == 0) {
+ if (struct_version == 3) {
+ kr = vproc_mig_spawn2(puc, indata, indata_cnt, _audit_session_self(), &p, &obsvr_port);
+ }
+ mach_port_deallocate(mach_task_self(), puc);
+ }
+ }
+
+out:
+ if (in_obj) {
+ launch_data_free(in_obj);
+ }
+
+ if (buf) {
+ free(buf);
+ }
+
+ switch (kr) {
+ case BOOTSTRAP_SUCCESS:
+ if (spawn_attrs && spawn_attrs->spawn_observer_port) {
+ *spawn_attrs->spawn_observer_port = obsvr_port;
+ } else {
+ if (struct_version == 3) {
+ mach_port_mod_refs(mach_task_self(), obsvr_port, MACH_PORT_RIGHT_RECEIVE, -1);
+ } else {
+ mach_port_deallocate(mach_task_self(), obsvr_port);
+ }
+ }
+ return p;
+ case BOOTSTRAP_NOT_PRIVILEGED:
+ errno = EPERM; break;
+ case BOOTSTRAP_NO_MEMORY:
+ errno = ENOMEM; break;
+ case BOOTSTRAP_NAME_IN_USE:
+ errno = EEXIST; break;
+ case 1:
+ errno = EIO; break;
+ default:
+ errno = EINVAL; break;
+ }
+
+ return -1;
+}
+
+kern_return_t
+mpm_wait(mach_port_t ajob __attribute__((unused)), int *wstatus)
+{
+ *wstatus = 0;
+ return 0;
+}
+
+kern_return_t
+mpm_uncork_fork(mach_port_t ajob __attribute__((unused)))
+{
+ return KERN_FAILURE;
+}
+
+kern_return_t
+_vprocmgr_getsocket(name_t sockpath)
+{
+ return vproc_mig_getsocket(bootstrap_port, sockpath);
+}
+
+vproc_err_t
+_vproc_get_last_exit_status(int *wstatus)
+{
+ int64_t val;
+
+ if (vproc_swap_integer(NULL, VPROC_GSK_LAST_EXIT_STATUS, 0, &val) == 0) {
+ *wstatus = (int)val;
+ return NULL;
+ }
+
+ return (vproc_err_t)_vproc_get_last_exit_status;
+}
+
+vproc_err_t
+_vproc_send_signal_by_label(const char *label, int sig)
+{
+ if (vproc_mig_send_signal(bootstrap_port, (char *)label, sig) == 0) {
+ return NULL;
+ }
+
+ return _vproc_send_signal_by_label;
+}
+
+vproc_err_t
+_vprocmgr_log_forward(mach_port_t mp, void *data, size_t len)
+{
+ if (vproc_mig_log_forward(mp, (vm_offset_t)data, len) == 0) {
+ return NULL;
+ }
+
+ return _vprocmgr_log_forward;
+}
+
+vproc_err_t
+_vprocmgr_log_drain(vproc_t vp __attribute__((unused)), pthread_mutex_t *mutex, _vprocmgr_log_drain_callback_t func)
+{
+ mach_msg_type_number_t outdata_cnt, tmp_cnt;
+ vm_offset_t outdata = 0;
+ struct timeval tv;
+ struct logmsg_s *lm;
+
+ if (!func) {
+ return _vprocmgr_log_drain;
+ }
+
+ if (vproc_mig_log_drain(bootstrap_port, &outdata, &outdata_cnt) != 0) {
+ return _vprocmgr_log_drain;
+ }
+
+ tmp_cnt = outdata_cnt;
+
+ if (mutex) {
+ pthread_mutex_lock(mutex);
+ }
+
+ for (lm = (struct logmsg_s *)outdata; tmp_cnt > 0; lm = ((void *)lm + lm->obj_sz)) {
+ lm->from_name = (char *)lm + lm->from_name_offset;
+ lm->about_name = (char *)lm + lm->about_name_offset;
+ lm->msg = (char *)lm + lm->msg_offset;
+ lm->session_name = (char *)lm + lm->session_name_offset;
+
+ tv.tv_sec = lm->when / USEC_PER_SEC;
+ tv.tv_usec = lm->when % USEC_PER_SEC;
+
+ func(&tv, lm->from_pid, lm->about_pid, lm->sender_uid, lm->sender_gid, lm->pri,
+ lm->from_name, lm->about_name, lm->session_name, lm->msg);
+
+ tmp_cnt -= lm->obj_sz;
+ }
+
+ if (mutex) {
+ pthread_mutex_unlock(mutex);
+ }
+
+ if (outdata) {
+ mig_deallocate(outdata, outdata_cnt);
+ }
+
+ return NULL;
+}
+
+vproc_err_t
+vproc_swap_integer(vproc_t vp, vproc_gsk_t key, int64_t *inval, int64_t *outval)
+{
+ kern_return_t kr = KERN_FAILURE;
+ int64_t dummyval = 0;
+ mach_port_t mp = vp ? vp->j_port : bootstrap_port;
+ if ((kr = vproc_mig_swap_integer(mp, inval ? key : 0, outval ? key : 0, inval ? *inval : 0, outval ? outval : &dummyval)) == 0) {
+ switch (key) {
+ case VPROC_GSK_PERUSER_SUSPEND:
+ if (dummyval) {
+ /* Wait for the per-user launchd to exit before returning. */
+ int kq = kqueue();
+ struct kevent kev;
+ EV_SET(&kev, dummyval, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, 0);
+ int r = kevent(kq, &kev, 1, &kev, 1, NULL);
+ (void)close(kq);
+ if (r != 1) {
+ return NULL;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return NULL;
+ }
+
+ return (vproc_err_t)vproc_swap_integer;
+}
+
+vproc_err_t
+vproc_swap_complex(vproc_t vp, vproc_gsk_t key, launch_data_t inval, launch_data_t *outval)
+{
+ size_t data_offset = 0, good_enough_size = 10*1024*1024;
+ mach_msg_type_number_t indata_cnt = 0, outdata_cnt;
+ vm_offset_t indata = 0, outdata = 0;
+ launch_data_t out_obj;
+ void *rval = vproc_swap_complex;
+ void *buf = NULL;
+
+ if (inval) {
+ if (!(buf = malloc(good_enough_size))) {
+ goto out;
+ }
+
+ if ((indata_cnt = launch_data_pack(inval, buf, good_enough_size, NULL, NULL)) == 0) {
+ goto out;
+ }
+
+ indata = (vm_offset_t)buf;
+ }
+
+ mach_port_t mp = vp ? vp->j_port : bootstrap_port;
+ if (vproc_mig_swap_complex(mp, inval ? key : 0, outval ? key : 0, indata, indata_cnt, &outdata, &outdata_cnt) != 0) {
+ goto out;
+ }
+
+ if (outval) {
+ if (!(out_obj = launch_data_unpack((void *)outdata, outdata_cnt, NULL, 0, &data_offset, NULL))) {
+ goto out;
+ }
+
+ if (!(*outval = launch_data_copy(out_obj))) {
+ goto out;
+ }
+ }
+
+ rval = NULL;
+out:
+ if (buf) {
+ free(buf);
+ }
+
+ if (outdata) {
+ mig_deallocate(outdata, outdata_cnt);
+ }
+
+ return rval;
+}
+
+vproc_err_t
+vproc_swap_string(vproc_t vp, vproc_gsk_t key, const char *instr, char **outstr)
+{
+ launch_data_t instr_data = instr ? launch_data_new_string(instr) : NULL;
+ launch_data_t outstr_data = NULL;
+
+ vproc_err_t verr = vproc_swap_complex(vp, key, instr_data, &outstr_data);
+ if (!verr && outstr) {
+ if (launch_data_get_type(outstr_data) == LAUNCH_DATA_STRING) {
+ *outstr = strdup(launch_data_get_string(outstr_data));
+ } else {
+ verr = (vproc_err_t)vproc_swap_string;
+ }
+ launch_data_free(outstr_data);
+ }
+ if (instr_data) {
+ launch_data_free(instr_data);
+ }
+
+ return verr;
+}
+
+void *
+reboot2(uint64_t flags)
+{
+ mach_port_t rootbs = MACH_PORT_NULL;
+ (void)bootstrap_get_root(bootstrap_port, &rootbs);
+ if (vproc_mig_reboot2(rootbs, flags) == 0) {
+ (void)mach_port_deallocate(mach_task_self(), rootbs);
+ return NULL;
+ }
+
+ return reboot2;
+}
+
+vproc_err_t
+_vproc_kickstart_by_label(const char *label, pid_t *out_pid,
+ mach_port_t *out_port_name __unused, mach_port_t *out_obsrvr_port __unused,
+ vproc_flags_t flags)
+{
+ /* Ignore the two port parameters. This SPI isn't long for this world, and
+ * all the current clients just leak them anyway.
+ */
+ kern_return_t kr = vproc_mig_kickstart(bootstrap_port, (char *)label, out_pid, flags);
+ if (kr == KERN_SUCCESS) {
+ return NULL;
+ }
+
+ return (vproc_err_t)_vproc_kickstart_by_label;
+}
+
+vproc_err_t
+_vproc_set_global_on_demand(bool state)
+{
+ int64_t val = state ? ~0 : 0;
+
+ if (vproc_swap_integer(NULL, VPROC_GSK_GLOBAL_ON_DEMAND, &val, NULL) == 0) {
+ return NULL;
+ }
+
+ return (vproc_err_t)_vproc_set_global_on_demand;
+}
+
+void
+_vproc_logv(int pri, int err, const char *msg, va_list ap)
+{
+ char flat_msg[3000];
+
+ vsnprintf(flat_msg, sizeof(flat_msg), msg, ap);
+
+ vproc_mig_log(bootstrap_port, pri, err, flat_msg);
+}
+
+void
+_vproc_log(int pri, const char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ _vproc_logv(pri, 0, msg, ap);
+ va_end(ap);
+}
+
+void
+_vproc_log_error(int pri, const char *msg, ...)
+{
+ int saved_errno = errno;
+ va_list ap;
+
+ va_start(ap, msg);
+ _vproc_logv(pri, saved_errno, msg, ap);
+ va_end(ap);
+}
+
+/* The type naming convention is as follows:
+ * For requests...
+ * union __RequestUnion__<userprefix><subsystem>_subsystem
+ * For replies...
+ * union __ReplyUnion__<userprefix><subsystem>_subsystem
+ */
+union maxmsgsz {
+ union __RequestUnion__helper_downcall_launchd_helper_subsystem req;
+ union __ReplyUnion__helper_downcall_launchd_helper_subsystem rep;
+};
+
+size_t vprocmgr_helper_maxmsgsz = sizeof(union maxmsgsz);
+
+kern_return_t
+helper_recv_wait(mach_port_t p, int status)
+{
+#if __LAUNCH_MACH_PORT_CONTEXT_T_DEFINED__
+ mach_port_context_t ctx = status;
+#else
+ mach_vm_address_t ctx = status;
+#endif
+
+ return (errno = mach_port_set_context(mach_task_self(), p, ctx));
+}
+
+int
+launch_wait(mach_port_t port)
+{
+ int status = -1;
+ errno = mach_msg_server_once(launchd_helper_server, vprocmgr_helper_maxmsgsz, port, 0);
+ if (errno == MACH_MSG_SUCCESS) {
+#if __LAUNCH_MACH_PORT_CONTEXT_T_DEFINED__
+ mach_port_context_t ctx = 0;
+#else
+ mach_vm_address_t ctx = 0;
+#endif
+ if ((errno = mach_port_get_context(mach_task_self(), port, &ctx)) == KERN_SUCCESS) {
+ status = ctx;
+ }
+ }
+
+ return status;
+}
+
+launch_data_t
+launch_socket_service_check_in(void)
+{
+ launch_data_t reply = NULL;
+
+ size_t big_enough = 10 * 1024;
+ void *buff = malloc(big_enough);
+ if (buff) {
+ launch_data_t req = launch_data_new_string(LAUNCH_KEY_CHECKIN);
+ if (req) {
+ size_t sz = launch_data_pack(req, buff, big_enough, NULL, NULL);
+ if (sz) {
+ vm_address_t sreply = 0;
+ mach_msg_size_t sreplyCnt = 0;
+ mach_port_array_t fdps = NULL;
+ mach_msg_size_t fdpsCnt = 0;
+ kern_return_t kr = vproc_mig_legacy_ipc_request(bootstrap_port, (vm_address_t)buff, sz, NULL, 0, &sreply, &sreplyCnt, &fdps, &fdpsCnt, _audit_session_self());
+ if (kr == BOOTSTRAP_SUCCESS) {
+ int fds[128];
+
+ size_t i = 0;
+ size_t nfds = fdpsCnt / sizeof(fdps[0]);
+ for (i = 0; i < nfds; i++) {
+ fds[i] = fileport_makefd(fdps[i]);
+ (void)mach_port_deallocate(mach_task_self(), fdps[i]);
+ }
+
+ size_t dataoff = 0;
+ size_t fdoff = 0;
+ reply = launch_data_unpack((void *)sreply, sreplyCnt, fds, nfds, &dataoff, &fdoff);
+ reply = launch_data_copy(reply);
+
+ mig_deallocate(sreply, sreplyCnt);
+ mig_deallocate((vm_address_t)fdps, fdpsCnt);
+ }
+ }
+
+ launch_data_free(req);
+ }
+
+ free(buff);
+ }
+
+ return reply;
+}
--- /dev/null
+/*
+ * Copyright (c) 2007 Apple 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@
+ */
+
+#ifndef __REBOOT2_H__
+#define __REBOOT2_H__
+
+#include <sys/cdefs.h>
+#include <sys/reboot.h>
+#include <stdint.h>
+
+__BEGIN_DECLS
+
+#define RB2_FULLREBOOT 0x8000000000000000llu
+
+/* Returns NULL on success. Not NULL on failure */
+
+__attribute__((visibility("default")))
+void *reboot2(uint64_t flags);
+
+__END_DECLS
+
+#endif /* __REBOOT2_H__ */
--- /dev/null
+/*
+ * Copyright (c) 2006 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@
+ */
+
+#ifndef __VPROC_H__
+#define __VPROC_H__
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <Availability.h>
+
+#ifndef VPROC_HAS_TRANSACTIONS
+ #define VPROC_HAS_TRANSACTIONS
+#endif
+
+__BEGIN_DECLS
+
+#pragma GCC visibility push(default)
+
+typedef void * vproc_err_t;
+
+typedef struct vproc_s * vproc_t;
+typedef void * vprocmgr_t;
+
+const char *vproc_strerror(vproc_err_t r);
+
+/*!
+ * @header vproc
+ *
+ * Processes have two reference counts associated with them:
+ *
+ * Transactions Tracks unfinished work. For example: saving a modified
+ * document.
+ * Standby Tracks outstanding callbacks from external subsystems.
+ *
+ * Descriptive aliases:
+ *
+ * A process with no outstanding transactions is called "clean."
+ * A process with outstanding transactions is called "dirty."
+ * A process with no standby work is called "idle."
+ *
+ * Sometimes, the operating system needs processes to exit. Unix has two
+ * primary signals to kill applications:
+ *
+ * SIGKILL Not catchable by the application.
+ * SIGTERM Catchable by the application.
+ *
+ * If a process is clean, the operating system is free to SIGKILL it at
+ * shutdown or logout. This behavior is opt in.
+ *
+ * If a process is clean and idle, the operating system may send SIGKILL after
+ * a application specified timeout. This behavior is opt in.
+ *
+ * If a process is dirty and idle, the operating system may send SIGTERM after
+ * a application specified timeout. This behavior is opt in.
+ *
+ *
+ * launchd jobs should update their property lists accordingly.
+ *
+ * We plan to have LaunchServices use private methods to coordinate
+ * whether GUI applications have opted into this design.
+ */
+
+/*!
+ * @typedef vproc_transaction_t
+ *
+ * @abstract
+ * An opaque handle used to track outstanding transactions.
+ */
+typedef struct vproc_transaction_s *vproc_transaction_t;
+
+/*!
+ * @function vproc_transaction_begin
+ *
+ * @param virtual_proc
+ * This is meant for future API improvements. Pass NULL for now.
+ *
+ * @result
+ * Returns an opaque handle to be passed to vproc_transaction_end().
+ *
+ * @abstract
+ * Call this API before creating data that needs to be saved via I/O later.
+ */
+vproc_transaction_t
+vproc_transaction_begin(vproc_t virtual_proc) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_5_0);
+
+/*!
+ * @function vproc_transaction_end
+ *
+ * @param virtual_proc
+ * This is meant for future API improvements. Pass NULL for now.
+ *
+ * @param handle
+ * The handle previously created with vproc_transaction_begin().
+ *
+ * @abstract
+ * Call this API after the data has either been flushed or otherwise resolved.
+ *
+ * @discussion
+ * Calling this API with the same handle more than once is undefined.
+ */
+void
+vproc_transaction_end(vproc_t virtual_proc, vproc_transaction_t handle) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_5_0);
+
+/*!
+ * @typedef vproc_standby_t
+ *
+ * @abstract
+ * An opaque handle used to track outstanding standby requests.
+ */
+typedef struct vproc_standby_s *vproc_standby_t;
+
+/*!
+ * @function vproc_standby_begin
+ *
+ * @param virtual_proc
+ * This is meant for future API improvements. Pass NULL for now.
+ *
+ * @result
+ * Returns an opaque handle to be passed to vproc_standby_end().
+ *
+ * @abstract
+ * Call this API before registering notifications. For example: timers network
+ * state change, or when monitoring keyboard/mouse events.
+ *
+ * @discussion
+ * This API is undefined and is currently a no-op.
+ */
+vproc_standby_t
+vproc_standby_begin(vproc_t virtual_proc) __OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_NA);
+
+/*!
+ * @function vproc_standby_end
+ *
+ * @param virtual_proc
+ * This is meant for future API improvements. Pass NULL for now.
+ *
+ * @param handle
+ * The handle previously created with vproc_standby_begin().
+ *
+ * @abstract
+ * Call this API when deregistering notifications.
+ *
+ * @discussion
+ * Calling this API with the same handle more than once is undefined.
+ * This API is undefined and is currently a no-op.
+ */
+void
+vproc_standby_end(vproc_t virtual_proc, vproc_standby_t handle) __OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_NA);
+
+#pragma GCC visibility pop
+
+__END_DECLS
+
+#endif /* __VPROC_H__ */
--- /dev/null
+/*
+ * Copyright (c) 2006-2007 Apple 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@
+ */
+
+#ifndef __VPROC_INTERNAL_H__
+#define __VPROC_INTERNAL_H__
+
+#include <mach/mach.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+#include <stdarg.h>
+#include <sys/syscall.h>
+#include <bsm/audit.h>
+#include "launch.h"
+#include "bootstrap_priv.h"
+#include "vproc.h"
+
+typedef char * _internal_string_t;
+typedef char * logmsg_t;
+typedef pid_t * pid_array_t;
+typedef mach_port_t vproc_mig_t;
+
+#if defined(job_MSG_COUNT) || defined (xpc_domain_MSG_COUNT)
+/* HACK */
+#include "core.h"
+#endif
+
+#define VPROC_ERR_TRY_PER_USER 1099
+
+#pragma GCC visibility push(default)
+
+vproc_err_t _vprocmgr_init(const char *session_type);
+vproc_err_t _vproc_post_fork_ping(void);
+
+#if !TARGET_OS_EMBEDDED
+#define _audit_session_self(v) (mach_port_t)syscall(SYS_audit_session_self)
+#define _audit_session_join(s) (au_asid_t)syscall(SYS_audit_session_join, session)
+#else
+#define _audit_session_self(v) MACH_PORT_NULL
+#define _audit_session_join(s) 0
+#endif
+
+#if __arm__
+#define __LAUNCH_MACH_PORT_CONTEXT_T_DEFINED__ 1
+#else
+#define __LAUNCH_MACH_PORT_CONTEXT_T_DEFINED__ 0
+#endif
+
+#define SPAWN_HAS_PATH 0x0001
+#define SPAWN_HAS_WDIR 0x0002
+#define SPAWN_HAS_UMASK 0x0004
+#define SPAWN_WANTS_WAIT4DEBUGGER 0x0008
+
+kern_return_t
+_vproc_grab_subset(mach_port_t bp, mach_port_t *reqport, mach_port_t *rcvright, launch_data_t *outval,
+ mach_port_array_t *ports, mach_msg_type_number_t *portCnt);
+
+kern_return_t _vprocmgr_getsocket(name_t);
+
+struct logmsg_s {
+ union {
+ STAILQ_ENTRY(logmsg_s) sqe;
+ uint64_t __pad;
+ };
+ int64_t when;
+ pid_t from_pid;
+ pid_t about_pid;
+ uid_t sender_uid;
+ gid_t sender_gid;
+ int err_num;
+ int pri;
+ union {
+ const char *from_name;
+ uint64_t from_name_offset;
+ };
+ union {
+ const char *about_name;
+ uint64_t about_name_offset;
+ };
+ union {
+ const char *session_name;
+ uint64_t session_name_offset;
+ };
+ union {
+ const char *msg;
+ uint64_t msg_offset;
+ };
+ uint64_t obj_sz;
+ char data[0];
+};
+
+
+vproc_err_t _vprocmgr_log_forward(mach_port_t mp, void *data, size_t len);
+
+kern_return_t
+bootstrap_info(mach_port_t bp,
+ name_array_t *service_names,
+ mach_msg_type_number_t *service_namesCnt,
+ name_array_t *service_jobs,
+ mach_msg_type_number_t *service_jobsCnt,
+ bootstrap_status_array_t *service_active,
+ mach_msg_type_number_t *service_activeCnt,
+ uint64_t flags);
+
+#pragma GCC visibility pop
+
+#endif /* __VPROC_INTERNAL_H__ */
--- /dev/null
+/*
+ * Copyright (c) 2006 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@
+ */
+
+#ifndef __VPROC_PRIVATE_H__
+#define __VPROC_PRIVATE_H__
+
+#include <Availability.h>
+#include <sys/types.h>
+#include <sys/cdefs.h>
+#include <sys/syslog.h>
+#include <sys/time.h>
+#include <stdbool.h>
+#include <launch.h>
+#include <vproc.h>
+#include <uuid/uuid.h>
+#include <servers/bootstrap.h>
+
+#ifndef VPROC_HAS_TRANSACTIONS
+#define VPROC_HAS_TRANSACTIONS 1
+#endif
+
+__BEGIN_DECLS
+
+#define VPROCMGR_SESSION_LOGINWINDOW "LoginWindow"
+#define VPROCMGR_SESSION_BACKGROUND "Background"
+#define VPROCMGR_SESSION_AQUA "Aqua"
+#define VPROCMGR_SESSION_STANDARDIO "StandardIO"
+#define VPROCMGR_SESSION_SYSTEM "System"
+
+#define XPC_DOMAIN_TYPE_SYSTEM "XPCSystem"
+#define XPC_DOMAIN_TYPE_PERUSER "XPCPerUser"
+#define XPC_DOMAIN_TYPE_PERSESSION "XPCPerSession"
+#define XPC_DOMAIN_TYPE_PERAPPLICATION "XPCPerApplication"
+
+#pragma GCC visibility push(default)
+
+/* DO NOT use this. This is a hack for 'launchctl' */
+#define VPROC_MAGIC_UNLOAD_SIGNAL 0x4141504C
+
+typedef void (*_vproc_transaction_callout)(void);
+
+typedef enum {
+ VPROC_GSK_ZERO,
+ VPROC_GSK_LAST_EXIT_STATUS,
+ VPROC_GSK_GLOBAL_ON_DEMAND,
+ VPROC_GSK_MGR_UID,
+ VPROC_GSK_MGR_PID,
+ VPROC_GSK_IS_MANAGED,
+ VPROC_GSK_MGR_NAME,
+ VPROC_GSK_BASIC_KEEPALIVE,
+ VPROC_GSK_START_INTERVAL,
+ VPROC_GSK_IDLE_TIMEOUT,
+ VPROC_GSK_EXIT_TIMEOUT,
+ VPROC_GSK_ENVIRONMENT,
+ VPROC_GSK_ALLJOBS,
+ VPROC_GSK_GLOBAL_LOG_MASK,
+ VPROC_GSK_GLOBAL_UMASK,
+ VPROC_GSK_ABANDON_PROCESS_GROUP,
+ VPROC_GSK_TRANSACTIONS_ENABLED,
+ VPROC_GSK_WEIRD_BOOTSTRAP,
+ VPROC_GSK_WAITFORDEBUGGER,
+ VPROC_GSK_SECURITYSESSION,
+ VPROC_GSK_SHUTDOWN_DEBUGGING,
+ VPROC_GSK_VERBOSE_BOOT,
+ VPROC_GSK_PERUSER_SUSPEND,
+ VPROC_GSK_PERUSER_RESUME,
+ VPROC_GSK_JOB_OVERRIDES_DB,
+ VPROC_GSK_JOB_CACHE_DB,
+ VPROC_GSK_EMBEDDEDROOTEQUIVALENT,
+} vproc_gsk_t;
+
+typedef unsigned int vproc_flags_t;
+/* For _vproc_kickstart_by_label() -- instructs launchd to kickstart the job to stall before exec(2). */
+#define VPROCFLAG_STALL_JOB_EXEC 1 << 1
+
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0)
+vproc_t
+vprocmgr_lookup_vproc(const char *label);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0)
+vproc_t
+vproc_retain(vproc_t vp);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0)
+void
+vproc_release(vproc_t vp);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_3_0)
+vproc_err_t
+vproc_swap_integer(vproc_t vp, vproc_gsk_t key,
+ int64_t *inval, int64_t *outval);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_3_0)
+vproc_err_t
+vproc_swap_complex(vproc_t vp, vproc_gsk_t key,
+ launch_data_t inval, launch_data_t *outval);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_4_0)
+vproc_err_t
+vproc_swap_string(vproc_t vp, vproc_gsk_t key,
+ const char *instr, char **outstr);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_NA)
+vproc_err_t
+_vproc_get_last_exit_status(int *wstatus);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_3_0)
+vproc_err_t
+_vproc_set_global_on_demand(bool val);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_3_0)
+vproc_err_t
+_vproc_send_signal_by_label(const char *label, int sig);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_3_0)
+vproc_err_t
+_vproc_kickstart_by_label(const char *label, pid_t *out_pid,
+ mach_port_t *out_port_name, mach_port_t *out_obsrvr_port,
+ vproc_flags_t flags);
+
+/* _vprocmgr_log_drain() is specific to syslogd. It is not for general use. */
+typedef void (*_vprocmgr_log_drain_callback_t)(struct timeval *when, pid_t
+ from_pid, pid_t about_pid, uid_t sender_uid, gid_t sender_gid, int priority,
+ const char *from_name, const char *about_name, const char *session_name,
+ const char *msg);
+
+vproc_err_t
+_vprocmgr_log_drain(vproc_t vp, pthread_mutex_t *optional_mutex_around_callback,
+ _vprocmgr_log_drain_callback_t func);
+
+__attribute__((format(printf, 2, 3)))
+void
+_vproc_log(int pri, const char *msg, ...);
+
+__attribute__((format(printf, 2, 3)))
+void
+_vproc_log_error(int pri, const char *msg, ...);
+
+__attribute__((format(printf, 3, 0)))
+void
+_vproc_logv(int pri, int err, const char *msg, va_list ap);
+
+/* One day, we'll be able to get rid of this... */
+vproc_err_t
+_vprocmgr_move_subset_to_user(uid_t target_user, const char *session_type,
+ uint64_t flags);
+
+vproc_err_t
+_vprocmgr_switch_to_session(const char *target_session, vproc_flags_t flags);
+
+vproc_err_t
+_vprocmgr_detach_from_console(vproc_flags_t flags);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
+void
+_vproc_standby_begin(void);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
+void
+_vproc_standby_end(void);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
+size_t
+_vproc_standby_count(void);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
+size_t
+_vproc_standby_timeout(void);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
+kern_return_t
+_vproc_transaction_count_for_pid(pid_t p, int32_t *count, bool *condemned);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
+bool
+_vproc_pid_is_managed(pid_t p);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
+void
+_vproc_transaction_try_exit(int status);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_5_0)
+void
+_vproc_transaction_begin(void);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_5_0)
+void
+_vproc_transaction_end(void);
+
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA)
+size_t
+_vproc_transaction_count(void);
+
+int32_t *
+_vproc_transaction_ptr(void);
+
+void
+_vproc_transaction_set_callouts(_vproc_transaction_callout gone2zero, _vproc_transaction_callout gonenonzero);
+
+void
+_vproc_transactions_enable(void);
+
+#pragma GCC visibility pop
+
+__END_DECLS
+
+#endif /* __VPROC_PRIVATE_H__ */
--- /dev/null
+.Dd 1 May, 2009
+.Dt launchctl 1
+.Os Darwin
+.Sh NAME
+.Nm launchctl
+.Nd Interfaces with launchd
+.Sh SYNOPSIS
+.Nm
+.Op Ar subcommand Op Ar arguments ...
+.Sh DESCRIPTION
+.Nm
+interfaces with
+.Nm launchd
+to load, unload daemons/agents and generally control
+.Nm launchd .
+.Nm
+supports taking subcommands on the command line, interactively or even redirected from standard input.
+These commands can be stored in
+.Nm $HOME/.launchd.conf
+or
+.Nm /etc/launchd.conf
+to be read at the time
+.Nm launchd
+starts.
+.Sh SUBCOMMANDS
+.Bl -tag -width -indent
+.It Xo Ar load Op Fl wF
+.Op Fl S Ar sessiontype
+.Op Fl D Ar domain
+.Ar paths ...
+.Xc
+Load the specified configuration files or directories of configuration files.
+Jobs that are not on-demand will be started as soon as possible.
+All specified jobs will be loaded before any of them are allowed to start.
+Note that per-user configuration files (LaunchAgents) must be owned by the user
+loading them. All system-wide daemons (LaunchDaemons) must be owned by root. Configuration files
+must not be group- or world-writable. These restrictions are in place for security reasons,
+as allowing writability to a launchd configuration file allows one to specify which executable
+will be launched.
+.Pp
+Note that allowing non-root write access to the /System/Library/LaunchDaemons directory WILL render your system unbootable.
+.Bl -tag -width -indent
+.It Fl w
+Overrides the Disabled key and sets it to false. In previous versions, this option
+would modify the configuration file. Now the state of the Disabled key is stored
+elsewhere on-disk.
+.It Fl F
+Force the loading of the plist. Ignore the Disabled key.
+.It Fl S Ar sessiontype
+Some jobs only make sense in certain contexts. This flag instructs
+.Nm launchctl
+to look for jobs in a different location when using the -D flag, and allows
+.Nm launchctl
+to restrict which jobs are loaded into which session types. Currently known
+session types include: Aqua, LoginWindow, Background, StandardIO and System.
+.It Fl D Ar domain
+Look for
+.Xr plist 5 files ending in *.plist in the domain given. Valid domains include
+"system," "local," "network" and "all." When providing a session type, an additional
+domain is available for use called "user." For example, without a session type given,
+"-D system" would load from property list files from /System/Library/LaunchDaemons.
+With a session type passed, it would load from /System/Library/LaunchAgents.
+.El
+.It Xo Ar unload Op Fl w
+.Op Fl S Ar sessiontype
+.Op Fl D Ar domain
+.Ar paths ...
+.Xc
+Unload the specified configuration files or directories of configuration files.
+This will also stop the job if it is running.
+.Bl -tag -width -indent
+.It Fl w
+Overrides the Disabled key and sets it to true. In previous versions, this option
+would modify the configuration file. Now the state of the Disabled key is stored
+elsewhere on-disk.
+.It Fl S Ar sessiontype
+Some jobs only make sense in certain contexts. This flag instructs
+.Nm launchctl
+to look for jobs in a different location when using the -D flag, and allows
+.Nm launchctl
+to restrict which jobs are loaded into which session types. Currently known
+session types include: Aqua, LoginWindow, Background, StandardIO and System.
+.It Fl D Ar domain
+Look for
+.Xr plist 5 files ending in *.plist in the domain given. Valid domains include
+"system," "local," "network" and "all." When providing a session type, an additional
+domain is available for use called "user." For example, without a session type given,
+"-D system" would load from property list files from /System/Library/LaunchDaemons.
+With a session type passed, it would load from /System/Library/LaunchAgents.
+.El
+.It Xo Ar submit Fl l Ar label
+.Op Fl p Ar executable
+.Op Fl o Ar path
+.Op Fl e Ar path
+.Ar -- command
+.Op Ar args
+.Xc
+A simple way of submitting a program to run without a configuration file. This mechanism also tells launchd to keep the program alive in the event of failure.
+.Bl -tag -width -indent
+.It Fl l Ar label
+What unique label to assign this job to launchd.
+.It Fl p Ar program
+What program to really execute, regardless of what follows the -- in the submit sub-command.
+.It Fl o Ar path
+Where to send the stdout of the program.
+.It Fl e Ar path
+Where to send the stderr of the program.
+.El
+.It Ar remove Ar job_label
+Remove the job from launchd by label.
+.It Ar start Ar job_label
+Start the specified job by label. The expected use of this subcommand is for
+debugging and testing so that one can manually kick-start an on-demand server.
+.It Ar stop Ar job_label
+Stop the specified job by label. If a job is on-demand, launchd may immediately
+restart the job if launchd finds any criteria that is satisfied.
+Non-demand based jobs will always be restarted. Use of this subcommand is discouraged.
+Jobs should ideally idle timeout by themselves.
+.It Xo Ar list
+.Op Ar -x
+.Op Ar label
+.Xc
+With no arguments, list all of the jobs loaded into
+.Nm launchd
+in three columns. The first column displays the PID of the job if it is running.
+The second column displays the last exit status of the job. If the number in this
+column is negative, it represents the negative of the signal which killed the job.
+Thus, "-15" would indicate that the job was terminated with SIGTERM. The third column
+is the job's label.
+.Pp
+Note that you may see some jobs in the list whose labels are in the style "0xdeadbeef.anonymous.program".
+These are jobs which are not managed by
+.Nm launchd ,
+but, at one point, made a request to it.
+.Nm launchd
+claims no ownership and makes no guarantees regarding these jobs. They are stored purely for
+bookkeeping purposes.
+.Pp
+Similarly, you may see labels of the style "0xdeadbeef.mach_init.program". These are legacy jobs that run
+under mach_init emulation. This mechanism will be removed in future versions, and all remaining mach_init
+jobs should be converted over to
+.Nm launchd .
+.Pp
+If
+.Op Ar label
+is specified, prints information about the requested job. If
+.Op Ar -x
+is specified, the information for the specified job is output as an XML property list.
+.It Ar setenv Ar key Ar value
+Set an environmental variable inside of
+.Nm launchd .
+.It Ar unsetenv Ar key
+Unset an environmental variable inside of
+.Nm launchd .
+.It Ar getenv Ar key
+Get an environmental variable inside of
+.Nm launchd .
+.It Ar export
+Export all of the environmental variables of
+.Nm launchd
+for use in a shell eval statement.
+.It Ar getrusage self | children
+Get the resource utilization statistics for
+.Nm launchd
+or the children of
+.Nm launchd .
+.It Xo Ar log
+.Op Ar level loglevel
+.Op Ar only | mask loglevels...
+.Xc
+Get and set the
+.Xr syslog 3
+log level mask. The available log levels are: debug, info, notice, warning, error, critical, alert and emergency.
+.It Xo Ar limit
+.Op Ar cpu | filesize | data | stack | core | rss | memlock | maxproc | maxfiles
+.Op Ar both Op Ar soft | hard
+.Xc
+With no arguments, this command prints all the resource limits of
+.Nm launchd
+as found via
+.Xr getrlimit 2 .
+When a given resource is specified, it prints the limits for that resource.
+With a third argument, it sets both the hard and soft limits to that value.
+With four arguments, the third and forth argument represent the soft and hard limits respectively.
+See
+.Xr setrlimit 2 .
+.It Ar shutdown
+Tell
+.Nm launchd
+to prepare for shutdown by removing all jobs.
+.It Ar umask Op Ar newmask
+Get or optionally set the
+.Xr umask 2
+of
+.Nm launchd .
+.It Xo Ar bslist
+.Op Ar PID | ..
+.Op Ar -j
+.Xc
+This prints out Mach bootstrap services and their respective states. While the
+namespace appears flat, it is in fact hierarchical, thus allowing for certain
+services to be only available to a subset of processes. The three states a
+service can be in are active ("A"), inactive ("I") and on-demand ("D").
+.Pp
+If
+.Op Ar PID
+is specified, print the Mach bootstrap services available to that PID. If
+.Op Ar ..
+is specified, print the Mach bootstrap services available in the parent of the
+current bootstrap. Note that in Mac OS X v10.6, the per-user Mach bootstrap namespace
+is flat, so you will only see a different set of services in a per-user bootstrap
+if you are in an explicitly-created bootstrap subset.
+.Pp
+If
+.Op Ar -j
+is specified, each service name will be followed by the name of the job which registered
+it.
+.It Ar bsexec Ar PID command Op Ar args
+This executes the given command in the same Mach bootstrap namespace hierachy
+as the given PID.
+.It Ar bstree Op Ar -j
+This prints a hierarchical view of the entire Mach bootstrap tree. If
+.Op Ar -j
+is specified, each service name will be followed by the name of the job which registered it.
+Requires root
+privileges.
+.It Ar managerpid
+This prints the PID of the launchd which manages the current bootstrap.
+.It Ar manageruid
+This prints the UID of the launchd which manages the current bootstrap.
+.It Ar managername
+This prints the name of the launchd job manager which manages the current bootstrap.
+See LimitLoadToSessionType in
+.Xr launchd.plist 5
+for more details.
+.It Ar help
+Print out a quick usage statement.
+.El
+.Sh ENVIRONMENTAL VARIABLES
+.Bl -tag -width -indent
+.It Pa LAUNCHD_SOCKET
+This variable informs launchctl how to find the correct launchd to talk to. If it is missing, launchctl will use a built-in default.
+.El
+.Sh FILES
+.Bl -tag -width "/System/Library/LaunchDaemons" -compact
+.It Pa ~/Library/LaunchAgents
+Per-user agents provided by the user.
+.It Pa /Library/LaunchAgents
+Per-user agents provided by the administrator.
+.It Pa /Library/LaunchDaemons
+System wide daemons provided by the administrator.
+.It Pa /System/Library/LaunchAgents
+Mac OS X Per-user agents.
+.It Pa /System/Library/LaunchDaemons
+Mac OS X System wide daemons.
+.El
+.Sh SEE ALSO
+.Xr launchd.plist 5 ,
+.Xr launchd.conf 5 ,
+.Xr launchd 8
--- /dev/null
+.Dd 1 May, 2009
+.Dt launchd 8
+.Os Darwin
+.Sh NAME
+.Nm launchd
+.Nd System wide and per-user daemon/agent manager
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl D
+.Op Fl s
+.Op Fl S Ar SessionType
+.Op Ar -- command Op Ar args ...
+.Sh DESCRIPTION
+.Nm
+manages processes, both for the system as a whole and for individual users.
+The primary and preferred interface to
+.Nm
+is via the
+.Xr launchctl 1
+tool which (among other options) allows the user or administrator to load and unload jobs.
+Where possible, it is preferable for jobs to launch on demand based on criteria specified
+in their respective configuration files.
+.Pp
+During boot
+.Nm
+is invoked by the kernel to run as the first process on the system and to further bootstrap the rest of the system.
+.Pp
+You cannot invoke
+.Nm
+directly.
+.Sh ENVIRONMENTAL VARIABLES
+.Bl -tag -width -indent
+.It Pa LAUNCHD_SOCKET
+This variable is exported when invoking a command via the launchd command line. It informs launchctl how to find the correct launchd to talk to.
+.El
+.Sh NOTES
+In Darwin, the canonical way to launch a daemon is through
+.Nm launchd
+as opposed to more traditional mechanisms or mechanisms provided in earlier versions of Mac OS X. These alternate methods should
+be considered deprecated and not suitable for new projects.
+.Pp
+In the
+.Nm launchd
+lexicon, a "daemon" is, by definition, a system-wide service of which there is one instance for all clients. An "agent" is a service that runs on
+a per-user basis. Daemons should not attempt to display UI or interact directly with a user's login session. Any and all work that involves interacting
+with a user should be done through agents.
+.Pp
+If you wish your service to run as a certain user, in that user's environment, making it a
+.Nm launchd
+agent is the ONLY supported means of accomplishing this on Mac OS X. In other words, it is not sufficient to perform a
+.Xr setuid 2
+to become a user in the truest sense on Mac OS X.
+.Sh FILES
+.Bl -tag -width "/System/Library/LaunchDaemons" -compact
+.It Pa ~/Library/LaunchAgents
+Per-user agents provided by the user.
+.It Pa /Library/LaunchAgents
+Per-user agents provided by the administrator.
+.It Pa /Library/LaunchDaemons
+System-wide daemons provided by the administrator.
+.It Pa /System/Library/LaunchAgents
+Per-user agents provided by Mac OS X.
+.It Pa /System/Library/LaunchDaemons
+System-wide daemons provided by Mac OS X.
+.El
+.Sh SEE ALSO
+.Xr launchctl 1 ,
+.Xr launchd.plist 5 ,
--- /dev/null
+.Dd 1 May, 2009
+.Dt launchd.conf 5
+.Os Darwin
+.Sh NAME
+.Nm launchd.conf
+.Nd launchd configuration file
+.Sh SYNOPSIS
+.Nm $HOME/.launchd.conf
+.Nm /etc/launchd.conf
+.Sh DESCRIPTION
+.Nm
+contains a list of subcommands
+.Ar ( load ,
+.Ar unload ,
+etc.) to run via
+.Xr launchctl 1
+when
+.Xr launchd 8
+starts.
+.Sh FILES
+.Bl -tag -width "$HOME/.launchd.conf" -compact
+.It Pa $HOME/.launchd.conf
+Your launchd configuration file (currently unsupported).
+.It Pa /etc/launchd.conf
+The system's launchd configuration file.
+.El
+.Sh SEE ALSO
+.Xr launchctl 1 ,
+.Xr launchd 8 ,
+.Xr launchd.plist 5
--- /dev/null
+.Dd 1 May, 2009
+.Dt launchd.plist 5
+.Os Darwin
+.Sh NAME
+.Nm launchd.plist
+.Nd System wide and per-user daemon/agent configuration files
+.Sh DESCRIPTION
+This document details the parameters that can be given to an XML property list that can be loaded into
+.Nm launchd
+with
+.Nm launchctl .
+.Sh EXPECTATIONS
+Daemons or agents managed by
+.Nm launchd
+are expected to behave certain ways.
+.Pp
+A daemon or agent launched by
+.Nm launchd
+MUST NOT do the following in the process directly launched by
+.Nm launchd :
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+Call
+.Xr daemon 3 .
+.It
+Do the moral equivalent of
+.Xr daemon 3
+by calling
+.Xr fork 2
+and have the parent process
+.Xr exit 3
+or
+.Xr _exit 2 .
+.El
+.Pp
+A daemon or agent launched by
+.Nm launchd
+SHOULD NOT do the following as a part of their startup initialization:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+Setup the user ID or group ID.
+.It
+Setup the working directory.
+.It
+.Xr chroot 2
+.It
+.Xr setsid 2
+.It
+Close "stray" file descriptors.
+.It
+Change
+.Xr stdio 3
+to /dev/null.
+.It
+Setup resource limits with
+.Xr setrusage 2 .
+.It
+Setup priority with
+.Xr setpriority 2 .
+.It
+Ignore the SIGTERM signal.
+.El
+.Pp
+A daemon or agent launched by
+.Nm launchd
+SHOULD:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+Launch on demand given criteria specified in the XML property list.
+More information can be found later in this man page.
+.It
+Catch the SIGTERM signal.
+.El
+.Sh XML PROPERTY LIST KEYS
+The following keys can be used to describe the configuration details of your daemon or agent.
+Property lists are Apple's standard configuration file format. Please see
+.Xr plist 5
+for more information. Please note: property list files are expected to have their name end in ".plist".
+Also please note that it is the expected convention for launchd property list files to be named <Label>.plist.
+Thus, if your job label is "com.apple.sshd", your plist file should be named "com.apple.sshd.plist".
+.Pp
+.Bl -ohang
+.It Sy Label <string>
+This required key uniquely identifies the job to
+.Nm launchd .
+.It Sy Disabled <boolean>
+This optional key is used as a hint to
+.Xr launchctl 1
+that it should not submit this job to
+.Nm launchd
+when loading a job or jobs. The value of this key does NOT reflect the current state of the job on the running system. If you wish to know whether a job is loaded in launchd, reading this key from a configuration file yourself is not a
+sufficient test. You should query
+.Nm launchd
+for the presence of the job using the
+.Xr launchctl 1
+.Ar list
+subcommand or use the ServiceManagement framework's SMJobCopyDictionary() method.
+.Pp
+Note that as of Mac OS X v10.6, this key's value in a configuration
+file conveys a default value, which is changed with the
+.Op Ar -w
+option of the
+.Xr launchctl 1
+.Ar load
+and
+.Ar unload
+subcommands. These subcommands no longer modify the configuration file, so the value displayed in the configuration file is not necessarily the value that
+.Xr launchctl 1
+will apply. See
+.Xr launchctl 1
+for more information.
+.Pp
+Please also be mindful that you should only use this key if the provided
+on-demand and KeepAlive criteria are insufficient to describe the conditions under which your job needs to run. The cost
+to have a job loaded in
+.Nm launchd
+is negligible, so there is no harm in loading a job which only runs once or very rarely.
+.It Sy UserName <string>
+This optional key specifies the user to run the job as. This key is only applicable when launchd is running as root.
+.It Sy GroupName <string>
+This optional key specifies the group to run the job as. This key is only applicable when launchd is running as root. If UserName is set and GroupName is not, the the group will be set to the default group of the user.
+.It Sy inetdCompatibility <dictionary>
+The presence of this key specifies that the daemon expects to be run as if it were launched from inetd.
+.Bl -ohang -offset indent
+.It Sy Wait <boolean>
+This flag corresponds to the "wait" or "nowait" option of inetd. If true, then the listening socket is passed via the standard in/out/error file descriptors. If false, then
+.Xr accept 2
+is called on behalf of the job, and the result is passed via the standard in/out/error descriptors.
+.El
+.It Sy LimitLoadToHosts <array of strings>
+This configuration file only applies to the hosts listed with this key. Note: One should set kern.hostname in
+.Xr sysctl.conf 5
+for this feature to work reliably.
+.It Sy LimitLoadFromHosts <array of strings>
+This configuration file only applies to hosts NOT listed with this key. Note: One should set kern.hostname in
+.Xr sysctl.conf 5
+for this feature to work reliably.
+.It Sy LimitLoadToSessionType <string>
+This configuration file only applies to sessions of the type specified. This key is used
+in concert with the -S flag to
+.Nm launchctl .
+.It Sy Program <string>
+This key maps to the first argument of
+.Xr execvp 3 .
+If this key is missing, then the first element of the array of strings provided to the ProgramArguments will be used instead.
+This key is required in the absence of the ProgramArguments key.
+.It Sy ProgramArguments <array of strings>
+This key maps to the second argument of
+.Xr execvp 3 .
+This key is required in the absence of the Program key. Please note: many people are confused by this key. Please read
+.Xr execvp 3
+very carefully!
+.It Sy EnableGlobbing <boolean>
+This flag causes
+.Nm launchd
+to use the
+.Xr glob 3
+mechanism to update the program arguments before invocation.
+.It Sy EnableTransactions <boolean>
+This flag instructs
+.Nm launchd
+that the job promises to use
+.Xr vproc_transaction_begin 3
+and
+.Xr vproc_transaction_end 3
+to track outstanding transactions that need to be reconciled before the process can safely terminate. If no outstanding transactions are in progress, then
+.Nm launchd
+is free to send the SIGKILL signal.
+.It Sy OnDemand <boolean>
+This key was used in Mac OS X 10.4 to control whether a job was kept alive or not. The default was true.
+This key has been deprecated and replaced in Mac OS X 10.5 and later with the more powerful KeepAlive option.
+.It Sy KeepAlive <boolean or dictionary of stuff>
+This optional key is used to control whether your job is to be kept
+continuously running or to let demand and conditions control the invocation. The
+default is false and therefore only demand will start the job. The value may be
+set to true to unconditionally keep the job alive. Alternatively, a dictionary
+of conditions may be specified to selectively control whether
+.Nm launchd
+keeps a job alive or not. If multiple keys are provided, launchd ORs them, thus
+providing maximum flexibility to the job to refine the logic and stall if necessary. If
+.Nm launchd
+finds no reason to restart the job, it falls back on demand based invocation.
+Jobs that exit quickly and frequently when configured to be kept alive will be
+throttled to converve system resources.
+.Bl -ohang -offset indent
+.It Sy SuccessfulExit <boolean>
+If true, the job will be restarted as long as the program exits and with an exit
+status of zero. If false, the job will be restarted in the inverse condition.
+This key implies that "RunAtLoad" is set to true, since the job needs to run at
+least once before we can get an exit status.
+.It Sy NetworkState <boolean>
+If true, the job will be kept alive as long as the network is up, where up is
+defined as at least one non-loopback interface being up and having IPv4 or IPv6
+addresses assigned to them.
+If false, the job will be kept alive in the inverse condition.
+.It Sy PathState <dictionary of booleans>
+Each key in this dictionary is a file-system path. If the value of the key is
+true, then the job will be kept alive as long as the path exists.
+If false, the job will be kept alive in the inverse condition. The intent of this
+feature is that two or more jobs may create semaphores in the file-system namespace.
+.It Sy OtherJobEnabled <dictionary of booleans>
+Each key in this dictionary is the label of another job. If the value of the key is
+true, then this job is kept alive as long as that other job is enabled. Otherwise,
+if the value is false, then this job is kept alive as long as the other job is disabled.
+This feature should not be considered a substitute for the use of IPC.
+.El
+.It Sy RunAtLoad <boolean>
+This optional key is used to control whether your job is launched once at the time the job is loaded. The default is false.
+.It Sy RootDirectory <string>
+This optional key is used to specify a directory to
+.Xr chroot 2
+to before running the job.
+.It Sy WorkingDirectory <string>
+This optional key is used to specify a directory to
+.Xr chdir 2
+to before running the job.
+.It Sy EnvironmentVariables <dictionary of strings>
+This optional key is used to specify additional environmental variables to be set before running the job.
+.It Sy Umask <integer>
+This optional key specifies what value should be passed to
+.Xr umask 2
+before running the job. Known bug: Property lists don't support octal, so please convert the value to decimal.
+.It Sy TimeOut <integer>
+The recommended idle time out (in seconds) to pass to the job. If no value is specified, a default time out will be supplied by
+.Nm launchd
+for use by the job at check in time.
+.It Sy ExitTimeOut <integer>
+The amount of time
+.Nm launchd
+waits before sending a SIGKILL signal. The default value is 20 seconds. The value zero is interpreted as infinity.
+.It Sy ThrottleInterval <integer>
+This key lets one override the default throttling policy imposed on jobs by
+.Nm launchd .
+The value is in seconds, and by default, jobs will not be spawned more than once every 10 seconds.
+The principle behind this is that jobs should linger around just in case they are needed again in the near future. This not only
+reduces the latency of responses, but it encourages developers to amortize the cost of program invocation.
+.It Sy InitGroups <boolean>
+This optional key specifies whether
+.Xr initgroups 3
+should be called before running the job.
+The default is true in 10.5 and false in 10.4. This key will be ignored if the UserName key is not set.
+.It Sy WatchPaths <array of strings>
+This optional key causes the job to be started if any one of the listed paths are modified.
+.It Sy QueueDirectories <array of strings>
+Much like the WatchPaths option, this key will watch the paths for modifications. The difference being that the job will only be started if the path is a directory and the directory is not empty.
+.It Sy StartOnMount <boolean>
+This optional key causes the job to be started every time a filesystem is mounted.
+.It Sy StartInterval <integer>
+This optional key causes the job to be started every N seconds.
+If the system is asleep, the job will be started the next time the computer
+wakes up. If multiple intervals transpire before the computer is woken, those
+events will be coalesced into one event upon wake from sleep.
+.It Sy StartCalendarInterval <dictionary of integers or array of dictionary of integers>
+This optional key causes the job to be started every calendar interval as specified. Missing arguments are considered to be wildcard. The semantics are much like
+.Xr crontab 5 .
+Unlike cron which skips job invocations when the computer is asleep, launchd
+will start the job the next time the computer wakes up. If multiple intervals
+transpire before the computer is woken, those events will be coalesced into one
+event upon wake from sleep.
+.Bl -ohang -offset indent
+.It Sy Minute <integer>
+The minute on which this job will be run.
+.It Sy Hour <integer>
+The hour on which this job will be run.
+.It Sy Day <integer>
+The day on which this job will be run.
+.It Sy Weekday <integer>
+The weekday on which this job will be run (0 and 7 are Sunday).
+.It Sy Month <integer>
+The month on which this job will be run.
+.El
+.It Sy StandardInPath <string>
+This optional key specifies what file should be used for data being supplied to stdin when using
+.Xr stdio 3 .
+.It Sy StandardOutPath <string>
+This optional key specifies what file should be used for data being sent to stdout when using
+.Xr stdio 3 .
+.It Sy StandardErrorPath <string>
+This optional key specifies what file should be used for data being sent to stderr when using
+.Xr stdio 3 .
+.It Sy Debug <boolean>
+This optional key specifies that
+.Nm launchd
+should adjust its log mask temporarily to LOG_DEBUG while dealing with this job.
+.It Sy WaitForDebugger <boolean>
+This optional key specifies that
+.Nm launchd
+should instruct the kernel to have the job wait for a debugger to attach before any code in the job is executed.
+.It Sy SoftResourceLimits <dictionary of integers>
+.It Sy HardResourceLimits <dictionary of integers>
+Resource limits to be imposed on the job. These adjust variables set with
+.Xr setrlimit 2 .
+The following keys apply:
+.Bl -ohang -offset indent
+.It Sy Core <integer>
+The largest size (in bytes) core file that may be created.
+.It Sy CPU <integer>
+The maximum amount of cpu time (in seconds) to be used by each process.
+.It Sy Data <integer>
+The maximum size (in bytes) of the data segment for a process; this defines how far a program may extend its break with the
+.Xr sbrk 2
+system call.
+.It Sy FileSize <integer>
+The largest size (in bytes) file that may be created.
+.It Sy MemoryLock <integer>
+The maximum size (in bytes) which a process may lock into memory using the
+.Xr mlock 2
+function.
+.It Sy NumberOfFiles <integer>
+The maximum number of open files for this process.
+Setting this value in a system wide daemon will set the
+.Xr sysctl 3
+kern.maxfiles (SoftResourceLimits) or kern.maxfilesperproc (HardResourceLimits) value in addition to the
+.Xr setrlimit 2
+values.
+.It Sy NumberOfProcesses <integer>
+The maximum number of simultaneous processes for this user id.
+Setting this value in a system wide daemon will set the
+.Xr sysctl 3
+kern.maxproc (SoftResourceLimits) or kern.maxprocperuid (HardResourceLimits)
+value in addition to the
+.Xr setrlimit 2
+values.
+.It Sy ResidentSetSize <integer>
+The maximum size (in bytes) to which a process's resident set size may grow.
+This imposes a limit on the amount of physical memory to be given to a process;
+if memory is tight, the system will prefer to take memory from processes that
+are exceeding their declared resident set size.
+.It Sy Stack <integer>
+The maximum size (in bytes) of the stack segment for a process; this defines
+how far a program's stack segment may be extended. Stack extension is
+performed automatically by the system.
+.El
+.It Sy Nice <integer>
+This optional key specifies what
+.Xr nice 3
+value should be applied to the daemon.
+.It Sy AbandonProcessGroup <boolean>
+When a job dies,
+.Nm launchd
+kills any remaining processes with the same process group ID as the job.
+Setting this key to true disables that behavior.
+.It Sy LowPriorityIO <boolean>
+This optional key specifies whether the kernel should consider this daemon to be low priority when doing file system I/O.
+.It Sy LaunchOnlyOnce <boolean>
+This optional key specifies whether the job can only be run once and only once.
+In other words, if the job cannot be safely respawned without a full machine
+reboot, then set this key to be true.
+.It Sy MachServices <dictionary of booleans or a dictionary of dictionaries>
+This optional key is used to specify Mach services to be registered with the
+Mach bootstrap sub-system. Each key in this dictionary should be the name of
+service to be advertised. The value of the key must be a boolean and set to true.
+Alternatively, a dictionary can be used instead of a simple true value.
+.Bl -ohang -offset indent
+.It Sy ResetAtClose <boolean>
+If this boolean is false, the port is recycled, thus leaving clients to remain oblivious
+to the demand nature of job. If the value is set to true, clients receive port
+death notifications when the job lets go of the receive right. The port will be
+recreated atomically with respect to bootstrap_look_up() calls, so that clients
+can trust that after receiving a port death notification, the new port will
+have already been recreated. Setting the value to true should be done with
+care. Not all clients may be able to handle this behavior. The default value is false.
+.It Sy HideUntilCheckIn <boolean>
+Reserve the name in the namespace, but cause bootstrap_look_up() to fail until the job has checked in with
+.Nm launchd .
+.El
+.Pp
+Finally, for the job itself, the values will be replaced with Mach ports at the time of check-in with
+.Nm launchd .
+.It Sy Sockets <dictionary of dictionaries... OR dictionary of array of dictionaries...>
+This optional key is used to specify launch on demand sockets that can be used to let
+.Nm launchd
+know when to run the job. The job must check-in to get a copy of the file descriptors using APIs outlined in
+.Xr launch 3 .
+The keys of the top level Sockets dictionary can be anything. They are meant for the application developer to use to
+differentiate which descriptors correspond to which application level protocols (e.g. http vs. ftp vs. DNS...).
+At check-in time, the value of each Sockets dictionary key will be an array of descriptors. Daemon/Agent writers should
+consider all descriptors of a given key to be to be effectively equivalent, even though each file descriptor likely represents
+a different networking protocol which conforms to the criteria specified in the job configuration file.
+.Pp
+The parameters below are used as inputs to call
+.Xr getaddrinfo 3 .
+.Bl -ohang -offset indent
+.It Sy SockType <string>
+This optional key tells
+.Nm launchctl
+what type of socket to create. The default is "stream" and other valid values for this key
+are "dgram" and "seqpacket" respectively.
+.It Sy SockPassive <boolean>
+This optional key specifies whether
+.Xr listen 2
+or
+.Xr connect 2
+should be called on the created file descriptor. The default is true ("to listen").
+.It Sy SockNodeName <string>
+This optional key specifies the node to
+.Xr connect 2
+or
+.Xr bind 2
+to.
+.It Sy SockServiceName <string>
+This optional key specifies the service on the node to
+.Xr connect 2
+or
+.Xr bind 2
+to.
+.It Sy SockFamily <string>
+This optional key can be used to specifically request that "IPv4" or "IPv6" socket(s) be created.
+.It Sy SockProtocol <string>
+This optional key specifies the protocol to be passed to
+.Xr socket 2 .
+The only value understood by this key at the moment is "TCP".
+.It Sy SockPathName <string>
+This optional key implies SockFamily is set to "Unix". It specifies the path to
+.Xr connect 2
+or
+.Xr bind 2
+to.
+.It Sy SecureSocketWithKey <string>
+This optional key is a variant of SockPathName. Instead of binding to a known
+path, a securely generated socket is created and the path is assigned to the
+environment variable that is inherited by all jobs spawned by launchd.
+.It Sy SockPathMode <integer>
+This optional key specifies the mode of the socket. Known bug: Property lists
+don't support octal, so please convert the value to decimal.
+.It Sy Bonjour <boolean or string or array of strings>
+This optional key can be used to request that the service be registered with the
+.Xr mDNSResponder 8 .
+If the value is boolean, the service name is inferred from the SockServiceName.
+.It Sy MulticastGroup <string>
+This optional key can be used to request that the datagram socket join a multicast group.
+If the value is a hostname, then
+.Xr getaddrinfo 3
+will be used to join the correct multicast address for a given socket family.
+If an explicit IPv4 or IPv6 address is given, it is required that the
+SockFamily family also be set, otherwise the results are undefined.
+.El
+.El
+.Pp
+.Sh DEPENDENCIES
+Unlike many bootstrapping daemons, launchd has no explicit dependency model.
+Interdependencies are expected to be solved through the use of IPC.
+It is therefore in the best interest of a job developer who expects dependents
+to define all of the sockets in the configuration file. This has the added
+benefit of making it possible to start the job based on demand instead of
+immediately.
+.Sh EXAMPLE XML PROPERTY LISTS
+.Pp
+The following XML Property List simply keeps "exampled" running continuously:
+.Pp
+.Dl <?xml version="1.0" encoding="UTF-8"?>
+.Dl <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+.Dl <plist version="1.0">
+.Dl <dict>
+.Dl <key>Label</key>
+.Dl <string>com.example.exampled</string>
+.Dl <key>ProgramArguments</key>
+.Dl <array>
+.Dl <string>exampled</string>
+.Dl </array>
+.Dl <key>KeepAlive</key>
+.Dl <true/>
+.Dl </dict>
+.Dl </plist>
+.Pp
+.Sh FILES
+.Bl -tag -width "/System/Library/LaunchDaemons" -compact
+.It Pa ~/Library/LaunchAgents
+Per-user agents provided by the user.
+.It Pa /Library/LaunchAgents
+Per-user agents provided by the administrator.
+.It Pa /Library/LaunchDaemons
+System-wide daemons provided by the administrator.
+.It Pa /System/Library/LaunchAgents
+Per-user agents provided by Mac OS X.
+.It Pa /System/Library/LaunchDaemons
+System-wide daemons provided by Mac OS X.
+.El
+.Sh SEE ALSO
+.Xr launchctl 1 ,
+.Xr sysctl 3 ,
+.Xr launchd 8 ,
+.Xr plist 5
--- /dev/null
+.Dd February 2, 2005
+.Dt launchproxy 8
+.Os Darwin
+.Sh NAME
+.Nm launchproxy
+.Nd inetd job emulation helper
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+.Nm
+handles the inetd emulation for
+.Nm launchd .
+This program may be merged into
+.Nm launchd
+in the future.
+.Sh SEE ALSO
+.Xr launchctl 1 ,
+.Xr launchd.plist 5 ,
+.Xr launchd 8
--- /dev/null
+.Dd 1 May, 2009
+.Dt RC 8
+.Os Darwin
+.Sh NAME
+.Nm rc
+.Nd command script for boot
+.Sh SYNOPSIS
+.Nm rc
+.Nm rc.local
+.Sh DESCRIPTION
+.Nm rc.local
+is now unsupported and has been replaced with
+.Xr launchd 8 ,
+which bootstraps itself via the
+.Xr launchctl 1
+.Ar bootstrap
+subcommand to read in
+.Xr launchd 8
+jobs from the standard locations.
+.Sh SEE ALSO
+.Xr launchd 8 ,
+.Xr launchctl 1
--- /dev/null
+.Dd April 6, 2004
+.Dt WAIT4PATH 1
+.Os "Mac OS X" 10.4
+.Sh NAME
+.Nm wait4path
+.Nd wait for given path to show up in the namespace
+.Sh SYNOPSIS
+.Nm
+.Ao Ar path Ac
+.Sh DESCRIPTION
+The
+.Nm
+program simply checks to see if the given path exists, and if so, it exits. Otherwise, it sleeps until the mount table is updated and checks again. The program will loop indefinitely until the path shows up in the file system namespace.
--- /dev/null
+##
+# Common setup for startup scripts.
+##
+# Copyright 1998-2002 Apple Computer, Inc.
+##
+
+#######################
+# Configure the shell #
+#######################
+
+##
+# Be strict
+##
+#set -e
+set -u
+
+##
+# Set command search path
+##
+PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/libexec:/System/Library/CoreServices; export PATH
+
+##
+# Set the terminal mode
+##
+#if [ -x /usr/bin/tset ] && [ -f /usr/share/misc/termcap ]; then
+# TERM=$(tset - -Q); export TERM
+#fi
+
+####################
+# Useful functions #
+####################
+
+##
+# Determine if the network is up by looking for any non-loopback
+# internet network interfaces.
+##
+CheckForNetwork()
+{
+ local test
+
+ if [ -z "${NETWORKUP:=}" ]; then
+ test=$(ifconfig -a inet 2>/dev/null | sed -n -e '/127.0.0.1/d' -e '/0.0.0.0/d' -e '/inet/p' | wc -l)
+ if [ "${test}" -gt 0 ]; then
+ NETWORKUP="-YES-"
+ else
+ NETWORKUP="-NO-"
+ fi
+ fi
+}
+
+alias ConsoleMessage=echo
+
+##
+# Process management
+##
+GetPID ()
+{
+ local program="$1"
+ local pidfile="${PIDFILE:=/var/run/${program}.pid}"
+ local pid=""
+
+ if [ -f "${pidfile}" ]; then
+ pid=$(head -1 "${pidfile}")
+ if ! kill -0 "${pid}" 2> /dev/null; then
+ echo "Bad pid file $pidfile; deleting."
+ pid=""
+ rm -f "${pidfile}"
+ fi
+ fi
+
+ if [ -n "${pid}" ]; then
+ echo "${pid}"
+ return 0
+ else
+ return 1
+ fi
+}
+
+##
+# Generic action handler
+##
+RunService ()
+{
+ case $1 in
+ start ) StartService ;;
+ stop ) StopService ;;
+ restart) RestartService ;;
+ * ) echo "$0: unknown argument: $1";;
+ esac
+}
+
+##########################
+# Get host configuration #
+##########################
+. /etc/hostconfig
--- /dev/null
+#!/bin/sh
+##
+# Copyright 2002-2009 Apple Inc.
+#
+# This script configures NetBoot
+##
+
+. /etc/rc.common
+
+#
+# Define: NETBOOT_SHADOW
+# Purpose:
+# To change the behavior of the system when choosing a netboot shadow
+# to use.
+# Values:
+# -NETWORK- Try to use the network for the shadow file, if
+# that fails, use the local drive
+# -NETWORK_ONLY- Only use the network, fail if not available
+# -LOCAL- Use the local drive for the shadow file, if that
+# fails, use the network
+# -LOCAL_ONLY- Only use the local drive for the shadow, fail if
+# not available
+
+NETBOOT_MOUNT=/var/netboot
+NETBOOT_SHADOW=${NETBOOT_SHADOW:-NETWORK-}
+
+Failed()
+{
+ echo rc.netboot: $1
+ echo rc.netboot: $1 > /dev/console
+ sleep 5
+ exit 1
+}
+
+common_start()
+{
+ netboot_dir=$1
+ netboot_shadow=$2
+ if [ "${netboot_dir}" = "" ] ; then
+ Failed "netboot_dir is empty"
+ fi
+ if [ "${netboot_shadow}" = "" ] ; then
+ Failed "netboot_shadow is empty"
+ fi
+ netboot_shadow="${netboot_dir}/${netboot_shadow}"
+ if ! mkdir -p "${netboot_dir}" ; then
+ Failed "create ${netboot_dir} failed"
+ fi
+ chmod 700 "${netboot_dir}"
+ mount -u -o ro /
+ root_device=$(mount | sed -n 's:/dev/\(.*\) on / .*:\1:p')
+ case "${root_device}" in
+ vn*)
+ if ! touch "${netboot_shadow}" ; then
+ Failed "create ${netboot_shadow} failed"
+ fi
+ chmod 600 "${netboot_shadow}"
+ if ! /usr/libexec/vndevice shadow "/dev/r${root_device}" "${netboot_shadow}" ; then
+ Failed "vndevice shadow failed"
+ fi
+ ;;
+ "")
+ Failed "root device unknown"
+ ;;
+ *)
+ if ! touch "${netboot_shadow}" ; then
+ Failed "failed to create shadow ${netboot_shadow}"
+ fi
+ chmod 600 "${netboot_shadow}"
+ if ! /usr/bin/nbdst -recycle "${root_device}" "${netboot_shadow}" ; then
+ Failed "nbdst failed"
+ fi
+ ;;
+ esac
+}
+
+local_mount()
+{
+ tries=0
+ limit=11
+ while [ $tries -lt $limit ]; do
+ tries=$(( tries + 1 ))
+ volinfo=`autodiskmount -F 2>/dev/null`
+ if [ $? -ne 0 ]; then
+ if [ $tries -lt $limit ]; then
+ echo "Waiting for local drives..."
+ echo "Waiting for local drives (retry ${tries}/$(( limit - 1 )))..." > /dev/console
+ sleep 5
+ else
+ echo "autodiskmount -F found no local drives"
+ return 1
+ fi
+ else
+ tries=$limit
+ fi
+ done
+ set ${volinfo}
+ devname=$1
+ fstype=$2
+
+ mount -t "${fstype}" -o nosuid,nodev "/dev/${devname}" "${NETBOOT_MOUNT}" 2>&1
+ if [ $? -ne 0 ]; then
+ echo "mount of ${devname} failed"
+ return 1
+ fi
+ common_start "${NETBOOT_MOUNT}/.com.apple.NetBootX" shadowfile
+ return 0
+}
+
+network_mount()
+{
+ mount_from=$(ipconfig netbootoption shadow_mount_path 2>&1)
+ if [ $? -ne 0 ]; then
+ echo "no network shadow mount path available"
+ return 1
+ fi
+ shadow_path=$(ipconfig netbootoption shadow_file_path 2>&1)
+ if [ $? -ne 0 ]; then
+ echo "no network shadow file path available"
+ return 1
+ fi
+ case "${mount_from}" in
+ afp:*)
+ fstype=afp
+ kextload -v 0 /System/Library/Filesystems/AppleShare/asp_tcp.kext
+ kextload -v 0 /System/Library/Filesystems/AppleShare/afpfs.kext
+ ;;
+ nfs:*) fstype=nfs;;
+ *) echo "unknown network filesystem mount from ${mount_from}"
+ return 1
+ ;;
+ esac
+ mount -t "${fstype}" -o nobrowse "${mount_from}" "${NETBOOT_MOUNT}"
+ if [ $? -ne 0 ]; then
+ echo "mount -t ${fstype} -o nobrowse ${mount_from} ${NETBOOT_MOUNT} failed"
+ return 1
+ fi
+ common_start "${NETBOOT_MOUNT}" "${shadow_path}"
+ return 0
+}
+
+do_start()
+{
+ case "${NETBOOT_SHADOW}" in
+ -LOCAL_ONLY-)
+ err=$(local_mount)
+ if [ $? -ne 0 ]; then
+ Failed "${err}"
+ fi
+ ;;
+ -LOCAL-)
+ err=$(local_mount)
+ if [ $? -ne 0 ]; then
+ err=$(network_mount)
+ if [ $? -ne 0 ]; then
+ Failed "Could not find a local or network drive"
+ fi
+ fi
+ ;;
+ -NETWORK_ONLY-)
+ err=$(network_mount)
+ if [ $? -ne 0 ]; then
+ Failed "${err}"
+ fi
+ ;;
+
+ *)
+ err=$(network_mount)
+ if [ $? -ne 0 ]; then
+ err=$(local_mount)
+ if [ $? -ne 0 ]; then
+ Failed "Could not find a network or local drive"
+ fi
+ fi
+ ;;
+ esac
+
+}
+
+do_init()
+{
+ # attach the shadow file to the root disk image
+ do_start
+
+ # make sure the root filesystem is clean
+ fsck -p || fsck -fy || Failed "Could not clean root filesystem"
+
+ # make it writable
+ mount -uw /
+
+ # adjust /private/var/vm to point to the writable area (if not diskless)
+ swapdir=/private/var/vm
+ mounted_from=$(mount | sed -n 's:\(.*\) on .*/var/netboot.*:\1:p')
+ case "${mounted_from}" in
+ /dev/*)
+ netboot_dir="${NETBOOT_MOUNT}/.com.apple.NetBootX"
+ if [ -d "${netboot_dir}" ]; then
+ rm -rf "${swapdir}"
+ ln -s "${netboot_dir}" "${swapdir}"
+ fi
+ ;;
+ *)
+ ;;
+ esac
+
+ # set the ComputerName based on what the NetBoot server told us it was
+ machine_name=$(ipconfig netbootoption machine_name 2>&1)
+ if [ $? -ne 0 ]; then
+ echo "no machine name option available"
+ else
+ echo "Setting ComputerName to ${machine_name}"
+ scutil --set ComputerName "${machine_name}"
+ fi
+}
+
+
+if [ $# -lt 1 ] ; then
+ exit 0
+fi
+
+command=$1
+
+shift
+
+case "${command}" in
+ init)
+ do_init $@
+ ;;
+esac
+
+##
+# Exit
+##
+exit 0
--- /dev/null
+#ifndef __CONFIG_H__
+#define __CONFIG_H__
+
+#include <TargetConditionals.h>
+
+#if __has_include(<quarantine.h>)
+#define HAVE_QUARANTINE 1
+#else
+#define HAVE_QUARANTINE 0
+#endif
+
+#if __has_include(<sandbox.h>)
+#define HAVE_SANDBOX 1
+#else
+#define HAVE_SANDBOX 0
+#endif
+
+#define HAVE_LIBAUDITD !TARGET_OS_EMBEDDED
+
+#endif /* __CONFIG_H__ */
--- /dev/null
+/*
+ * @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 "config.h"
+#include "core.h"
+#include "internal.h"
+#include "helper.h"
+
+#include <TargetConditionals.h>
+#include <mach/mach.h>
+#include <mach/mach_error.h>
+#include <mach/boolean.h>
+#include <mach/message.h>
+#include <mach/notify.h>
+#include <mach/mig_errors.h>
+#include <mach/mach_traps.h>
+#include <mach/mach_interface.h>
+#include <mach/host_info.h>
+#include <mach/mach_host.h>
+#include <mach/exception.h>
+#include <mach/host_reboot.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/event.h>
+#include <sys/stat.h>
+#include <sys/ucred.h>
+#include <sys/fcntl.h>
+#include <sys/un.h>
+#include <sys/reboot.h>
+#include <sys/wait.h>
+#include <sys/sysctl.h>
+#include <sys/sockio.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <sys/pipe.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <sys/kern_memorystatus.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet6/nd6.h>
+#include <bsm/libbsm.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <paths.h>
+#include <pwd.h>
+#include <grp.h>
+#include <ttyent.h>
+#include <dlfcn.h>
+#include <dirent.h>
+#include <string.h>
+#include <ctype.h>
+#include <glob.h>
+#include <System/sys/spawn.h>
+#include <System/sys/spawn_internal.h>
+#include <spawn.h>
+#include <spawn_private.h>
+#include <time.h>
+#include <libinfo.h>
+#include <assumes.h>
+#include <xpc/launchd.h>
+
+#include <libproc.h>
+#include <System/sys/proc_info.h>
+#include <malloc/malloc.h>
+#include <pthread.h>
+#include <libproc.h>
+#if HAVE_SANDBOX
+#define __APPLE_API_PRIVATE
+#include <sandbox.h>
+#endif
+#if HAVE_QUARANTINE
+#include <quarantine.h>
+#endif
+#if !TARGET_OS_EMBEDDED
+extern int gL1CacheEnabled;
+#endif
+
+#include "launch.h"
+#include "launch_priv.h"
+#include "launch_internal.h"
+#include "bootstrap.h"
+#include "bootstrap_priv.h"
+#include "vproc.h"
+#include "vproc_internal.h"
+
+#include "reboot2.h"
+
+#include "launchd.h"
+#include "runtime.h"
+#include "ipc.h"
+#include "job.h"
+#include "jobServer.h"
+#include "job_reply.h"
+#include "job_forward.h"
+#include "mach_excServer.h"
+
+#define POSIX_SPAWN_IOS_INTERACTIVE 0
+
+/* LAUNCHD_DEFAULT_EXIT_TIMEOUT
+ * If the job hasn't exited in the given number of seconds after sending
+ * it a SIGTERM, SIGKILL it. Can be overriden in the job plist.
+ */
+#define LAUNCHD_MIN_JOB_RUN_TIME 10
+#define LAUNCHD_DEFAULT_EXIT_TIMEOUT 20
+#define LAUNCHD_SIGKILL_TIMER 4
+#define LAUNCHD_LOG_FAILED_EXEC_FREQ 10
+
+#define SHUTDOWN_LOG_DIR "/var/log/shutdown"
+
+#define TAKE_SUBSET_NAME "TakeSubsetName"
+#define TAKE_SUBSET_PID "TakeSubsetPID"
+#define TAKE_SUBSET_PERPID "TakeSubsetPerPID"
+
+#define IS_POWER_OF_TWO(v) (!(v & (v - 1)) && v)
+
+extern char **environ;
+
+struct waiting_for_removal {
+ SLIST_ENTRY(waiting_for_removal) sle;
+ mach_port_t reply_port;
+};
+
+static bool waiting4removal_new(job_t j, mach_port_t rp);
+static void waiting4removal_delete(job_t j, struct waiting_for_removal *w4r);
+
+struct machservice {
+ SLIST_ENTRY(machservice) sle;
+ SLIST_ENTRY(machservice) special_port_sle;
+ LIST_ENTRY(machservice) name_hash_sle;
+ LIST_ENTRY(machservice) port_hash_sle;
+ struct machservice *alias;
+ job_t job;
+ unsigned int gen_num;
+ mach_port_name_t port;
+ unsigned int
+ isActive:1,
+ reset:1,
+ recv:1,
+ hide:1,
+ kUNCServer:1,
+ per_user_hack:1,
+ debug_on_close:1,
+ per_pid:1,
+ delete_on_destruction:1,
+ drain_one_on_crash:1,
+ drain_all_on_crash:1,
+ upfront:1,
+ event_channel:1,
+ /* Don't let the size of this field to get too small. It has to be large
+ * enough to represent the reasonable range of special port numbers.
+ */
+ special_port_num:18;
+ const char name[0];
+};
+
+// HACK: This should be per jobmgr_t
+static SLIST_HEAD(, machservice) special_ports;
+
+#define PORT_HASH_SIZE 32
+#define HASH_PORT(x) (IS_POWER_OF_TWO(PORT_HASH_SIZE) ? (MACH_PORT_INDEX(x) & (PORT_HASH_SIZE - 1)) : (MACH_PORT_INDEX(x) % PORT_HASH_SIZE))
+
+static LIST_HEAD(, machservice) port_hash[PORT_HASH_SIZE];
+
+static void machservice_setup(launch_data_t obj, const char *key, void *context);
+static void machservice_setup_options(launch_data_t obj, const char *key, void *context);
+static void machservice_resetport(job_t j, struct machservice *ms);
+static void machservice_stamp_port(job_t j, struct machservice *ms);
+static struct machservice *machservice_new(job_t j, const char *name, mach_port_t *serviceport, bool pid_local);
+static struct machservice *machservice_new_alias(job_t aj, struct machservice *orig);
+static void machservice_ignore(job_t j, struct machservice *ms);
+static void machservice_watch(job_t j, struct machservice *ms);
+static void machservice_delete(job_t j, struct machservice *, bool port_died);
+static void machservice_request_notifications(struct machservice *);
+static mach_port_t machservice_port(struct machservice *);
+static job_t machservice_job(struct machservice *);
+static bool machservice_hidden(struct machservice *);
+static bool machservice_active(struct machservice *);
+static const char *machservice_name(struct machservice *);
+static bootstrap_status_t machservice_status(struct machservice *);
+void machservice_drain_port(struct machservice *);
+
+struct socketgroup {
+ SLIST_ENTRY(socketgroup) sle;
+ int *fds;
+ unsigned int fd_cnt;
+ union {
+ const char name[0];
+ char name_init[0];
+ };
+};
+
+static bool socketgroup_new(job_t j, const char *name, int *fds, size_t fd_cnt);
+static void socketgroup_delete(job_t j, struct socketgroup *sg);
+static void socketgroup_watch(job_t j, struct socketgroup *sg);
+static void socketgroup_ignore(job_t j, struct socketgroup *sg);
+static void socketgroup_callback(job_t j);
+static void socketgroup_setup(launch_data_t obj, const char *key, void *context);
+static void socketgroup_kevent_mod(job_t j, struct socketgroup *sg, bool do_add);
+
+struct calendarinterval {
+ LIST_ENTRY(calendarinterval) global_sle;
+ SLIST_ENTRY(calendarinterval) sle;
+ job_t job;
+ struct tm when;
+ time_t when_next;
+};
+
+static LIST_HEAD(, calendarinterval) sorted_calendar_events;
+
+static bool calendarinterval_new(job_t j, struct tm *w);
+static bool calendarinterval_new_from_obj(job_t j, launch_data_t obj);
+static void calendarinterval_new_from_obj_dict_walk(launch_data_t obj, const char *key, void *context);
+static void calendarinterval_delete(job_t j, struct calendarinterval *ci);
+static void calendarinterval_setalarm(job_t j, struct calendarinterval *ci);
+static void calendarinterval_callback(void);
+static void calendarinterval_sanity_check(void);
+
+struct envitem {
+ SLIST_ENTRY(envitem) sle;
+ char *value;
+ union {
+ const char key[0];
+ char key_init[0];
+ };
+};
+
+static bool envitem_new(job_t j, const char *k, const char *v, bool global);
+static void envitem_delete(job_t j, struct envitem *ei, bool global);
+static void envitem_setup(launch_data_t obj, const char *key, void *context);
+
+struct limititem {
+ SLIST_ENTRY(limititem) sle;
+ struct rlimit lim;
+ unsigned int setsoft:1, sethard:1, which:30;
+};
+
+static bool limititem_update(job_t j, int w, rlim_t r);
+static void limititem_delete(job_t j, struct limititem *li);
+static void limititem_setup(launch_data_t obj, const char *key, void *context);
+#if HAVE_SANDBOX
+static void seatbelt_setup_flags(launch_data_t obj, const char *key, void *context);
+#endif
+
+static void jetsam_property_setup(launch_data_t obj, const char *key, job_t j);
+
+typedef enum {
+ NETWORK_UP = 1,
+ NETWORK_DOWN,
+ SUCCESSFUL_EXIT,
+ FAILED_EXIT,
+ CRASHED,
+ DID_NOT_CRASH,
+ OTHER_JOB_ENABLED,
+ OTHER_JOB_DISABLED,
+ OTHER_JOB_ACTIVE,
+ OTHER_JOB_INACTIVE,
+} semaphore_reason_t;
+
+struct semaphoreitem {
+ SLIST_ENTRY(semaphoreitem) sle;
+ semaphore_reason_t why;
+
+ union {
+ const char what[0];
+ char what_init[0];
+ };
+};
+
+struct semaphoreitem_dict_iter_context {
+ job_t j;
+ semaphore_reason_t why_true;
+ semaphore_reason_t why_false;
+};
+
+static bool semaphoreitem_new(job_t j, semaphore_reason_t why, const char *what);
+static void semaphoreitem_delete(job_t j, struct semaphoreitem *si);
+static void semaphoreitem_setup(launch_data_t obj, const char *key, void *context);
+static void semaphoreitem_setup_dict_iter(launch_data_t obj, const char *key, void *context);
+static void semaphoreitem_runtime_mod_ref(struct semaphoreitem *si, bool add);
+
+struct externalevent {
+ LIST_ENTRY(externalevent) sys_le;
+ LIST_ENTRY(externalevent) job_le;
+ struct eventsystem *sys;
+
+ uint64_t id;
+ job_t job;
+ bool state;
+ bool wanted_state;
+ bool internal;
+ xpc_object_t event;
+
+ char name[0];
+};
+
+struct externalevent_iter_ctx {
+ job_t j;
+ struct eventsystem *sys;
+};
+
+static bool externalevent_new(job_t j, struct eventsystem *sys, const char *evname, xpc_object_t event);
+static void externalevent_delete(struct externalevent *ee);
+static void externalevent_setup(launch_data_t obj, const char *key, void *context);
+static struct externalevent *externalevent_find(const char *sysname, uint64_t id);
+
+struct eventsystem {
+ LIST_ENTRY(eventsystem) global_le;
+ LIST_HEAD(, externalevent) events;
+ uint64_t curid;
+ char name[0];
+};
+
+static struct eventsystem *eventsystem_new(const char *name);
+static void eventsystem_delete(struct eventsystem *sys) __attribute__((unused));
+static void eventsystem_setup(launch_data_t obj, const char *key, void *context);
+static struct eventsystem *eventsystem_find(const char *name);
+static void eventsystem_ping(void);
+
+#define ACTIVE_JOB_HASH_SIZE 32
+#define ACTIVE_JOB_HASH(x) (IS_POWER_OF_TWO(ACTIVE_JOB_HASH_SIZE) ? (x & (ACTIVE_JOB_HASH_SIZE - 1)) : (x % ACTIVE_JOB_HASH_SIZE))
+
+#define MACHSERVICE_HASH_SIZE 37
+
+#define LABEL_HASH_SIZE 53
+struct jobmgr_s {
+ kq_callback kqjobmgr_callback;
+ LIST_ENTRY(jobmgr_s) xpc_le;
+ SLIST_ENTRY(jobmgr_s) sle;
+ SLIST_HEAD(, jobmgr_s) submgrs;
+ LIST_HEAD(, job_s) jobs;
+
+ /* For legacy reasons, we keep all job labels that are imported in the root
+ * job manager's label hash. If a job manager is an XPC domain, then it gets
+ * its own label hash that is separate from the "global" one stored in the
+ * root job manager.
+ */
+ LIST_HEAD(, job_s) label_hash[LABEL_HASH_SIZE];
+ LIST_HEAD(, job_s) active_jobs[ACTIVE_JOB_HASH_SIZE];
+ LIST_HEAD(, machservice) ms_hash[MACHSERVICE_HASH_SIZE];
+ LIST_HEAD(, job_s) global_env_jobs;
+ mach_port_t jm_port;
+ mach_port_t req_port;
+ jobmgr_t parentmgr;
+ int reboot_flags;
+ time_t shutdown_time;
+ unsigned int global_on_demand_cnt;
+ unsigned int normal_active_cnt;
+ unsigned int
+ shutting_down:1,
+ session_initialized:1,
+ killed_stray_jobs:1,
+ monitor_shutdown:1,
+ shutdown_jobs_dirtied:1,
+ shutdown_jobs_cleaned:1,
+ xpc_singleton:1;
+ uint32_t properties;
+ // XPC-specific properties.
+ char owner[MAXCOMLEN];
+ char *shortdesc;
+ mach_port_t req_bsport;
+ mach_port_t req_excport;
+ mach_port_t req_asport;
+ pid_t req_pid;
+ uid_t req_euid;
+ gid_t req_egid;
+ au_asid_t req_asid;
+ vm_offset_t req_ctx;
+ mach_msg_type_number_t req_ctx_sz;
+ mach_port_t req_rport;
+ kern_return_t error;
+ union {
+ const char name[0];
+ char name_init[0];
+ };
+};
+
+// Global XPC domains.
+static jobmgr_t _s_xpc_system_domain;
+static LIST_HEAD(, jobmgr_s) _s_xpc_user_domains;
+static LIST_HEAD(, jobmgr_s) _s_xpc_session_domains;
+
+#define jobmgr_assumes(jm, e) osx_assumes_ctx(jobmgr_log_bug, jm, (e))
+#define jobmgr_assumes_zero(jm, e) osx_assumes_zero_ctx(jobmgr_log_bug, jm, (e))
+#define jobmgr_assumes_zero_p(jm, e) posix_assumes_zero_ctx(jobmgr_log_bug, jm, (e))
+
+static jobmgr_t jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bool sflag, const char *name, bool no_init, mach_port_t asport);
+static jobmgr_t jobmgr_new_xpc_singleton_domain(jobmgr_t jm, name_t name);
+static jobmgr_t jobmgr_find_xpc_per_user_domain(jobmgr_t jm, uid_t uid);
+static jobmgr_t jobmgr_find_xpc_per_session_domain(jobmgr_t jm, au_asid_t asid);
+static job_t jobmgr_import2(jobmgr_t jm, launch_data_t pload);
+static jobmgr_t jobmgr_parent(jobmgr_t jm);
+static jobmgr_t jobmgr_do_garbage_collection(jobmgr_t jm);
+static bool jobmgr_label_test(jobmgr_t jm, const char *str);
+static void jobmgr_reap_bulk(jobmgr_t jm, struct kevent *kev);
+static void jobmgr_log_stray_children(jobmgr_t jm, bool kill_strays);
+static void jobmgr_kill_stray_children(jobmgr_t jm, pid_t *p, size_t np);
+static void jobmgr_remove(jobmgr_t jm);
+static void jobmgr_dispatch_all(jobmgr_t jm, bool newmounthack);
+static job_t jobmgr_init_session(jobmgr_t jm, const char *session_type, bool sflag);
+static job_t jobmgr_find_by_pid_deep(jobmgr_t jm, pid_t p, bool anon_okay);
+static job_t jobmgr_find_by_pid(jobmgr_t jm, pid_t p, bool create_anon);
+static jobmgr_t jobmgr_find_by_name(jobmgr_t jm, const char *where);
+static job_t job_mig_intran2(jobmgr_t jm, mach_port_t mport, pid_t upid);
+static job_t jobmgr_lookup_per_user_context_internal(job_t j, uid_t which_user, mach_port_t *mp);
+static void job_export_all2(jobmgr_t jm, launch_data_t where);
+static void jobmgr_callback(void *obj, struct kevent *kev);
+static void jobmgr_setup_env_from_other_jobs(jobmgr_t jm);
+static void jobmgr_export_env_from_other_jobs(jobmgr_t jm, launch_data_t dict);
+static struct machservice *jobmgr_lookup_service(jobmgr_t jm, const char *name, bool check_parent, pid_t target_pid);
+static void jobmgr_logv(jobmgr_t jm, int pri, int err, const char *msg, va_list ap) __attribute__((format(printf, 4, 0)));
+static void jobmgr_log(jobmgr_t jm, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4)));
+static void jobmgr_log_perf_statistics(jobmgr_t jm);
+// static void jobmgr_log_error(jobmgr_t jm, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4)));
+static bool jobmgr_log_bug(aslmsg asl_message, void *ctx, const char *message);
+
+#define AUTO_PICK_LEGACY_LABEL (const char *)(~0)
+#define AUTO_PICK_ANONYMOUS_LABEL (const char *)(~1)
+#define AUTO_PICK_XPC_LABEL (const char *)(~2)
+
+struct suspended_peruser {
+ LIST_ENTRY(suspended_peruser) sle;
+ job_t j;
+};
+
+struct job_s {
+ // MUST be first element of this structure.
+ kq_callback kqjob_callback;
+ LIST_ENTRY(job_s) sle;
+ LIST_ENTRY(job_s) subjob_sle;
+ LIST_ENTRY(job_s) needing_session_sle;
+ LIST_ENTRY(job_s) jetsam_sle;
+ LIST_ENTRY(job_s) pid_hash_sle;
+ LIST_ENTRY(job_s) label_hash_sle;
+ LIST_ENTRY(job_s) global_env_sle;
+ SLIST_ENTRY(job_s) curious_jobs_sle;
+ LIST_HEAD(, suspended_peruser) suspended_perusers;
+ LIST_HEAD(, waiting_for_exit) exit_watchers;
+ LIST_HEAD(, job_s) subjobs;
+ LIST_HEAD(, externalevent) events;
+ SLIST_HEAD(, socketgroup) sockets;
+ SLIST_HEAD(, calendarinterval) cal_intervals;
+ SLIST_HEAD(, envitem) global_env;
+ SLIST_HEAD(, envitem) env;
+ SLIST_HEAD(, limititem) limits;
+ SLIST_HEAD(, machservice) machservices;
+ SLIST_HEAD(, semaphoreitem) semaphores;
+ SLIST_HEAD(, waiting_for_removal) removal_watchers;
+ job_t alias;
+ struct rusage ru;
+ cpu_type_t *j_binpref;
+ size_t j_binpref_cnt;
+ mach_port_t j_port;
+ mach_port_t exit_status_dest;
+ mach_port_t exit_status_port;
+ mach_port_t spawn_reply_port;
+ uid_t mach_uid;
+ jobmgr_t mgr;
+ size_t argc;
+ char **argv;
+ char *prog;
+ char *rootdir;
+ char *workingdir;
+ char *username;
+ char *groupname;
+ char *stdinpath;
+ char *stdoutpath;
+ char *stderrpath;
+ char *alt_exc_handler;
+ unsigned int nruns;
+ uint64_t trt;
+#if HAVE_SANDBOX
+ char *seatbelt_profile;
+ uint64_t seatbelt_flags;
+#endif
+#if HAVE_QUARANTINE
+ void *quarantine_data;
+ size_t quarantine_data_sz;
+#endif
+ pid_t p;
+ int last_exit_status;
+ int stdin_fd;
+ int fork_fd;
+ int nice;
+ uint32_t pstype;
+ int32_t jetsam_priority;
+ int32_t jetsam_memlimit;
+ int32_t main_thread_priority;
+ uint32_t timeout;
+ uint32_t exit_timeout;
+ uint64_t sent_signal_time;
+ uint64_t start_time;
+ uint32_t min_run_time;
+ uint32_t start_interval;
+ uint32_t peruser_suspend_count;
+ uuid_t instance_id;
+ mode_t mask;
+ pid_t tracing_pid;
+ mach_port_t asport;
+ // Only set for per-user launchd's.
+ au_asid_t asid;
+ uuid_t expected_audit_uuid;
+ bool
+ // man launchd.plist --> Debug
+ debug:1,
+ // man launchd.plist --> KeepAlive == false
+ ondemand:1,
+ // man launchd.plist --> SessionCreate
+ session_create:1,
+ // man launchd.plist --> LowPriorityIO
+ low_pri_io:1,
+ // man launchd.plist --> InitGroups
+ no_init_groups:1,
+ /* A legacy mach_init concept to make bootstrap_create_server/service()
+ * work
+ */
+ priv_port_has_senders:1,
+ // A hack during job importing
+ importing_global_env:1,
+ // A hack during job importing
+ importing_hard_limits:1,
+ // man launchd.plist --> Umask
+ setmask:1,
+ // A process that launchd knows about but doesn't manage.
+ anonymous:1,
+ // A legacy mach_init concept to detect sick jobs
+ checkedin:1,
+ // A job created via bootstrap_create_server()
+ legacy_mach_job:1,
+ // A job created via spawn_via_launchd()
+ legacy_LS_job:1,
+ // A legacy job that wants inetd compatible semantics
+ inetcompat:1,
+ // A twist on inetd compatibility
+ inetcompat_wait:1,
+ /* An event fired and the job should start, but not necessarily right
+ * away.
+ */
+ start_pending:1,
+ // man launchd.plist --> EnableGlobbing
+ globargv:1,
+ // man launchd.plist --> WaitForDebugger
+ wait4debugger:1,
+ // One-shot WaitForDebugger.
+ wait4debugger_oneshot:1,
+ // MachExceptionHandler == true
+ internal_exc_handler:1,
+ // A hack to support an option of spawn_via_launchd()
+ stall_before_exec:1,
+ /* man launchd.plist --> LaunchOnlyOnce.
+ *
+ * Note: <rdar://problem/5465184> Rename this to "HopefullyNeverExits".
+ */
+ only_once:1,
+ /* Make job_ignore() / job_watch() work. If these calls were balanced,
+ * then this wouldn't be necessarily.
+ */
+ currently_ignored:1,
+ /* A job that forced all other jobs to be temporarily launch-on-
+ * demand
+ */
+ forced_peers_to_demand_mode:1,
+ // man launchd.plist --> Nice
+ setnice:1,
+ /* A job was asked to be unloaded/removed while running, we'll remove it
+ * after it exits.
+ */
+ removal_pending:1,
+ // job_kill() was called.
+ sent_sigkill:1,
+ // Enter the kernel debugger before killing a job.
+ debug_before_kill:1,
+ // A hack that launchd+launchctl use during jobmgr_t creation.
+ weird_bootstrap:1,
+ // man launchd.plist --> StartOnMount
+ start_on_mount:1,
+ // This job is a per-user launchd managed by the PID 1 launchd.
+ per_user:1,
+ // A job thoroughly confused launchd. We need to unload it ASAP.
+ unload_at_mig_return:1,
+ // man launchd.plist --> AbandonProcessGroup
+ abandon_pg:1,
+ /* During shutdown, do not send SIGTERM to stray processes in the
+ * process group of this job.
+ */
+ ignore_pg_at_shutdown:1,
+ /* Don't let this job create new 'job_t' objects in launchd. Has been
+ * seriously overloaded for the purposes of sandboxing.
+ */
+ deny_job_creation:1,
+ // man launchd.plist --> EnableTransactions
+ enable_transactions:1,
+ // The job was sent SIGKILL because it was clean.
+ clean_kill:1,
+ /* The job has a tracing PID (probably a debugger) and exited before the
+ * tracer did. So we must defer our reap attempt until after the tracer
+ * has exited. This works around our busted ptrace(3) implementation.
+ */
+ reap_after_trace:1,
+ // The job has an OtherJobEnabled KeepAlive criterion.
+ nosy:1,
+ // The job exited due to a crash.
+ crashed:1,
+ // We've received NOTE_EXIT for the job and reaped it.
+ reaped:1,
+ // job_stop() was called.
+ stopped:1,
+ // The job is considered "frontmost" by Jetsam.
+ jetsam_frontmost:1,
+ /* The job is not frontmost, but it is considered "active" (i.e.
+ * backgrounded) by Jetsam.
+ */
+ jetsam_active:1,
+ /* The job is to be kept alive continuously, but it must first get an
+ * initial kick off.
+ */
+ needs_kickoff:1,
+ // The job is a bootstrapper.
+ is_bootstrapper:1,
+ // The job owns the console.
+ has_console:1,
+ /* The job runs as a non-root user on embedded but has select privileges
+ * of the root user. This is SpringBoard.
+ */
+ embedded_god:1,
+ // We got NOTE_EXEC for the job.
+ did_exec:1,
+ // The job is an XPC service, and XPC proxy successfully exec(3)ed.
+ xpcproxy_did_exec:1,
+ // The (anonymous) job called vprocmgr_switch_to_session().
+ holds_ref:1,
+ // The job has Jetsam limits in place.
+ jetsam_properties:1,
+ /* This job was created as the result of a look up of a service provided
+ * by a MultipleInstance job.
+ */
+ dedicated_instance:1,
+ // The job supports creating additional instances of itself.
+ multiple_instances:1,
+ /* The sub-job was already removed from the parent's list of
+ * sub-jobs.
+ */
+ former_subjob:1,
+ /* The job is responsible for monitoring external events for this
+ * launchd.
+ */
+ event_monitor:1,
+ // The event monitor job has retrieved the initial list of events.
+ event_monitor_ready2signal:1,
+ // A lame hack.
+ removing:1,
+ // Disable ASLR when launching this job.
+ disable_aslr:1,
+ // The job is an XPC Service.
+ xpc_service:1,
+ // The job is the Performance team's shutdown monitor.
+ shutdown_monitor:1,
+ // We should open a transaction for the job when shutdown begins.
+ dirty_at_shutdown:1,
+ /* The job was sent SIGKILL but did not exit in a timely fashion,
+ * indicating a kernel bug.
+ */
+ workaround9359725:1,
+ // The job is the XPC domain bootstrapper.
+ xpc_bootstrapper:1,
+ // The job is an app (on either iOS or OS X) and has different resource
+ // limitations.
+ app:1,
+ // The job failed to exec(3) for reasons that may be transient, so we're
+ // waiting for UserEventAgent to tell us when it's okay to try spawning
+ // again (i.e. when the executable path appears, when the UID appears,
+ // etc.).
+ waiting4ok:1;
+
+ const char label[0];
+};
+
+static size_t hash_label(const char *label) __attribute__((pure));
+static size_t hash_ms(const char *msstr) __attribute__((pure));
+static SLIST_HEAD(, job_s) s_curious_jobs;
+
+#define job_assumes(j, e) osx_assumes_ctx(job_log_bug, j, (e))
+#define job_assumes_zero(j, e) osx_assumes_zero_ctx(job_log_bug, j, (e))
+#define job_assumes_zero_p(j, e) posix_assumes_zero_ctx(job_log_bug, j, (e))
+
+static void job_import_keys(launch_data_t obj, const char *key, void *context);
+static void job_import_bool(job_t j, const char *key, bool value);
+static void job_import_string(job_t j, const char *key, const char *value);
+static void job_import_integer(job_t j, const char *key, long long value);
+static void job_import_dictionary(job_t j, const char *key, launch_data_t value);
+static void job_import_array(job_t j, const char *key, launch_data_t value);
+static void job_import_opaque(job_t j, const char *key, launch_data_t value);
+static bool job_set_global_on_demand(job_t j, bool val);
+static const char *job_active(job_t j);
+static void job_watch(job_t j);
+static void job_ignore(job_t j);
+static void job_cleanup_after_tracer(job_t j);
+static void job_reap(job_t j);
+static bool job_useless(job_t j);
+static bool job_keepalive(job_t j);
+static void job_dispatch_curious_jobs(job_t j);
+static void job_start(job_t j);
+static void job_start_child(job_t j) __attribute__((noreturn));
+static void job_setup_attributes(job_t j);
+static bool job_setup_machport(job_t j);
+static kern_return_t job_setup_exit_port(job_t j);
+static void job_setup_fd(job_t j, int target_fd, const char *path, int flags);
+static void job_postfork_become_user(job_t j);
+static void job_postfork_test_user(job_t j);
+static void job_log_pids_with_weird_uids(job_t j);
+static void job_setup_exception_port(job_t j, task_t target_task);
+static void job_callback(void *obj, struct kevent *kev);
+static void job_callback_proc(job_t j, struct kevent *kev);
+static void job_callback_timer(job_t j, void *ident);
+static void job_callback_read(job_t j, int ident);
+static void job_log_stray_pg(job_t j);
+static void job_log_children_without_exec(job_t j);
+static job_t job_new_anonymous(jobmgr_t jm, pid_t anonpid) __attribute__((malloc, nonnull, warn_unused_result));
+static job_t job_new(jobmgr_t jm, const char *label, const char *prog, const char *const *argv) __attribute__((malloc, nonnull(1,2), warn_unused_result));
+static job_t job_new_alias(jobmgr_t jm, job_t src);
+static job_t job_new_via_mach_init(job_t j, const char *cmd, uid_t uid, bool ond) __attribute__((malloc, nonnull, warn_unused_result));
+static job_t job_new_subjob(job_t j, uuid_t identifier);
+static void job_kill(job_t j);
+static void job_uncork_fork(job_t j);
+static void job_logv(job_t j, int pri, int err, const char *msg, va_list ap) __attribute__((format(printf, 4, 0)));
+static void job_log_error(job_t j, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4)));
+static bool job_log_bug(aslmsg asl_message, void *ctx, const char *message);
+static void job_log_perf_statistics(job_t j);
+static void job_set_exception_port(job_t j, mach_port_t port);
+static kern_return_t job_mig_spawn_internal(job_t j, vm_offset_t indata, mach_msg_type_number_t indataCnt, mach_port_t asport, job_t *outj);
+static void job_open_shutdown_transaction(job_t ji);
+static void job_close_shutdown_transaction(job_t ji);
+static launch_data_t job_do_legacy_ipc_request(job_t j, launch_data_t request, mach_port_t asport);
+static void job_setup_per_user_directory(job_t j, uid_t uid, const char *path);
+static void job_setup_per_user_directories(job_t j, uid_t uid, const char *label);
+
+static const struct {
+ const char *key;
+ int val;
+} launchd_keys2limits[] = {
+ { LAUNCH_JOBKEY_RESOURCELIMIT_CORE, RLIMIT_CORE },
+ { LAUNCH_JOBKEY_RESOURCELIMIT_CPU, RLIMIT_CPU },
+ { LAUNCH_JOBKEY_RESOURCELIMIT_DATA, RLIMIT_DATA },
+ { LAUNCH_JOBKEY_RESOURCELIMIT_FSIZE, RLIMIT_FSIZE },
+ { LAUNCH_JOBKEY_RESOURCELIMIT_MEMLOCK, RLIMIT_MEMLOCK },
+ { LAUNCH_JOBKEY_RESOURCELIMIT_NOFILE, RLIMIT_NOFILE },
+ { LAUNCH_JOBKEY_RESOURCELIMIT_NPROC, RLIMIT_NPROC },
+ { LAUNCH_JOBKEY_RESOURCELIMIT_RSS, RLIMIT_RSS },
+ { LAUNCH_JOBKEY_RESOURCELIMIT_STACK, RLIMIT_STACK },
+};
+
+static time_t cronemu(int mon, int mday, int hour, int min);
+static time_t cronemu_wday(int wday, int hour, int min);
+static bool cronemu_mon(struct tm *wtm, int mon, int mday, int hour, int min);
+static bool cronemu_mday(struct tm *wtm, int mday, int hour, int min);
+static bool cronemu_hour(struct tm *wtm, int hour, int min);
+static bool cronemu_min(struct tm *wtm, int min);
+
+// miscellaneous file local functions
+static size_t get_kern_max_proc(void);
+static char **mach_cmd2argv(const char *string);
+static size_t our_strhash(const char *s) __attribute__((pure));
+
+void eliminate_double_reboot(void);
+
+#pragma mark XPC Domain Forward Declarations
+static job_t _xpc_domain_import_service(jobmgr_t jm, launch_data_t pload);
+static int _xpc_domain_import_services(job_t j, launch_data_t services);
+
+#pragma mark XPC Event Forward Declarations
+static int xpc_event_find_channel(job_t j, const char *stream, struct machservice **ms);
+static int xpc_event_get_event_name(job_t j, xpc_object_t request, xpc_object_t *reply);
+static int xpc_event_set_event(job_t j, xpc_object_t request, xpc_object_t *reply);
+static int xpc_event_copy_event(job_t j, xpc_object_t request, xpc_object_t *reply);
+static int xpc_event_channel_check_in(job_t j, xpc_object_t request, xpc_object_t *reply);
+static int xpc_event_channel_look_up(job_t j, xpc_object_t request, xpc_object_t *reply);
+static int xpc_event_provider_check_in(job_t j, xpc_object_t request, xpc_object_t *reply);
+static int xpc_event_provider_set_state(job_t j, xpc_object_t request, xpc_object_t *reply);
+
+// file local globals
+static job_t _launchd_embedded_god = NULL;
+static size_t total_children;
+static size_t total_anon_children;
+static mach_port_t the_exception_server;
+static job_t workaround_5477111;
+static LIST_HEAD(, job_s) s_needing_sessions;
+static LIST_HEAD(, eventsystem) _s_event_systems;
+static struct eventsystem *_launchd_support_system;
+static job_t _launchd_event_monitor;
+static job_t _launchd_xpc_bootstrapper;
+static job_t _launchd_shutdown_monitor;
+
+mach_port_t launchd_audit_port = MACH_PORT_NULL;
+#if !TARGET_OS_EMBEDDED
+au_asid_t launchd_audit_session = AU_DEFAUDITSID;
+#else
+pid_t launchd_audit_session = 0;
+#endif
+
+static int s_no_hang_fd = -1;
+
+// process wide globals
+mach_port_t inherited_bootstrap_port;
+jobmgr_t root_jobmgr;
+bool launchd_shutdown_debugging = false;
+bool launchd_verbose_boot = false;
+bool launchd_embedded_handofgod = false;
+bool launchd_runtime_busy_time = false;
+
+void
+job_ignore(job_t j)
+{
+ struct socketgroup *sg;
+ struct machservice *ms;
+
+ if (j->currently_ignored) {
+ return;
+ }
+
+ job_log(j, LOG_DEBUG, "Ignoring...");
+
+ j->currently_ignored = true;
+
+ SLIST_FOREACH(sg, &j->sockets, sle) {
+ socketgroup_ignore(j, sg);
+ }
+
+ SLIST_FOREACH(ms, &j->machservices, sle) {
+ machservice_ignore(j, ms);
+ }
+}
+
+void
+job_watch(job_t j)
+{
+ struct socketgroup *sg;
+ struct machservice *ms;
+
+ if (!j->currently_ignored) {
+ return;
+ }
+
+ job_log(j, LOG_DEBUG, "Watching...");
+
+ j->currently_ignored = false;
+
+ SLIST_FOREACH(sg, &j->sockets, sle) {
+ socketgroup_watch(j, sg);
+ }
+
+ SLIST_FOREACH(ms, &j->machservices, sle) {
+ machservice_watch(j, ms);
+ }
+}
+
+void
+job_stop(job_t j)
+{
+ int sig;
+
+ if (unlikely(!j->p || j->stopped || j->anonymous)) {
+ return;
+ }
+
+#if TARGET_OS_EMBEDDED
+ if (launchd_embedded_handofgod && _launchd_embedded_god) {
+ if (!_launchd_embedded_god->username || !j->username) {
+ errno = EPERM;
+ return;
+ }
+
+ if (strcmp(j->username, _launchd_embedded_god->username) != 0) {
+ errno = EPERM;
+ return;
+ }
+ } else if (launchd_embedded_handofgod) {
+ errno = EINVAL;
+ return;
+ }
+#endif
+
+ j->sent_signal_time = runtime_get_opaque_time();
+
+ job_log(j, LOG_DEBUG | LOG_CONSOLE, "Stopping job...");
+
+ int error = -1;
+ error = proc_terminate(j->p, &sig);
+ if (error) {
+ job_log(j, LOG_ERR | LOG_CONSOLE, "Could not terminate job: %d: %s", error, strerror(error));
+ job_log(j, LOG_NOTICE | LOG_CONSOLE, "Using fallback option to terminate job...");
+ error = kill2(j->p, SIGTERM);
+ if (error) {
+ job_log(j, LOG_ERR, "Could not signal job: %d: %s", error, strerror(error));
+ } else {
+ sig = SIGTERM;
+ }
+ }
+
+ if (!error) {
+ switch (sig) {
+ case SIGKILL:
+ j->sent_sigkill = true;
+ j->clean_kill = true;
+ error = kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, LAUNCHD_SIGKILL_TIMER, j);
+ (void)job_assumes_zero_p(j, error);
+
+ job_log(j, LOG_DEBUG | LOG_CONSOLE, "Sent job SIGKILL.");
+ break;
+ case SIGTERM:
+ if (j->exit_timeout) {
+ error = kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, j->exit_timeout, j);
+ (void)job_assumes_zero_p(j, error);
+ } else {
+ job_log(j, LOG_NOTICE, "This job has an infinite exit timeout");
+ }
+ job_log(j, LOG_DEBUG, "Sent job SIGTERM.");
+ break;
+ default:
+ job_log(j, LOG_ERR | LOG_CONSOLE, "Job was sent unexpected signal: %d: %s", sig, strsignal(sig));
+ break;
+ }
+ }
+
+ j->stopped = true;
+}
+
+launch_data_t
+job_export(job_t j)
+{
+ launch_data_t tmp, tmp2, tmp3, r = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+
+ if (r == NULL) {
+ return NULL;
+ }
+
+ if ((tmp = launch_data_new_string(j->label))) {
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_LABEL);
+ }
+ if ((tmp = launch_data_new_string(j->mgr->name))) {
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE);
+ }
+ if ((tmp = launch_data_new_bool(j->ondemand))) {
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_ONDEMAND);
+ }
+ if ((tmp = launch_data_new_integer(j->last_exit_status))) {
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_LASTEXITSTATUS);
+ }
+ if (j->p && (tmp = launch_data_new_integer(j->p))) {
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_PID);
+ }
+ if ((tmp = launch_data_new_integer(j->timeout))) {
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_TIMEOUT);
+ }
+ if (j->prog && (tmp = launch_data_new_string(j->prog))) {
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_PROGRAM);
+ }
+ if (j->stdinpath && (tmp = launch_data_new_string(j->stdinpath))) {
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_STANDARDINPATH);
+ }
+ if (j->stdoutpath && (tmp = launch_data_new_string(j->stdoutpath))) {
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_STANDARDOUTPATH);
+ }
+ if (j->stderrpath && (tmp = launch_data_new_string(j->stderrpath))) {
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_STANDARDERRORPATH);
+ }
+ if (likely(j->argv) && (tmp = launch_data_alloc(LAUNCH_DATA_ARRAY))) {
+ size_t i;
+
+ for (i = 0; i < j->argc; i++) {
+ if ((tmp2 = launch_data_new_string(j->argv[i]))) {
+ launch_data_array_set_index(tmp, tmp2, i);
+ }
+ }
+
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_PROGRAMARGUMENTS);
+ }
+
+ if (j->enable_transactions && (tmp = launch_data_new_bool(true))) {
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_ENABLETRANSACTIONS);
+ }
+
+ if (j->session_create && (tmp = launch_data_new_bool(true))) {
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_SESSIONCREATE);
+ }
+
+ if (j->inetcompat && (tmp = launch_data_alloc(LAUNCH_DATA_DICTIONARY))) {
+ if ((tmp2 = launch_data_new_bool(j->inetcompat_wait))) {
+ launch_data_dict_insert(tmp, tmp2, LAUNCH_JOBINETDCOMPATIBILITY_WAIT);
+ }
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_INETDCOMPATIBILITY);
+ }
+
+ if (!SLIST_EMPTY(&j->sockets) && (tmp = launch_data_alloc(LAUNCH_DATA_DICTIONARY))) {
+ struct socketgroup *sg;
+ unsigned int i;
+
+ SLIST_FOREACH(sg, &j->sockets, sle) {
+ if ((tmp2 = launch_data_alloc(LAUNCH_DATA_ARRAY))) {
+ for (i = 0; i < sg->fd_cnt; i++) {
+ if ((tmp3 = launch_data_new_fd(sg->fds[i]))) {
+ launch_data_array_set_index(tmp2, tmp3, i);
+ }
+ }
+ launch_data_dict_insert(tmp, tmp2, sg->name);
+ }
+ }
+
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_SOCKETS);
+ }
+
+ if (!SLIST_EMPTY(&j->machservices) && (tmp = launch_data_alloc(LAUNCH_DATA_DICTIONARY))) {
+ struct machservice *ms;
+
+ tmp3 = NULL;
+
+ SLIST_FOREACH(ms, &j->machservices, sle) {
+ if (ms->per_pid) {
+ if (tmp3 == NULL) {
+ tmp3 = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ }
+ if (tmp3) {
+ tmp2 = launch_data_new_machport(MACH_PORT_NULL);
+ launch_data_dict_insert(tmp3, tmp2, ms->name);
+ }
+ } else {
+ tmp2 = launch_data_new_machport(MACH_PORT_NULL);
+ launch_data_dict_insert(tmp, tmp2, ms->name);
+ }
+ }
+
+ launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_MACHSERVICES);
+
+ if (tmp3) {
+ launch_data_dict_insert(r, tmp3, LAUNCH_JOBKEY_PERJOBMACHSERVICES);
+ }
+ }
+
+ return r;
+}
+
+static void
+jobmgr_log_active_jobs(jobmgr_t jm)
+{
+ const char *why_active;
+ jobmgr_t jmi;
+ job_t ji;
+
+ SLIST_FOREACH(jmi, &jm->submgrs, sle) {
+ jobmgr_log_active_jobs(jmi);
+ }
+
+ int level = LOG_DEBUG;
+ if (pid1_magic) {
+ level |= LOG_CONSOLE;
+ }
+
+ LIST_FOREACH(ji, &jm->jobs, sle) {
+ if ((why_active = job_active(ji))) {
+ if (ji->p != 1) {
+ job_log(ji, level, "%s", why_active);
+
+ uint32_t flags = 0;
+ (void)proc_get_dirty(ji->p, &flags);
+ if (!(flags & PROC_DIRTY_TRACKED)) {
+ continue;
+ }
+
+ char *dirty = "clean";
+ if (flags & PROC_DIRTY_IS_DIRTY) {
+ dirty = "dirty";
+ }
+
+ char *idle_exit = "idle-exit unsupported";
+ if (flags & PROC_DIRTY_ALLOWS_IDLE_EXIT) {
+ idle_exit = "idle-exit supported";
+ }
+
+ job_log(ji, level, "Killability: %s/%s", dirty, idle_exit);
+ }
+ }
+ }
+}
+
+static void
+jobmgr_still_alive_with_check(jobmgr_t jm)
+{
+ int level = LOG_DEBUG;
+ if (pid1_magic) {
+ level |= LOG_CONSOLE;
+ }
+
+ jobmgr_log(jm, level, "Still alive with %lu/%lu (normal/anonymous) children.", total_children, total_anon_children);
+ jobmgr_log_active_jobs(jm);
+ launchd_log_push();
+}
+
+jobmgr_t
+jobmgr_shutdown(jobmgr_t jm)
+{
+ jobmgr_t jmi, jmn;
+ jobmgr_log(jm, LOG_DEBUG, "Beginning job manager shutdown with flags: %s", reboot_flags_to_C_names(jm->reboot_flags));
+
+ jm->shutdown_time = runtime_get_wall_time() / USEC_PER_SEC;
+
+ struct tm curtime;
+ (void)localtime_r(&jm->shutdown_time, &curtime);
+
+ char date[26];
+ (void)asctime_r(&curtime, date);
+ // Trim the new line that asctime_r(3) puts there for some reason.
+ date[24] = 0;
+
+ if (jm == root_jobmgr && pid1_magic) {
+ jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Userspace shutdown begun at: %s", date);
+ } else {
+ jobmgr_log(jm, LOG_DEBUG, "Job manager shutdown begun at: %s", date);
+ }
+
+ jm->shutting_down = true;
+
+ SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) {
+ jobmgr_shutdown(jmi);
+ }
+
+ if (!jm->parentmgr) {
+ if (pid1_magic) {
+ // Spawn the shutdown monitor.
+ if (_launchd_shutdown_monitor && !_launchd_shutdown_monitor->p) {
+ job_log(_launchd_shutdown_monitor, LOG_NOTICE | LOG_CONSOLE, "Starting shutdown monitor.");
+ job_dispatch(_launchd_shutdown_monitor, true);
+ }
+ }
+
+ (void)jobmgr_assumes_zero_p(jm, kevent_mod((uintptr_t)jm, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, 5, jm));
+ }
+
+ return jobmgr_do_garbage_collection(jm);
+}
+
+void
+jobmgr_remove(jobmgr_t jm)
+{
+ jobmgr_t jmi;
+ job_t ji;
+
+ jobmgr_log(jm, LOG_DEBUG, "Removing job manager.");
+ if (!SLIST_EMPTY(&jm->submgrs)) {
+ size_t cnt = 0;
+ while ((jmi = SLIST_FIRST(&jm->submgrs))) {
+ jobmgr_remove(jmi);
+ cnt++;
+ }
+
+ (void)jobmgr_assumes_zero(jm, cnt);
+ }
+
+ while ((ji = LIST_FIRST(&jm->jobs))) {
+ if (!ji->anonymous && ji->p != 0) {
+ job_log(ji, LOG_ERR, "Job is still active at job manager teardown.");
+ ji->p = 0;
+ }
+ job_remove(ji);
+ }
+
+ if (jm->req_port) {
+ (void)jobmgr_assumes_zero(jm, launchd_mport_deallocate(jm->req_port));
+ }
+ if (jm->jm_port) {
+ (void)jobmgr_assumes_zero(jm, launchd_mport_close_recv(jm->jm_port));
+ }
+
+ if (jm->req_bsport) {
+ (void)jobmgr_assumes_zero(jm, launchd_mport_deallocate(jm->req_bsport));
+ }
+ if (jm->req_excport) {
+ (void)jobmgr_assumes_zero(jm, launchd_mport_deallocate(jm->req_excport));
+ }
+ if (MACH_PORT_VALID(jm->req_asport)) {
+ (void)jobmgr_assumes_zero(jm, launchd_mport_deallocate(jm->req_asport));
+ }
+ if (jm->req_rport) {
+ kern_return_t kr = xpc_call_wakeup(jm->req_rport, jm->error);
+ if (!(kr == KERN_SUCCESS || kr == MACH_SEND_INVALID_DEST)) {
+ /* If the originator went away, the reply port will be a dead name,
+ * and we expect this to fail.
+ */
+ (void)jobmgr_assumes_zero(jm, kr);
+ }
+ }
+ if (jm->req_ctx) {
+ (void)jobmgr_assumes_zero(jm, vm_deallocate(mach_task_self(), jm->req_ctx, jm->req_ctx_sz));
+ }
+
+ time_t ts = runtime_get_wall_time() / USEC_PER_SEC;
+ struct tm curtime;
+ (void)localtime_r(&ts, &curtime);
+
+ char date[26];
+ (void)asctime_r(&curtime, date);
+ date[24] = 0;
+
+ time_t delta = ts - jm->shutdown_time;
+ if (jm == root_jobmgr && pid1_magic) {
+ jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Userspace shutdown finished at: %s", date);
+ jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Userspace shutdown took approximately %ld second%s.", delta, (delta != 1) ? "s" : "");
+ } else {
+ jobmgr_log(jm, LOG_DEBUG, "Job manager shutdown finished at: %s", date);
+ jobmgr_log(jm, LOG_DEBUG, "Job manager shutdown took approximately %ld second%s.", delta, (delta != 1) ? "s" : "");
+ }
+
+ if (jm->parentmgr) {
+ runtime_del_weak_ref();
+ SLIST_REMOVE(&jm->parentmgr->submgrs, jm, jobmgr_s, sle);
+ } else if (pid1_magic) {
+ eliminate_double_reboot();
+ launchd_log_vm_stats();
+ jobmgr_log_stray_children(jm, true);
+ jobmgr_log(root_jobmgr, LOG_NOTICE | LOG_CONSOLE, "About to call: reboot(%s).", reboot_flags_to_C_names(jm->reboot_flags));
+ launchd_closelog();
+ (void)jobmgr_assumes_zero_p(jm, reboot(jm->reboot_flags));
+ } else {
+ jobmgr_log(jm, LOG_DEBUG, "About to exit");
+ launchd_closelog();
+ exit(EXIT_SUCCESS);
+ }
+
+ free(jm);
+}
+
+void
+job_remove(job_t j)
+{
+ struct waiting_for_removal *w4r;
+ struct calendarinterval *ci;
+ struct semaphoreitem *si;
+ struct socketgroup *sg;
+ struct machservice *ms;
+ struct limititem *li;
+ struct envitem *ei;
+
+ if (j->alias) {
+ /* HACK: Egregious code duplication. But as with machservice_delete(),
+ * job aliases can't (and shouldn't) have any complex behaviors
+ * associated with them.
+ */
+ while ((ms = SLIST_FIRST(&j->machservices))) {
+ machservice_delete(j, ms, false);
+ }
+
+ LIST_REMOVE(j, sle);
+ LIST_REMOVE(j, label_hash_sle);
+ free(j);
+ return;
+ }
+
+#if TARGET_OS_EMBEDDED
+ if (launchd_embedded_handofgod && _launchd_embedded_god) {
+ if (!(_launchd_embedded_god->username && j->username)) {
+ errno = EPERM;
+ return;
+ }
+
+ if (strcmp(j->username, _launchd_embedded_god->username) != 0) {
+ errno = EPERM;
+ return;
+ }
+ } else if (launchd_embedded_handofgod) {
+ errno = EINVAL;
+ return;
+ }
+#endif
+
+ /* Do this BEFORE we check and see whether the job is still active. If we're
+ * a sub-job, we're being removed due to the parent job removing us.
+ * Therefore, the parent job will free itself after this call completes. So
+ * if we defer removing ourselves from the parent's list, we'll crash when
+ * we finally get around to it.
+ */
+ if (j->dedicated_instance && !j->former_subjob) {
+ LIST_REMOVE(j, subjob_sle);
+ j->former_subjob = true;
+ }
+
+ if (unlikely(j->p)) {
+ if (j->anonymous) {
+ job_reap(j);
+ } else {
+ job_log(j, LOG_DEBUG, "Removal pended until the job exits");
+
+ if (!j->removal_pending) {
+ j->removal_pending = true;
+ job_stop(j);
+ }
+
+ return;
+ }
+ }
+
+ if (!j->removing) {
+ j->removing = true;
+ job_dispatch_curious_jobs(j);
+ }
+
+ ipc_close_all_with_job(j);
+
+ if (j->forced_peers_to_demand_mode) {
+ job_set_global_on_demand(j, false);
+ }
+
+ if (job_assumes_zero(j, j->fork_fd)) {
+ (void)posix_assumes_zero(runtime_close(j->fork_fd));
+ }
+
+ if (j->stdin_fd) {
+ (void)posix_assumes_zero(runtime_close(j->stdin_fd));
+ }
+
+ if (j->j_port) {
+ (void)job_assumes_zero(j, launchd_mport_close_recv(j->j_port));
+ }
+
+ while ((sg = SLIST_FIRST(&j->sockets))) {
+ socketgroup_delete(j, sg);
+ }
+ while ((ci = SLIST_FIRST(&j->cal_intervals))) {
+ calendarinterval_delete(j, ci);
+ }
+ while ((ei = SLIST_FIRST(&j->env))) {
+ envitem_delete(j, ei, false);
+ }
+ while ((ei = SLIST_FIRST(&j->global_env))) {
+ envitem_delete(j, ei, true);
+ }
+ while ((li = SLIST_FIRST(&j->limits))) {
+ limititem_delete(j, li);
+ }
+ while ((ms = SLIST_FIRST(&j->machservices))) {
+ machservice_delete(j, ms, false);
+ }
+ while ((si = SLIST_FIRST(&j->semaphores))) {
+ semaphoreitem_delete(j, si);
+ }
+ while ((w4r = SLIST_FIRST(&j->removal_watchers))) {
+ waiting4removal_delete(j, w4r);
+ }
+
+ struct externalevent *eei = NULL;
+ while ((eei = LIST_FIRST(&j->events))) {
+ externalevent_delete(eei);
+ }
+
+ if (j->event_monitor) {
+ _launchd_event_monitor = NULL;
+ }
+ if (j->xpc_bootstrapper) {
+ _launchd_xpc_bootstrapper = NULL;
+ }
+
+ if (j->prog) {
+ free(j->prog);
+ }
+ if (j->argv) {
+ free(j->argv);
+ }
+ if (j->rootdir) {
+ free(j->rootdir);
+ }
+ if (j->workingdir) {
+ free(j->workingdir);
+ }
+ if (j->username) {
+ free(j->username);
+ }
+ if (j->groupname) {
+ free(j->groupname);
+ }
+ if (j->stdinpath) {
+ free(j->stdinpath);
+ }
+ if (j->stdoutpath) {
+ free(j->stdoutpath);
+ }
+ if (j->stderrpath) {
+ free(j->stderrpath);
+ }
+ if (j->alt_exc_handler) {
+ free(j->alt_exc_handler);
+ }
+#if HAVE_SANDBOX
+ if (j->seatbelt_profile) {
+ free(j->seatbelt_profile);
+ }
+#endif
+#if HAVE_QUARANTINE
+ if (j->quarantine_data) {
+ free(j->quarantine_data);
+ }
+#endif
+ if (j->j_binpref) {
+ free(j->j_binpref);
+ }
+ if (j->start_interval) {
+ runtime_del_weak_ref();
+ (void)job_assumes_zero_p(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_DELETE, 0, 0, NULL));
+ }
+ if (j->exit_timeout) {
+ /* If this fails, it just means the timer's already fired, so no need to
+ * wrap it in an assumes() macro.
+ */
+ (void)kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
+ }
+ if (j->asport != MACH_PORT_NULL) {
+ (void)job_assumes_zero(j, launchd_mport_deallocate(j->asport));
+ }
+ if (!uuid_is_null(j->expected_audit_uuid)) {
+ LIST_REMOVE(j, needing_session_sle);
+ }
+ if (j->embedded_god) {
+ _launchd_embedded_god = NULL;
+ }
+ if (j->shutdown_monitor) {
+ _launchd_shutdown_monitor = NULL;
+ }
+
+ (void)kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
+
+ LIST_REMOVE(j, sle);
+ LIST_REMOVE(j, label_hash_sle);
+
+ job_t ji = NULL;
+ job_t jit = NULL;
+ LIST_FOREACH_SAFE(ji, &j->subjobs, subjob_sle, jit) {
+ job_remove(ji);
+ }
+
+ job_log(j, LOG_DEBUG, "Removed");
+
+ j->kqjob_callback = (kq_callback)0x8badf00d;
+ free(j);
+}
+
+void
+socketgroup_setup(launch_data_t obj, const char *key, void *context)
+{
+ launch_data_t tmp_oai;
+ job_t j = context;
+ size_t i, fd_cnt = 1;
+ int *fds;
+
+ if (launch_data_get_type(obj) == LAUNCH_DATA_ARRAY) {
+ fd_cnt = launch_data_array_get_count(obj);
+ }
+
+ fds = alloca(fd_cnt * sizeof(int));
+
+ for (i = 0; i < fd_cnt; i++) {
+ if (launch_data_get_type(obj) == LAUNCH_DATA_ARRAY) {
+ tmp_oai = launch_data_array_get_index(obj, i);
+ } else {
+ tmp_oai = obj;
+ }
+
+ fds[i] = launch_data_get_fd(tmp_oai);
+ }
+
+ socketgroup_new(j, key, fds, fd_cnt);
+
+ ipc_revoke_fds(obj);
+}
+
+bool
+job_set_global_on_demand(job_t j, bool val)
+{
+ if (j->forced_peers_to_demand_mode && val) {
+ return false;
+ } else if (!j->forced_peers_to_demand_mode && !val) {
+ return false;
+ }
+
+ if ((j->forced_peers_to_demand_mode = val)) {
+ j->mgr->global_on_demand_cnt++;
+ } else {
+ j->mgr->global_on_demand_cnt--;
+ }
+
+ if (j->mgr->global_on_demand_cnt == 0) {
+ jobmgr_dispatch_all(j->mgr, false);
+ }
+
+ return true;
+}
+
+bool
+job_setup_machport(job_t j)
+{
+ if (job_assumes_zero(j, launchd_mport_create_recv(&j->j_port)) != KERN_SUCCESS) {
+ goto out_bad;
+ }
+
+ if (job_assumes_zero(j, runtime_add_mport(j->j_port, job_server)) != KERN_SUCCESS) {
+ goto out_bad2;
+ }
+
+ if (job_assumes_zero(j, launchd_mport_notify_req(j->j_port, MACH_NOTIFY_NO_SENDERS)) != KERN_SUCCESS) {
+ (void)job_assumes_zero(j, launchd_mport_close_recv(j->j_port));
+ goto out_bad;
+ }
+
+ return true;
+out_bad2:
+ (void)job_assumes_zero(j, launchd_mport_close_recv(j->j_port));
+out_bad:
+ return false;
+}
+
+kern_return_t
+job_setup_exit_port(job_t j)
+{
+ kern_return_t kr = launchd_mport_create_recv(&j->exit_status_port);
+ if (job_assumes_zero(j, kr) != KERN_SUCCESS) {
+ return MACH_PORT_NULL;
+ }
+
+ struct mach_port_limits limits = {
+ .mpl_qlimit = 1,
+ };
+ kr = mach_port_set_attributes(mach_task_self(), j->exit_status_port, MACH_PORT_LIMITS_INFO, (mach_port_info_t)&limits, sizeof(limits));
+ (void)job_assumes_zero(j, kr);
+
+ kr = launchd_mport_make_send_once(j->exit_status_port, &j->exit_status_dest);
+ if (job_assumes_zero(j, kr) != KERN_SUCCESS) {
+ (void)job_assumes_zero(j, launchd_mport_close_recv(j->exit_status_port));
+ j->exit_status_port = MACH_PORT_NULL;
+ }
+
+ return kr;
+}
+
+job_t
+job_new_via_mach_init(job_t j, const char *cmd, uid_t uid, bool ond)
+{
+ const char **argv = (const char **)mach_cmd2argv(cmd);
+ job_t jr = NULL;
+
+ if (!argv) {
+ goto out_bad;
+ }
+
+ jr = job_new(j->mgr, AUTO_PICK_LEGACY_LABEL, NULL, argv);
+ free(argv);
+
+ // Job creation can be denied during shutdown.
+ if (unlikely(jr == NULL)) {
+ goto out_bad;
+ }
+
+ jr->mach_uid = uid;
+ jr->ondemand = ond;
+ jr->legacy_mach_job = true;
+ jr->abandon_pg = true;
+ jr->priv_port_has_senders = true; // the IPC that called us will make-send on this port
+
+ if (!job_setup_machport(jr)) {
+ goto out_bad;
+ }
+
+ job_log(jr, LOG_INFO, "Legacy%s server created", ond ? " on-demand" : "");
+
+ return jr;
+
+out_bad:
+ if (jr) {
+ job_remove(jr);
+ }
+ return NULL;
+}
+
+job_t
+job_new_anonymous(jobmgr_t jm, pid_t anonpid)
+{
+ struct proc_bsdshortinfo proc;
+ bool shutdown_state;
+ job_t jp = NULL, jr = NULL;
+ uid_t kp_euid, kp_uid, kp_svuid;
+ gid_t kp_egid, kp_gid, kp_svgid;
+
+ if (anonpid == 0) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (anonpid >= 100000) {
+ /* The kernel current defines PID_MAX to be 99999, but that define isn't
+ * exported.
+ */
+ launchd_syslog(LOG_WARNING, "Did PID_MAX change? Got request from PID: %d", anonpid);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* libproc returns the number of bytes written into the buffer upon success,
+ * zero on failure. I'd much rather it return -1 on failure, like sysctl(3).
+ */
+ if (proc_pidinfo(anonpid, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
+ if (errno != ESRCH) {
+ (void)jobmgr_assumes_zero(jm, errno);
+ }
+ return NULL;
+ }
+
+ if (proc.pbsi_comm[0] == '\0') {
+ launchd_syslog(LOG_WARNING, "Blank command for PID: %d", anonpid);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (unlikely(proc.pbsi_status == SZOMB)) {
+ jobmgr_log(jm, LOG_DEBUG, "Tried to create an anonymous job for zombie PID %u: %s", anonpid, proc.pbsi_comm);
+ }
+
+ if (unlikely(proc.pbsi_flags & P_SUGID)) {
+ jobmgr_log(jm, LOG_DEBUG, "Inconsistency: P_SUGID is set on PID %u: %s", anonpid, proc.pbsi_comm);
+ }
+
+ kp_euid = proc.pbsi_uid;
+ kp_uid = proc.pbsi_ruid;
+ kp_svuid = proc.pbsi_svuid;
+ kp_egid = proc.pbsi_gid;
+ kp_gid = proc.pbsi_rgid;
+ kp_svgid = proc.pbsi_svgid;
+
+ if (unlikely(kp_euid != kp_uid || kp_euid != kp_svuid || kp_uid != kp_svuid || kp_egid != kp_gid || kp_egid != kp_svgid || kp_gid != kp_svgid)) {
+ jobmgr_log(jm, LOG_DEBUG, "Inconsistency: Mixed credentials (e/r/s UID %u/%u/%u GID %u/%u/%u) detected on PID %u: %s",
+ kp_euid, kp_uid, kp_svuid, kp_egid, kp_gid, kp_svgid, anonpid, proc.pbsi_comm);
+ }
+
+ /* "Fix" for when the kernel turns the process tree into a weird, cyclic
+ * graph.
+ *
+ * See <rdar://problem/7264615> for the symptom and <rdar://problem/5020256>
+ * as to why this can happen.
+ */
+ if ((pid_t)proc.pbsi_ppid == anonpid) {
+ jobmgr_log(jm, LOG_WARNING, "Process has become its own parent through ptrace(3). Ignoring: %s", proc.pbsi_comm);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* HACK: Normally, job_new() returns an error during shutdown, but anonymous
+ * jobs can pop up during shutdown and need to talk to us.
+ */
+ if (unlikely(shutdown_state = jm->shutting_down)) {
+ jm->shutting_down = false;
+ }
+
+ // We only set requestor_pid for XPC domains.
+ const char *whichlabel = (jm->req_pid == anonpid) ? AUTO_PICK_XPC_LABEL : AUTO_PICK_ANONYMOUS_LABEL;
+ if ((jr = job_new(jm, whichlabel, proc.pbsi_comm, NULL))) {
+ u_int proc_fflags = NOTE_EXEC|NOTE_FORK|NOTE_EXIT;
+
+ total_anon_children++;
+ jr->anonymous = true;
+ jr->p = anonpid;
+
+ // Anonymous process reaping is messy.
+ LIST_INSERT_HEAD(&jm->active_jobs[ACTIVE_JOB_HASH(jr->p)], jr, pid_hash_sle);
+
+ if (unlikely(kevent_mod(jr->p, EVFILT_PROC, EV_ADD, proc_fflags, 0, root_jobmgr) == -1)) {
+ if (errno != ESRCH) {
+ (void)job_assumes_zero(jr, errno);
+ }
+
+ // Zombies interact weirdly with kevent(3).
+ job_log(jr, LOG_ERR, "Failed to add kevent for PID %u. Will unload at MIG return", jr->p);
+ jr->unload_at_mig_return = true;
+ }
+
+ if (unlikely(shutdown_state)) {
+ job_log(jr, LOG_APPLEONLY, "This process showed up to the party while all the guests were leaving. Odds are that it will have a miserable time.");
+ }
+
+ job_log(jr, LOG_DEBUG, "Created PID %u anonymously by PPID %u%s%s", anonpid, proc.pbsi_ppid, jp ? ": " : "", jp ? jp->label : "");
+ } else {
+ (void)osx_assumes_zero(errno);
+ }
+
+ // Undo our hack from above.
+ if (unlikely(shutdown_state)) {
+ jm->shutting_down = true;
+ }
+
+ /* This is down here to prevent infinite recursion due to a process
+ * attaching to its parent through ptrace(3) -- causing a cycle in the
+ * process tree and thereby not making it a tree anymore. We need to make
+ * sure that the anonymous job has been added to the process list so that
+ * we'll find the tracing parent PID of the parent process, which is the
+ * child, when we go looking for it in jobmgr_find_by_pid().
+ *
+ * <rdar://problem/7264615>
+ */
+ switch (proc.pbsi_ppid) {
+ case 0:
+ // The kernel.
+ break;
+ case 1:
+ if (!pid1_magic) {
+ break;
+ }
+ // Fall through.
+ default:
+ jp = jobmgr_find_by_pid(jm, proc.pbsi_ppid, true);
+ if (jobmgr_assumes(jm, jp != NULL)) {
+ if (jp && !jp->anonymous && unlikely(!(proc.pbsi_flags & P_EXEC))) {
+ job_log(jp, LOG_DEBUG, "Called *fork(). Please switch to posix_spawn*(), pthreads or launchd. Child PID %u", proc.pbsi_pid);
+ }
+ }
+ break;
+ }
+
+ return jr;
+}
+
+job_t
+job_new_subjob(job_t j, uuid_t identifier)
+{
+ char label[0];
+ uuid_string_t idstr;
+ uuid_unparse(identifier, idstr);
+ size_t label_sz = snprintf(label, 0, "%s.%s", j->label, idstr);
+
+ job_t nj = (struct job_s *)calloc(1, sizeof(struct job_s) + label_sz + 1);
+ if (nj != NULL) {
+ nj->kqjob_callback = job_callback;
+ nj->mgr = j->mgr;
+ nj->min_run_time = j->min_run_time;
+ nj->timeout = j->timeout;
+ nj->exit_timeout = j->exit_timeout;
+
+ snprintf((char *)nj->label, label_sz + 1, "%s.%s", j->label, idstr);
+
+ // Set all our simple Booleans that are applicable.
+ nj->debug = j->debug;
+ nj->ondemand = j->ondemand;
+ nj->checkedin = true;
+ nj->low_pri_io = j->low_pri_io;
+ nj->setmask = j->setmask;
+ nj->wait4debugger = j->wait4debugger;
+ nj->internal_exc_handler = j->internal_exc_handler;
+ nj->setnice = j->setnice;
+ nj->abandon_pg = j->abandon_pg;
+ nj->ignore_pg_at_shutdown = j->ignore_pg_at_shutdown;
+ nj->deny_job_creation = j->deny_job_creation;
+ nj->enable_transactions = j->enable_transactions;
+ nj->needs_kickoff = j->needs_kickoff;
+ nj->currently_ignored = true;
+ nj->dedicated_instance = true;
+ nj->xpc_service = j->xpc_service;
+ nj->xpc_bootstrapper = j->xpc_bootstrapper;
+
+ nj->mask = j->mask;
+ uuid_copy(nj->instance_id, identifier);
+
+ // These jobs are purely on-demand Mach jobs.
+ // {Hard | Soft}ResourceLimits are not supported.
+ // JetsamPriority is not supported.
+
+ if (j->prog) {
+ nj->prog = strdup(j->prog);
+ }
+ if (j->argv) {
+ size_t sz = malloc_size(j->argv);
+ nj->argv = (char **)malloc(sz);
+ if (nj->argv != NULL) {
+ // This is the start of our strings.
+ char *p = ((char *)nj->argv) + ((j->argc + 1) * sizeof(char *));
+
+ size_t i = 0;
+ for (i = 0; i < j->argc; i++) {
+ (void)strcpy(p, j->argv[i]);
+ nj->argv[i] = p;
+ p += (strlen(j->argv[i]) + 1);
+ }
+ nj->argv[i] = NULL;
+ } else {
+ (void)job_assumes_zero(nj, errno);
+ }
+
+ nj->argc = j->argc;
+ }
+
+ struct machservice *msi = NULL;
+ SLIST_FOREACH(msi, &j->machservices, sle) {
+ /* Only copy MachServices that were actually declared in the plist.
+ * So skip over per-PID ones and ones that were created via
+ * bootstrap_register().
+ */
+ if (msi->upfront) {
+ mach_port_t mp = MACH_PORT_NULL;
+ struct machservice *msj = machservice_new(nj, msi->name, &mp, msi->per_pid);
+ if (msj != NULL) {
+ msj->reset = msi->reset;
+ msj->delete_on_destruction = msi->delete_on_destruction;
+ msj->drain_one_on_crash = msi->drain_one_on_crash;
+ msj->drain_all_on_crash = msi->drain_all_on_crash;
+ } else {
+ (void)job_assumes_zero(nj, errno);
+ }
+ }
+ }
+
+ // We ignore global environment variables.
+ struct envitem *ei = NULL;
+ SLIST_FOREACH(ei, &j->env, sle) {
+ if (envitem_new(nj, ei->key, ei->value, false)) {
+ (void)job_assumes_zero(nj, errno);
+ }
+ }
+ uuid_string_t val;
+ uuid_unparse(identifier, val);
+ if (envitem_new(nj, LAUNCH_ENV_INSTANCEID, val, false)) {
+ (void)job_assumes_zero(nj, errno);
+ }
+
+ if (j->rootdir) {
+ nj->rootdir = strdup(j->rootdir);
+ }
+ if (j->workingdir) {
+ nj->workingdir = strdup(j->workingdir);
+ }
+ if (j->username) {
+ nj->username = strdup(j->username);
+ }
+ if (j->groupname) {
+ nj->groupname = strdup(j->groupname);
+ }
+
+ /* FIXME: We shouldn't redirect all the output from these jobs to the
+ * same file. We should uniquify the file names. But this hasn't shown
+ * to be a problem in practice.
+ */
+ if (j->stdinpath) {
+ nj->stdinpath = strdup(j->stdinpath);
+ }
+ if (j->stdoutpath) {
+ nj->stdoutpath = strdup(j->stdinpath);
+ }
+ if (j->stderrpath) {
+ nj->stderrpath = strdup(j->stderrpath);
+ }
+ if (j->alt_exc_handler) {
+ nj->alt_exc_handler = strdup(j->alt_exc_handler);
+ }
+#if HAVE_SANDBOX
+ if (j->seatbelt_profile) {
+ nj->seatbelt_profile = strdup(j->seatbelt_profile);
+ }
+#endif
+
+#if HAVE_QUARANTINE
+ if (j->quarantine_data) {
+ nj->quarantine_data = strdup(j->quarantine_data);
+ }
+ nj->quarantine_data_sz = j->quarantine_data_sz;
+#endif
+ if (j->j_binpref) {
+ size_t sz = malloc_size(j->j_binpref);
+ nj->j_binpref = (cpu_type_t *)malloc(sz);
+ if (nj->j_binpref) {
+ memcpy(&nj->j_binpref, &j->j_binpref, sz);
+ } else {
+ (void)job_assumes_zero(nj, errno);
+ }
+ }
+
+ if (j->asport != MACH_PORT_NULL) {
+ (void)job_assumes_zero(nj, launchd_mport_copy_send(j->asport));
+ nj->asport = j->asport;
+ }
+
+ LIST_INSERT_HEAD(&nj->mgr->jobs, nj, sle);
+
+ jobmgr_t where2put = root_jobmgr;
+ if (j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) {
+ where2put = j->mgr;
+ }
+ LIST_INSERT_HEAD(&where2put->label_hash[hash_label(nj->label)], nj, label_hash_sle);
+ LIST_INSERT_HEAD(&j->subjobs, nj, subjob_sle);
+ } else {
+ (void)osx_assumes_zero(errno);
+ }
+
+ return nj;
+}
+
+job_t
+job_new(jobmgr_t jm, const char *label, const char *prog, const char *const *argv)
+{
+ const char *const *argv_tmp = argv;
+ char tmp_path[PATH_MAX];
+ char auto_label[1000];
+ const char *bn = NULL;
+ char *co;
+ size_t minlabel_len;
+ size_t i, cc = 0;
+ job_t j;
+
+ __OSX_COMPILETIME_ASSERT__(offsetof(struct job_s, kqjob_callback) == 0);
+
+ if (unlikely(jm->shutting_down)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (unlikely(prog == NULL && argv == NULL)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* I'd really like to redo this someday. Anonymous jobs carry all the
+ * baggage of managed jobs with them, even though most of it is unused.
+ * Maybe when we have Objective-C objects in libSystem, there can be a base
+ * job type that anonymous and managed jobs inherit from...
+ */
+ char *anon_or_legacy = (label == AUTO_PICK_ANONYMOUS_LABEL) ? "anonymous" : "mach_init";
+ if (unlikely(label == AUTO_PICK_LEGACY_LABEL || label == AUTO_PICK_ANONYMOUS_LABEL)) {
+ if (prog) {
+ bn = prog;
+ } else {
+ strlcpy(tmp_path, argv[0], sizeof(tmp_path));
+ // prog for auto labels is kp.kp_kproc.p_comm.
+ bn = basename(tmp_path);
+ }
+
+ (void)snprintf(auto_label, sizeof(auto_label), "%s.%s.%s", sizeof(void *) == 8 ? "0xdeadbeeffeedface" : "0xbabecafe", anon_or_legacy, bn);
+ label = auto_label;
+ /* This is so we can do gross things later. See NOTE_EXEC for anonymous
+ * jobs.
+ */
+ minlabel_len = strlen(label) + MAXCOMLEN;
+ } else {
+ if (label == AUTO_PICK_XPC_LABEL) {
+ minlabel_len = snprintf(auto_label, sizeof(auto_label), "com.apple.xpc.domain-owner.%s", jm->owner);
+ } else {
+ minlabel_len = strlen(label);
+ }
+ }
+
+ j = calloc(1, sizeof(struct job_s) + minlabel_len + 1);
+
+ if (!j) {
+ (void)osx_assumes_zero(errno);
+ return NULL;
+ }
+
+ if (unlikely(label == auto_label)) {
+ (void)snprintf((char *)j->label, strlen(label) + 1, "%p.%s.%s", j, anon_or_legacy, bn);
+ } else {
+ (void)strcpy((char *)j->label, (label == AUTO_PICK_XPC_LABEL) ? auto_label : label);
+ }
+
+ j->kqjob_callback = job_callback;
+ j->mgr = jm;
+ j->min_run_time = LAUNCHD_MIN_JOB_RUN_TIME;
+ j->timeout = RUNTIME_ADVISABLE_IDLE_TIMEOUT;
+ j->exit_timeout = LAUNCHD_DEFAULT_EXIT_TIMEOUT;
+ j->currently_ignored = true;
+ j->ondemand = true;
+ j->checkedin = true;
+ j->jetsam_priority = DEFAULT_JETSAM_PRIORITY;
+ j->jetsam_memlimit = -1;
+ uuid_clear(j->expected_audit_uuid);
+#if TARGET_OS_EMBEDDED
+ /* Run embedded daemons as background by default. SpringBoard jobs are
+ * Interactive by default. Unfortunately, so many daemons have opted into
+ * this priority band that its usefulness is highly questionable.
+ *
+ * See <rdar://problem/9539873>.
+ */
+ if (launchd_embedded_handofgod) {
+ j->pstype = POSIX_SPAWN_IOS_INTERACTIVE;
+ j->app = true;
+ } else {
+ j->pstype = POSIX_SPAWN_IOS_APPLE_DAEMON_START;
+ }
+#endif
+
+ if (prog) {
+ j->prog = strdup(prog);
+ if (!j->prog) {
+ (void)osx_assumes_zero(errno);
+ goto out_bad;
+ }
+ }
+
+ if (likely(argv)) {
+ while (*argv_tmp++) {
+ j->argc++;
+ }
+
+ for (i = 0; i < j->argc; i++) {
+ cc += strlen(argv[i]) + 1;
+ }
+
+ j->argv = malloc((j->argc + 1) * sizeof(char *) + cc);
+ if (!j->argv) {
+ (void)job_assumes_zero(j, errno);
+ goto out_bad;
+ }
+
+ co = ((char *)j->argv) + ((j->argc + 1) * sizeof(char *));
+
+ for (i = 0; i < j->argc; i++) {
+ j->argv[i] = co;
+ (void)strcpy(co, argv[i]);
+ co += strlen(argv[i]) + 1;
+ }
+ j->argv[i] = NULL;
+ }
+
+ // Sssshhh... don't tell anyone.
+ if (strcmp(j->label, "com.apple.WindowServer") == 0) {
+ j->has_console = true;
+ }
+
+ LIST_INSERT_HEAD(&jm->jobs, j, sle);
+
+ jobmgr_t where2put_label = root_jobmgr;
+ if (j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) {
+ where2put_label = j->mgr;
+ }
+ LIST_INSERT_HEAD(&where2put_label->label_hash[hash_label(j->label)], j, label_hash_sle);
+ uuid_clear(j->expected_audit_uuid);
+
+ job_log(j, LOG_DEBUG, "Conceived");
+
+ return j;
+
+out_bad:
+ if (j->prog) {
+ free(j->prog);
+ }
+ free(j);
+
+ return NULL;
+}
+
+job_t
+job_new_alias(jobmgr_t jm, job_t src)
+{
+ if (job_find(jm, src->label)) {
+ errno = EEXIST;
+ return NULL;
+ }
+
+ job_t j = calloc(1, sizeof(struct job_s) + strlen(src->label) + 1);
+ if (!j) {
+ (void)osx_assumes_zero(errno);
+ return NULL;
+ }
+
+ (void)strcpy((char *)j->label, src->label);
+ LIST_INSERT_HEAD(&jm->jobs, j, sle);
+ LIST_INSERT_HEAD(&jm->label_hash[hash_label(j->label)], j, label_hash_sle);
+ /* Bad jump address. The kqueue callback for aliases should never be
+ * invoked.
+ */
+ j->kqjob_callback = (kq_callback)0xfa1afe1;
+ j->alias = src;
+ j->mgr = jm;
+
+ struct machservice *msi = NULL;
+ SLIST_FOREACH(msi, &src->machservices, sle) {
+ if (!machservice_new_alias(j, msi)) {
+ jobmgr_log(jm, LOG_ERR, "Failed to alias job: %s", src->label);
+ errno = EINVAL;
+ job_remove(j);
+ j = NULL;
+ break;
+ }
+ }
+
+ if (j) {
+ job_log(j, LOG_DEBUG, "Aliased service into domain: %s", jm->name);
+ }
+
+ return j;
+}
+
+job_t
+job_import(launch_data_t pload)
+{
+ job_t j = jobmgr_import2(root_jobmgr, pload);
+
+ if (unlikely(j == NULL)) {
+ return NULL;
+ }
+
+ /* Since jobs are effectively stalled until they get security sessions
+ * assigned to them, we may wish to reconsider this behavior of calling the
+ * job "enabled" as far as other jobs with the OtherJobEnabled KeepAlive
+ * criterion set.
+ */
+ job_dispatch_curious_jobs(j);
+ return job_dispatch(j, false);
+}
+
+launch_data_t
+job_import_bulk(launch_data_t pload)
+{
+ launch_data_t resp = launch_data_alloc(LAUNCH_DATA_ARRAY);
+ job_t *ja;
+ size_t i, c = launch_data_array_get_count(pload);
+
+ ja = alloca(c * sizeof(job_t));
+
+ for (i = 0; i < c; i++) {
+ if ((likely(ja[i] = jobmgr_import2(root_jobmgr, launch_data_array_get_index(pload, i)))) && errno != ENEEDAUTH) {
+ errno = 0;
+ }
+ launch_data_array_set_index(resp, launch_data_new_errno(errno), i);
+ }
+
+ for (i = 0; i < c; i++) {
+ if (likely(ja[i])) {
+ job_dispatch_curious_jobs(ja[i]);
+ job_dispatch(ja[i], false);
+ }
+ }
+
+ return resp;
+}
+
+void
+job_import_bool(job_t j, const char *key, bool value)
+{
+ bool found_key = false;
+
+ switch (key[0]) {
+ case 'a':
+ case 'A':
+ if (strcasecmp(key, LAUNCH_JOBKEY_ABANDONPROCESSGROUP) == 0) {
+ j->abandon_pg = value;
+ found_key = true;
+ }
+ break;
+ case 'b':
+ case 'B':
+ if (strcasecmp(key, LAUNCH_JOBKEY_BEGINTRANSACTIONATSHUTDOWN) == 0) {
+ j->dirty_at_shutdown = value;
+ found_key = true;
+ }
+ break;
+ case 'k':
+ case 'K':
+ if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE) == 0) {
+ j->ondemand = !value;
+ found_key = true;
+ }
+ break;
+ case 'o':
+ case 'O':
+ if (strcasecmp(key, LAUNCH_JOBKEY_ONDEMAND) == 0) {
+ j->ondemand = value;
+ found_key = true;
+ }
+ break;
+ case 'd':
+ case 'D':
+ if (strcasecmp(key, LAUNCH_JOBKEY_DEBUG) == 0) {
+ j->debug = value;
+ found_key = true;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_DISABLED) == 0) {
+ (void)job_assumes(j, !value);
+ found_key = true;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_DISABLEASLR) == 0) {
+ j->disable_aslr = value;
+ found_key = true;
+ }
+ break;
+ case 'h':
+ case 'H':
+ if (strcasecmp(key, LAUNCH_JOBKEY_HOPEFULLYEXITSLAST) == 0) {
+ job_log(j, LOG_PERF, "%s has been deprecated. Please use the new %s key instead and add EnableTransactions to your launchd.plist.", LAUNCH_JOBKEY_HOPEFULLYEXITSLAST, LAUNCH_JOBKEY_BEGINTRANSACTIONATSHUTDOWN);
+ j->dirty_at_shutdown = value;
+ found_key = true;
+ }
+ break;
+ case 's':
+ case 'S':
+ if (strcasecmp(key, LAUNCH_JOBKEY_SESSIONCREATE) == 0) {
+ j->session_create = value;
+ found_key = true;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_STARTONMOUNT) == 0) {
+ j->start_on_mount = value;
+ found_key = true;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_SERVICEIPC) == 0) {
+ // this only does something on Mac OS X 10.4 "Tiger"
+ found_key = true;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_SHUTDOWNMONITOR) == 0) {
+ if (_launchd_shutdown_monitor) {
+ job_log(j, LOG_ERR, "Only one job may monitor shutdown.");
+ } else {
+ j->shutdown_monitor = true;
+ _launchd_shutdown_monitor = j;
+ }
+ found_key = true;
+ }
+ break;
+ case 'l':
+ case 'L':
+ if (strcasecmp(key, LAUNCH_JOBKEY_LOWPRIORITYIO) == 0) {
+ j->low_pri_io = value;
+ found_key = true;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_LAUNCHONLYONCE) == 0) {
+ j->only_once = value;
+ found_key = true;
+ }
+ break;
+ case 'm':
+ case 'M':
+ if (strcasecmp(key, LAUNCH_JOBKEY_MACHEXCEPTIONHANDLER) == 0) {
+ j->internal_exc_handler = value;
+ found_key = true;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_MULTIPLEINSTANCES) == 0) {
+ j->multiple_instances = value;
+ found_key = true;
+ }
+ break;
+ case 'i':
+ case 'I':
+ if (strcasecmp(key, LAUNCH_JOBKEY_INITGROUPS) == 0) {
+ if (getuid() != 0) {
+ job_log(j, LOG_WARNING, "Ignored this key: %s", key);
+ return;
+ }
+ j->no_init_groups = !value;
+ found_key = true;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_IGNOREPROCESSGROUPATSHUTDOWN) == 0) {
+ j->ignore_pg_at_shutdown = value;
+ found_key = true;
+ }
+ break;
+ case 'r':
+ case 'R':
+ if (strcasecmp(key, LAUNCH_JOBKEY_RUNATLOAD) == 0) {
+ if (value) {
+ // We don't want value == false to change j->start_pending
+ j->start_pending = true;
+ }
+ found_key = true;
+ }
+ break;
+ case 'e':
+ case 'E':
+ if (strcasecmp(key, LAUNCH_JOBKEY_ENABLEGLOBBING) == 0) {
+ j->globargv = value;
+ found_key = true;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_ENABLETRANSACTIONS) == 0) {
+ j->enable_transactions = value;
+ found_key = true;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_ENTERKERNELDEBUGGERBEFOREKILL) == 0) {
+ j->debug_before_kill = value;
+ found_key = true;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_EMBEDDEDPRIVILEGEDISPENSATION) == 0) {
+#if TARGET_OS_EMBEDDED
+ if (!_launchd_embedded_god) {
+ if ((j->embedded_god = value)) {
+ _launchd_embedded_god = j;
+ }
+ } else {
+ job_log(j, LOG_ERR, "Job tried to claim %s after it has already been claimed.", key);
+ }
+#else
+ job_log(j, LOG_ERR, "This key is not supported on this platform: %s", key);
+#endif
+ found_key = true;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_EVENTMONITOR) == 0) {
+ if (!_launchd_event_monitor) {
+ j->event_monitor = value;
+ if (value) {
+ _launchd_event_monitor = j;
+ }
+ } else {
+ job_log(j, LOG_NOTICE, "Job tried to steal event monitoring responsibility from: %s", _launchd_event_monitor->label);
+ }
+ found_key = true;
+ }
+ break;
+ case 'w':
+ case 'W':
+ if (strcasecmp(key, LAUNCH_JOBKEY_WAITFORDEBUGGER) == 0) {
+ j->wait4debugger = value;
+ found_key = true;
+ }
+ break;
+ case 'x':
+ case 'X':
+ if (strcasecmp(key, LAUNCH_JOBKEY_XPCDOMAINBOOTSTRAPPER) == 0) {
+ if (pid1_magic) {
+ if (_launchd_xpc_bootstrapper) {
+ job_log(j, LOG_ERR, "This job tried to steal the XPC domain bootstrapper property from the following job: %s", _launchd_xpc_bootstrapper->label);
+ } else {
+ _launchd_xpc_bootstrapper = j;
+ j->xpc_bootstrapper = value;
+ }
+ } else {
+ job_log(j, LOG_ERR, "Non-daemon tried to claim XPC bootstrapper property.");
+ }
+ }
+ found_key = true;
+ break;
+ default:
+ break;
+ }
+
+ if (unlikely(!found_key)) {
+ job_log(j, LOG_WARNING, "Unknown key for boolean: %s", key);
+ }
+}
+
+void
+job_import_string(job_t j, const char *key, const char *value)
+{
+ char **where2put = NULL;
+
+ switch (key[0]) {
+ case 'm':
+ case 'M':
+ if (strcasecmp(key, LAUNCH_JOBKEY_MACHEXCEPTIONHANDLER) == 0) {
+ where2put = &j->alt_exc_handler;
+ }
+ break;
+ case 'p':
+ case 'P':
+ if (strcasecmp(key, LAUNCH_JOBKEY_PROGRAM) == 0) {
+ return;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_POSIXSPAWNTYPE) == 0) {
+ if (strcasecmp(value, LAUNCH_KEY_POSIXSPAWNTYPE_TALAPP) == 0) {
+#if !TARGET_OS_EMBEDDED
+ j->pstype = POSIX_SPAWN_OSX_TALAPP_START;
+#endif
+ } else if (strcasecmp(value, LAUNCH_KEY_POSIXSPAWNTYPE_WIDGET) == 0) {
+#if !TARGET_OS_EMBEDDED
+ j->pstype = POSIX_SPAWN_OSX_DBCLIENT_START;
+#endif
+ } else if (strcasecmp(value, LAUNCH_KEY_POSIXSPAWNTYPE_IOSAPP) == 0) {
+#if TARGET_OS_EMBEDDED
+ j->pstype = POSIX_SPAWN_IOS_APP_START;
+#endif
+ } else if (strcasecmp(value, LAUNCH_KEY_POSIXSPAWNTYPE_INTERACTIVE) == 0) {
+#if TARGET_OS_EMBEDDED
+ j->pstype = POSIX_SPAWN_IOS_INTERACTIVE;
+#endif
+ } else if (strcasecmp(value, LAUNCH_KEY_POSIXSPAWNTYPE_BACKGROUND) == 0) {
+#if TARGET_OS_EMBEDDED
+ j->pstype = POSIX_SPAWN_IOS_APPLE_DAEMON_START;
+#endif
+ } else if (strcasecmp(value, "Adaptive") == 0) {
+ // Hack.
+ } else {
+ job_log(j, LOG_ERR, "Unknown value for key %s: %s", key, value);
+ }
+ return;
+ }
+ break;
+ case 'l':
+ case 'L':
+ if (strcasecmp(key, LAUNCH_JOBKEY_LABEL) == 0) {
+ return;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOHOSTS) == 0) {
+ return;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADFROMHOSTS) == 0) {
+ return;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE) == 0) {
+ return;
+ }
+ break;
+ case 'r':
+ case 'R':
+ if (strcasecmp(key, LAUNCH_JOBKEY_ROOTDIRECTORY) == 0) {
+ if (getuid() != 0) {
+ job_log(j, LOG_WARNING, "Ignored this key: %s", key);
+ return;
+ }
+ where2put = &j->rootdir;
+ }
+ break;
+ case 'w':
+ case 'W':
+ if (strcasecmp(key, LAUNCH_JOBKEY_WORKINGDIRECTORY) == 0) {
+ where2put = &j->workingdir;
+ }
+ break;
+ case 'u':
+ case 'U':
+ if (strcasecmp(key, LAUNCH_JOBKEY_USERNAME) == 0) {
+ if (getuid() != 0) {
+ job_log(j, LOG_WARNING, "Ignored this key: %s", key);
+ return;
+ } else if (strcmp(value, "root") == 0) {
+ return;
+ }
+ where2put = &j->username;
+ }
+ break;
+ case 'g':
+ case 'G':
+ if (strcasecmp(key, LAUNCH_JOBKEY_GROUPNAME) == 0) {
+ if (getuid() != 0) {
+ job_log(j, LOG_WARNING, "Ignored this key: %s", key);
+ return;
+ } else if (strcmp(value, "wheel") == 0) {
+ return;
+ }
+ where2put = &j->groupname;
+ }
+ break;
+ case 's':
+ case 'S':
+ if (strcasecmp(key, LAUNCH_JOBKEY_STANDARDOUTPATH) == 0) {
+ where2put = &j->stdoutpath;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_STANDARDERRORPATH) == 0) {
+ where2put = &j->stderrpath;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_STANDARDINPATH) == 0) {
+ where2put = &j->stdinpath;
+ j->stdin_fd = _fd(open(value, O_RDONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, DEFFILEMODE));
+ if (job_assumes_zero_p(j, j->stdin_fd) != -1) {
+ // open() should not block, but regular IO by the job should
+ (void)job_assumes_zero_p(j, fcntl(j->stdin_fd, F_SETFL, 0));
+ // XXX -- EV_CLEAR should make named pipes happy?
+ (void)job_assumes_zero_p(j, kevent_mod(j->stdin_fd, EVFILT_READ, EV_ADD|EV_CLEAR, 0, 0, j));
+ } else {
+ j->stdin_fd = 0;
+ }
+#if HAVE_SANDBOX
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_SANDBOXPROFILE) == 0) {
+ where2put = &j->seatbelt_profile;
+#endif
+ }
+ break;
+ case 'X':
+ case 'x':
+ if (strcasecmp(key, LAUNCH_JOBKEY_XPCDOMAIN) == 0) {
+ return;
+ }
+ break;
+ default:
+ job_log(j, LOG_WARNING, "Unknown key for string: %s", key);
+ break;
+ }
+
+ if (likely(where2put)) {
+ if (!(*where2put = strdup(value))) {
+ (void)job_assumes_zero(j, errno);
+ }
+ } else {
+ // See rdar://problem/5496612. These two are okay.
+ if (strncmp(key, "SHAuthorizationRight", sizeof("SHAuthorizationRight")) == 0
+ || strncmp(key, "ServiceDescription", sizeof("ServiceDescription")) == 0) {
+ job_log(j, LOG_APPLEONLY, "This key is no longer relevant and should be removed: %s", key);
+ } else {
+ job_log(j, LOG_WARNING, "Unknown key: %s", key);
+ }
+ }
+}
+
+void
+job_import_integer(job_t j, const char *key, long long value)
+{
+ switch (key[0]) {
+ case 'e':
+ case 'E':
+ if (strcasecmp(key, LAUNCH_JOBKEY_EXITTIMEOUT) == 0) {
+ if (unlikely(value < 0)) {
+ job_log(j, LOG_WARNING, "%s less than zero. Ignoring.", LAUNCH_JOBKEY_EXITTIMEOUT);
+ } else if (unlikely(value > UINT32_MAX)) {
+ job_log(j, LOG_WARNING, "%s is too large. Ignoring.", LAUNCH_JOBKEY_EXITTIMEOUT);
+ } else {
+ j->exit_timeout = (typeof(j->exit_timeout)) value;
+ }
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_EMBEDDEDMAINTHREADPRIORITY) == 0) {
+ j->main_thread_priority = value;
+ }
+ break;
+ case 'j':
+ case 'J':
+ if (strcasecmp(key, LAUNCH_JOBKEY_JETSAMPRIORITY) == 0) {
+ job_log(j, LOG_WARNING | LOG_CONSOLE, "Please change the JetsamPriority key to be in a dictionary named JetsamProperties.");
+
+ launch_data_t pri = launch_data_new_integer(value);
+ if (job_assumes(j, pri != NULL)) {
+ jetsam_property_setup(pri, LAUNCH_JOBKEY_JETSAMPRIORITY, j);
+ launch_data_free(pri);
+ }
+ }
+ case 'n':
+ case 'N':
+ if (strcasecmp(key, LAUNCH_JOBKEY_NICE) == 0) {
+ if (unlikely(value < PRIO_MIN)) {
+ job_log(j, LOG_WARNING, "%s less than %d. Ignoring.", LAUNCH_JOBKEY_NICE, PRIO_MIN);
+ } else if (unlikely(value > PRIO_MAX)) {
+ job_log(j, LOG_WARNING, "%s is greater than %d. Ignoring.", LAUNCH_JOBKEY_NICE, PRIO_MAX);
+ } else {
+ j->nice = (typeof(j->nice)) value;
+ j->setnice = true;
+ }
+ }
+ break;
+ case 't':
+ case 'T':
+ if (strcasecmp(key, LAUNCH_JOBKEY_TIMEOUT) == 0) {
+ if (unlikely(value < 0)) {
+ job_log(j, LOG_WARNING, "%s less than zero. Ignoring.", LAUNCH_JOBKEY_TIMEOUT);
+ } else if (unlikely(value > UINT32_MAX)) {
+ job_log(j, LOG_WARNING, "%s is too large. Ignoring.", LAUNCH_JOBKEY_TIMEOUT);
+ } else {
+ j->timeout = (typeof(j->timeout)) value;
+ }
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_THROTTLEINTERVAL) == 0) {
+ if (value < 0) {
+ job_log(j, LOG_WARNING, "%s less than zero. Ignoring.", LAUNCH_JOBKEY_THROTTLEINTERVAL);
+ } else if (value > UINT32_MAX) {
+ job_log(j, LOG_WARNING, "%s is too large. Ignoring.", LAUNCH_JOBKEY_THROTTLEINTERVAL);
+ } else {
+ j->min_run_time = (typeof(j->min_run_time)) value;
+ }
+ }
+ break;
+ case 'u':
+ case 'U':
+ if (strcasecmp(key, LAUNCH_JOBKEY_UMASK) == 0) {
+ j->mask = value;
+ j->setmask = true;
+ }
+ break;
+ case 's':
+ case 'S':
+ if (strcasecmp(key, LAUNCH_JOBKEY_STARTINTERVAL) == 0) {
+ if (unlikely(value <= 0)) {
+ job_log(j, LOG_WARNING, "%s is not greater than zero. Ignoring.", LAUNCH_JOBKEY_STARTINTERVAL);
+ } else if (unlikely(value > UINT32_MAX)) {
+ job_log(j, LOG_WARNING, "%s is too large. Ignoring.", LAUNCH_JOBKEY_STARTINTERVAL);
+ } else {
+ runtime_add_weak_ref();
+ j->start_interval = (typeof(j->start_interval)) value;
+
+ (void)job_assumes_zero_p(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, j->start_interval, j));
+ }
+#if HAVE_SANDBOX
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_SANDBOXFLAGS) == 0) {
+ j->seatbelt_flags = value;
+#endif
+ }
+
+ break;
+ default:
+ job_log(j, LOG_WARNING, "Unknown key for integer: %s", key);
+ break;
+ }
+}
+
+void
+job_import_opaque(job_t j __attribute__((unused)), const char *key, launch_data_t value __attribute__((unused)))
+{
+ switch (key[0]) {
+ case 'q':
+ case 'Q':
+#if HAVE_QUARANTINE
+ if (strcasecmp(key, LAUNCH_JOBKEY_QUARANTINEDATA) == 0) {
+ size_t tmpsz = launch_data_get_opaque_size(value);
+
+ if (job_assumes(j, j->quarantine_data = malloc(tmpsz))) {
+ memcpy(j->quarantine_data, launch_data_get_opaque(value), tmpsz);
+ j->quarantine_data_sz = tmpsz;
+ }
+ }
+#endif
+ case 's':
+ case 'S':
+ if (strcasecmp(key, LAUNCH_JOBKEY_SECURITYSESSIONUUID) == 0) {
+ size_t tmpsz = launch_data_get_opaque_size(value);
+ if (job_assumes(j, tmpsz == sizeof(uuid_t))) {
+ memcpy(j->expected_audit_uuid, launch_data_get_opaque(value), sizeof(uuid_t));
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+policy_setup(launch_data_t obj, const char *key, void *context)
+{
+ job_t j = context;
+ bool found_key = false;
+
+ switch (key[0]) {
+ case 'd':
+ case 'D':
+ if (strcasecmp(key, LAUNCH_JOBPOLICY_DENYCREATINGOTHERJOBS) == 0) {
+ j->deny_job_creation = launch_data_get_bool(obj);
+ found_key = true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (unlikely(!found_key)) {
+ job_log(j, LOG_WARNING, "Unknown policy: %s", key);
+ }
+}
+
+void
+job_import_dictionary(job_t j, const char *key, launch_data_t value)
+{
+ launch_data_t tmp;
+
+ switch (key[0]) {
+ case 'p':
+ case 'P':
+ if (strcasecmp(key, LAUNCH_JOBKEY_POLICIES) == 0) {
+ launch_data_dict_iterate(value, policy_setup, j);
+ }
+ break;
+ case 'k':
+ case 'K':
+ if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE) == 0) {
+ launch_data_dict_iterate(value, semaphoreitem_setup, j);
+ }
+ break;
+ case 'i':
+ case 'I':
+ if (strcasecmp(key, LAUNCH_JOBKEY_INETDCOMPATIBILITY) == 0) {
+ j->inetcompat = true;
+ j->abandon_pg = true;
+ if ((tmp = launch_data_dict_lookup(value, LAUNCH_JOBINETDCOMPATIBILITY_WAIT))) {
+ j->inetcompat_wait = launch_data_get_bool(tmp);
+ }
+ }
+ break;
+ case 'j':
+ case 'J':
+ if (strcasecmp(key, LAUNCH_JOBKEY_JETSAMPROPERTIES) == 0) {
+ launch_data_dict_iterate(value, (void (*)(launch_data_t, const char *, void *))jetsam_property_setup, j);
+ }
+ case 'e':
+ case 'E':
+ if (strcasecmp(key, LAUNCH_JOBKEY_ENVIRONMENTVARIABLES) == 0) {
+ launch_data_dict_iterate(value, envitem_setup, j);
+ }
+ break;
+ case 'u':
+ case 'U':
+ if (strcasecmp(key, LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES) == 0) {
+ j->importing_global_env = true;
+ launch_data_dict_iterate(value, envitem_setup, j);
+ j->importing_global_env = false;
+ }
+ break;
+ case 's':
+ case 'S':
+ if (strcasecmp(key, LAUNCH_JOBKEY_SOCKETS) == 0) {
+ launch_data_dict_iterate(value, socketgroup_setup, j);
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_STARTCALENDARINTERVAL) == 0) {
+ calendarinterval_new_from_obj(j, value);
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_SOFTRESOURCELIMITS) == 0) {
+ launch_data_dict_iterate(value, limititem_setup, j);
+#if HAVE_SANDBOX
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_SANDBOXFLAGS) == 0) {
+ launch_data_dict_iterate(value, seatbelt_setup_flags, j);
+#endif
+ }
+ break;
+ case 'h':
+ case 'H':
+ if (strcasecmp(key, LAUNCH_JOBKEY_HARDRESOURCELIMITS) == 0) {
+ j->importing_hard_limits = true;
+ launch_data_dict_iterate(value, limititem_setup, j);
+ j->importing_hard_limits = false;
+ }
+ break;
+ case 'm':
+ case 'M':
+ if (strcasecmp(key, LAUNCH_JOBKEY_MACHSERVICES) == 0) {
+ launch_data_dict_iterate(value, machservice_setup, j);
+ }
+ break;
+ case 'l':
+ case 'L':
+ if (strcasecmp(key, LAUNCH_JOBKEY_LAUNCHEVENTS) == 0) {
+ launch_data_dict_iterate(value, eventsystem_setup, j);
+ } else {
+ if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOHARDWARE) == 0) {
+ return;
+ }
+ if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADFROMHARDWARE) == 0) {
+ return;
+ }
+ }
+ break;
+ default:
+ job_log(j, LOG_WARNING, "Unknown key for dictionary: %s", key);
+ break;
+ }
+}
+
+void
+job_import_array(job_t j, const char *key, launch_data_t value)
+{
+ size_t i, value_cnt = launch_data_array_get_count(value);
+
+ switch (key[0]) {
+ case 'p':
+ case 'P':
+ if (strcasecmp(key, LAUNCH_JOBKEY_PROGRAMARGUMENTS) == 0) {
+ return;
+ }
+ break;
+ case 'l':
+ case 'L':
+ if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOHOSTS) == 0) {
+ return;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADFROMHOSTS) == 0) {
+ return;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE) == 0) {
+ job_log(j, LOG_NOTICE, "launchctl should have transformed the \"%s\" array to a string", LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE);
+ return;
+ }
+ break;
+ case 'b':
+ case 'B':
+ if (strcasecmp(key, LAUNCH_JOBKEY_BINARYORDERPREFERENCE) == 0) {
+ if (job_assumes(j, j->j_binpref = malloc(value_cnt * sizeof(*j->j_binpref)))) {
+ j->j_binpref_cnt = value_cnt;
+ for (i = 0; i < value_cnt; i++) {
+ j->j_binpref[i] = (cpu_type_t) launch_data_get_integer(launch_data_array_get_index(value, i));
+ }
+ }
+ }
+ break;
+ case 's':
+ case 'S':
+ if (strcasecmp(key, LAUNCH_JOBKEY_STARTCALENDARINTERVAL) == 0) {
+ for (i = 0; i < value_cnt; i++) {
+ calendarinterval_new_from_obj(j, launch_data_array_get_index(value, i));
+ }
+ }
+ break;
+ default:
+ job_log(j, LOG_WARNING, "Unknown key for array: %s", key);
+ break;
+ }
+}
+
+void
+job_import_keys(launch_data_t obj, const char *key, void *context)
+{
+ job_t j = context;
+ launch_data_type_t kind;
+
+ if (!obj) {
+ launchd_syslog(LOG_ERR, "NULL object given to job_import_keys().");
+ return;
+ }
+
+ kind = launch_data_get_type(obj);
+
+ switch (kind) {
+ case LAUNCH_DATA_BOOL:
+ job_import_bool(j, key, launch_data_get_bool(obj));
+ break;
+ case LAUNCH_DATA_STRING:
+ job_import_string(j, key, launch_data_get_string(obj));
+ break;
+ case LAUNCH_DATA_INTEGER:
+ job_import_integer(j, key, launch_data_get_integer(obj));
+ break;
+ case LAUNCH_DATA_DICTIONARY:
+ job_import_dictionary(j, key, obj);
+ break;
+ case LAUNCH_DATA_ARRAY:
+ job_import_array(j, key, obj);
+ break;
+ case LAUNCH_DATA_OPAQUE:
+ job_import_opaque(j, key, obj);
+ break;
+ default:
+ job_log(j, LOG_WARNING, "Unknown value type '%d' for key: %s", kind, key);
+ break;
+ }
+}
+
+job_t
+jobmgr_import2(jobmgr_t jm, launch_data_t pload)
+{
+ launch_data_t tmp, ldpa;
+ const char *label = NULL, *prog = NULL;
+ const char **argv = NULL;
+ job_t j;
+
+ if (!jobmgr_assumes(jm, pload != NULL)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (unlikely(launch_data_get_type(pload) != LAUNCH_DATA_DICTIONARY)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (unlikely(!(tmp = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_LABEL)))) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (unlikely(launch_data_get_type(tmp) != LAUNCH_DATA_STRING)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (unlikely(!(label = launch_data_get_string(tmp)))) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+#if TARGET_OS_EMBEDDED
+ if (unlikely(launchd_embedded_handofgod && _launchd_embedded_god)) {
+ if (unlikely(!(tmp = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_USERNAME)))) {
+ errno = EPERM;
+ return NULL;
+ }
+
+ const char *username = NULL;
+ if (likely(tmp && launch_data_get_type(tmp) == LAUNCH_DATA_STRING)) {
+ username = launch_data_get_string(tmp);
+ } else {
+ errno = EPERM;
+ return NULL;
+ }
+
+ if (!jobmgr_assumes(jm, _launchd_embedded_god->username != NULL && username != NULL)) {
+ errno = EPERM;
+ return NULL;
+ }
+
+ if (unlikely(strcmp(_launchd_embedded_god->username, username) != 0)) {
+ errno = EPERM;
+ return NULL;
+ }
+ } else if (launchd_embedded_handofgod) {
+ errno = EINVAL;
+ return NULL;
+ }
+#endif
+
+ if ((tmp = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAM))
+ && (launch_data_get_type(tmp) == LAUNCH_DATA_STRING)) {
+ prog = launch_data_get_string(tmp);
+ }
+
+ int argc = 0;
+ if ((ldpa = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAMARGUMENTS))) {
+ size_t i, c;
+
+ if (launch_data_get_type(ldpa) != LAUNCH_DATA_ARRAY) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ c = launch_data_array_get_count(ldpa);
+
+ argv = alloca((c + 1) * sizeof(char *));
+
+ for (i = 0; i < c; i++) {
+ tmp = launch_data_array_get_index(ldpa, i);
+
+ if (launch_data_get_type(tmp) != LAUNCH_DATA_STRING) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ argv[i] = launch_data_get_string(tmp);
+ }
+
+ argv[i] = NULL;
+ argc = i;
+ }
+
+ if (!prog && argc == 0) {
+ jobmgr_log(jm, LOG_ERR, "Job specifies neither Program nor ProgramArguments: %s", label);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* Find the requested session. You cannot load services into XPC domains in
+ * this manner.
+ */
+ launch_data_t session = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE);
+ if (session) {
+ jobmgr_t jmt = NULL;
+ if (launch_data_get_type(session) == LAUNCH_DATA_STRING) {
+ jmt = jobmgr_find_by_name(jm, launch_data_get_string(session));
+ if (!jmt) {
+ jobmgr_log(jm, LOG_ERR, "Could not find requested session: %s", launch_data_get_string(session));
+ } else {
+ jm = jmt;
+ }
+ } else {
+ jobmgr_log(jm, LOG_ERR, "Session type is not a string.");
+ }
+
+ if (!jmt) {
+ errno = EINVAL;
+ return NULL;
+ }
+ }
+
+ /* For legacy reasons, we have a global hash of all labels in all job
+ * managers. So rather than make it a global, we store it in the root job
+ * manager. But for an XPC domain, we store a local hash of all services in
+ * the domain.
+ */
+ jobmgr_t where2look = (jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) ? jm : root_jobmgr;
+ if (unlikely((j = job_find(where2look, label)) != NULL)) {
+ if (jm->xpc_singleton) {
+ /* There can (and probably will be) multiple attemtps to import the
+ * same XPC service from the same framework. This is okay. It's
+ * treated as a singleton, so just return the existing one so that
+ * it may be aliased into the requesting process' XPC domain.
+ */
+ errno = EEXIST;
+ return j;
+ } else {
+ /* If we're not a global XPC domain, then it's an error to try
+ * importing the same job/service multiple times.
+ */
+ errno = EEXIST;
+ return NULL;
+ }
+ } else if (unlikely(!jobmgr_label_test(where2look, label))) {
+ errno = EINVAL;
+ return NULL;
+ }
+ jobmgr_log(jm, LOG_DEBUG, "Importing %s.", label);
+
+ if (likely(j = job_new(jm, label, prog, argv))) {
+ launch_data_dict_iterate(pload, job_import_keys, j);
+ if (!uuid_is_null(j->expected_audit_uuid)) {
+ uuid_string_t uuid_str;
+ uuid_unparse(j->expected_audit_uuid, uuid_str);
+ job_log(j, LOG_DEBUG, "Imported job. Waiting for session for UUID %s.", uuid_str);
+ LIST_INSERT_HEAD(&s_needing_sessions, j, needing_session_sle);
+ errno = ENEEDAUTH;
+ } else {
+ job_log(j, LOG_DEBUG, "No security session specified.");
+ j->asport = MACH_PORT_NULL;
+ }
+
+ if (pid1_magic && !jm->parentmgr) {
+ /* Workaround reentrancy in CF. We don't make this a global variable
+ * because we don't want per-user launchd's to inherit it. So we
+ * just set it for every job that we import into the System session.
+ *
+ * See <rdar://problem/9468837>.
+ */
+ envitem_new(j, "__CF_USER_TEXT_ENCODING", "0x0:0:0", false);
+ }
+
+ if (j->event_monitor) {
+ eventsystem_ping();
+ }
+
+#if TARGET_OS_EMBEDDED
+ /* SpringBoard runs at Interactive priority.
+ *
+ * See <rdar://problem/9539873>.
+ */
+ if (j->embedded_god) {
+ j->pstype = POSIX_SPAWN_IOS_INTERACTIVE;
+ }
+#endif
+ }
+
+ return j;
+}
+
+bool
+jobmgr_label_test(jobmgr_t jm, const char *str)
+{
+ char *endstr = NULL;
+ const char *ptr;
+
+ if (str[0] == '\0') {
+ jobmgr_log(jm, LOG_ERR, "Empty job labels are not allowed");
+ return false;
+ }
+
+ for (ptr = str; *ptr; ptr++) {
+ if (iscntrl(*ptr)) {
+ jobmgr_log(jm, LOG_ERR, "ASCII control characters are not allowed in job labels. Index: %td Value: 0x%hhx", ptr - str, *ptr);
+ return false;
+ }
+ }
+
+ strtoll(str, &endstr, 0);
+
+ if (str != endstr) {
+ jobmgr_log(jm, LOG_ERR, "Job labels are not allowed to begin with numbers: %s", str);
+ return false;
+ }
+
+ if ((strncasecmp(str, "com.apple.launchd", strlen("com.apple.launchd")) == 0)
+ || (strncasecmp(str, "com.apple.launchctl", strlen("com.apple.launchctl")) == 0)) {
+ jobmgr_log(jm, LOG_ERR, "Job labels are not allowed to use a reserved prefix: %s", str);
+ return false;
+ }
+
+ return true;
+}
+
+job_t
+job_find(jobmgr_t jm, const char *label)
+{
+ job_t ji;
+
+ if (!jm) {
+ jm = root_jobmgr;
+ }
+
+ LIST_FOREACH(ji, &jm->label_hash[hash_label(label)], label_hash_sle) {
+ if (unlikely(ji->removal_pending || ji->mgr->shutting_down)) {
+ // 5351245 and 5488633 respectively
+ continue;
+ }
+
+ if (strcmp(ji->label, label) == 0) {
+ return ji;
+ }
+ }
+
+ errno = ESRCH;
+ return NULL;
+}
+
+// Should try and consolidate with job_mig_intran2() and jobmgr_find_by_pid().
+job_t
+jobmgr_find_by_pid_deep(jobmgr_t jm, pid_t p, bool anon_okay)
+{
+ job_t ji = NULL;
+ LIST_FOREACH(ji, &jm->active_jobs[ACTIVE_JOB_HASH(p)], pid_hash_sle) {
+ if (ji->p == p && (!ji->anonymous || (ji->anonymous && anon_okay))) {
+ return ji;
+ }
+ }
+
+ jobmgr_t jmi = NULL;
+ SLIST_FOREACH(jmi, &jm->submgrs, sle) {
+ if ((ji = jobmgr_find_by_pid_deep(jmi, p, anon_okay))) {
+ break;
+ }
+ }
+
+ return ji;
+}
+
+job_t
+jobmgr_find_by_pid(jobmgr_t jm, pid_t p, bool create_anon)
+{
+ job_t ji;
+
+ LIST_FOREACH(ji, &jm->active_jobs[ACTIVE_JOB_HASH(p)], pid_hash_sle) {
+ if (ji->p == p) {
+ return ji;
+ }
+ }
+
+ return create_anon ? job_new_anonymous(jm, p) : NULL;
+}
+
+job_t
+job_mig_intran2(jobmgr_t jm, mach_port_t mport, pid_t upid)
+{
+ jobmgr_t jmi;
+ job_t ji;
+
+ if (jm->jm_port == mport) {
+ return jobmgr_find_by_pid(jm, upid, true);
+ }
+
+ SLIST_FOREACH(jmi, &jm->submgrs, sle) {
+ job_t jr;
+
+ if ((jr = job_mig_intran2(jmi, mport, upid))) {
+ return jr;
+ }
+ }
+
+ LIST_FOREACH(ji, &jm->jobs, sle) {
+ if (ji->j_port == mport) {
+ return ji;
+ }
+ }
+
+ return NULL;
+}
+
+job_t
+job_mig_intran(mach_port_t p)
+{
+ struct ldcred *ldc = runtime_get_caller_creds();
+ job_t jr;
+
+ jr = job_mig_intran2(root_jobmgr, p, ldc->pid);
+
+ if (!jr) {
+ struct proc_bsdshortinfo proc;
+ if (proc_pidinfo(ldc->pid, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
+ if (errno != ESRCH) {
+ (void)jobmgr_assumes_zero(root_jobmgr, errno);
+ } else {
+ jobmgr_log(root_jobmgr, LOG_ERR, "%s[%i] disappeared out from under us (UID: %u EUID: %u)", proc.pbsi_comm, ldc->pid, ldc->uid, ldc->euid);
+ }
+ }
+ }
+
+ return jr;
+}
+
+job_t
+job_find_by_service_port(mach_port_t p)
+{
+ struct machservice *ms;
+
+ LIST_FOREACH(ms, &port_hash[HASH_PORT(p)], port_hash_sle) {
+ if (ms->recv && (ms->port == p)) {
+ return ms->job;
+ }
+ }
+
+ return NULL;
+}
+
+void
+job_mig_destructor(job_t j)
+{
+ /* The job can go invalid before this point.
+ *
+ * <rdar://problem/5477111>
+ */
+ if (unlikely(j && (j != workaround_5477111) && j->unload_at_mig_return)) {
+ job_log(j, LOG_NOTICE, "Unloading PID %u at MIG return.", j->p);
+ job_remove(j);
+ }
+
+ workaround_5477111 = NULL;
+
+ calendarinterval_sanity_check();
+}
+
+void
+job_export_all2(jobmgr_t jm, launch_data_t where)
+{
+ jobmgr_t jmi;
+ job_t ji;
+
+ SLIST_FOREACH(jmi, &jm->submgrs, sle) {
+ job_export_all2(jmi, where);
+ }
+
+ LIST_FOREACH(ji, &jm->jobs, sle) {
+ launch_data_t tmp;
+
+ if (jobmgr_assumes(jm, (tmp = job_export(ji)) != NULL)) {
+ launch_data_dict_insert(where, tmp, ji->label);
+ }
+ }
+}
+
+launch_data_t
+job_export_all(void)
+{
+ launch_data_t resp = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+
+ if (resp != NULL) {
+ job_export_all2(root_jobmgr, resp);
+ } else {
+ (void)osx_assumes_zero(errno);
+ }
+
+ return resp;
+}
+
+void
+job_log_stray_pg(job_t j)
+{
+ pid_t *pids = NULL;
+ size_t len = sizeof(pid_t) * get_kern_max_proc();
+ int i = 0, kp_cnt = 0;
+
+ if (!launchd_apple_internal) {
+ return;
+ }
+
+ runtime_ktrace(RTKT_LAUNCHD_FINDING_STRAY_PG, j->p, 0, 0);
+
+ if (!job_assumes(j, (pids = malloc(len)) != NULL)) {
+ return;
+ }
+ if (job_assumes_zero_p(j, (kp_cnt = proc_listpgrppids(j->p, pids, len))) == -1) {
+ goto out;
+ }
+
+ for (i = 0; i < kp_cnt; i++) {
+ pid_t p_i = pids[i];
+ if (p_i == j->p) {
+ continue;
+ } else if (p_i == 0 || p_i == 1) {
+ continue;
+ }
+
+ struct proc_bsdshortinfo proc;
+ if (proc_pidinfo(p_i, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
+ if (errno != ESRCH) {
+ (void)job_assumes_zero(j, errno);
+ }
+ continue;
+ }
+
+ pid_t pp_i = proc.pbsi_ppid;
+ const char *z = (proc.pbsi_status == SZOMB) ? "zombie " : "";
+ const char *n = proc.pbsi_comm;
+
+ job_log(j, LOG_WARNING, "Stray %sprocess with PGID equal to this dead job: PID %u PPID %u PGID %u %s", z, p_i, pp_i, proc.pbsi_pgid, n);
+ }
+
+out:
+ free(pids);
+}
+
+void
+job_reap(job_t j)
+{
+ struct rusage ru;
+ int status;
+
+ bool is_system_bootstrapper = ((j->is_bootstrapper && pid1_magic) && !j->mgr->parentmgr);
+
+ job_log(j, LOG_DEBUG, "Reaping");
+
+ if (unlikely(j->weird_bootstrap)) {
+ int64_t junk = 0;
+ job_mig_swap_integer(j, VPROC_GSK_WEIRD_BOOTSTRAP, 0, 0, &junk);
+ }
+
+ if (j->fork_fd) {
+ (void)job_assumes_zero_p(j, runtime_close(j->fork_fd));
+ j->fork_fd = 0;
+ }
+
+ if (j->anonymous) {
+ status = 0;
+ memset(&ru, 0, sizeof(ru));
+ } else {
+ uint64_t rt = runtime_get_nanoseconds_since(j->start_time);
+ j->trt += rt;
+
+ job_log(j, LOG_PERF, "Last instance wall time: %06f", (double)rt / (double)NSEC_PER_SEC);
+ j->nruns++;
+
+ /* The job is dead. While the PID/PGID is still known to be valid, try
+ * to kill abandoned descendant processes.
+ */
+ job_log_stray_pg(j);
+ if (!j->abandon_pg) {
+ if (unlikely(killpg2(j->p, SIGTERM) == -1 && errno != ESRCH)) {
+ job_log(j, LOG_APPLEONLY, "Bug: 5487498");
+ }
+ }
+
+ /* We have to work around one of two kernel bugs here. ptrace(3) may
+ * have abducted the child away from us and reparented it to the tracing
+ * process. If the process then exits, we still get NOTE_EXIT, but we
+ * cannot reap it because the kernel may not have restored the true
+ * parent/child relationship in time.
+ *
+ * See <rdar://problem/5020256>.
+ *
+ * The other bug is if the shutdown monitor has suspended a task and not
+ * resumed it before exiting. In this case, the kernel will not clean up
+ * after the shutdown monitor. It will, instead, leave the task
+ * task suspended and not process any pending signals on the event loop
+ * for the task.
+ *
+ * There are a variety of other kernel bugs that could prevent a process
+ * from exiting, usually having to do with faulty hardware or talking to
+ * misbehaving drivers that mark a thread as uninterruptible and
+ * deadlock/hang before unmarking it as such. So we have to work around
+ * that too.
+ *
+ * See <rdar://problem/9284889&9359725>.
+ */
+ int r = -1;
+ if (j->workaround9359725) {
+ job_log(j, LOG_NOTICE, "Simulated exit: <rdar://problem/9359725>");
+ status = W_EXITCODE(-1, SIGSEGV);
+ memset(&ru, 0, sizeof(ru));
+ } else if ((r = wait4(j->p, &status, 0, &ru)) == -1) {
+ job_log(j, LOG_NOTICE, "Assuming job exited: <rdar://problem/5020256>: %d: %s", errno, strerror(errno));
+ status = W_EXITCODE(-1, SIGSEGV);
+ memset(&ru, 0, sizeof(ru));
+ }
+
+ if (launchd_log_perf && r != -1) {
+ job_log(j, LOG_PERF, "Last instance user time: %ld.%06u", ru.ru_utime.tv_sec, ru.ru_utime.tv_usec);
+ job_log(j, LOG_PERF, "Last instance system time: %ld.%06u", ru.ru_stime.tv_sec, ru.ru_stime.tv_usec);
+ job_log(j, LOG_PERF, "Last instance maximum resident size: %lu", ru.ru_maxrss);
+ job_log(j, LOG_PERF, "Last instance integral shared memory size: %lu", ru.ru_ixrss);
+ job_log(j, LOG_PERF, "Last instance integral unshared data size: %lu", ru.ru_idrss);
+ job_log(j, LOG_PERF, "Last instance integral unshared stack size: %lu", ru.ru_isrss);
+ job_log(j, LOG_PERF, "Last instance page reclaims: %lu", ru.ru_minflt);
+ job_log(j, LOG_PERF, "Last instance page faults: %lu", ru.ru_majflt);
+ job_log(j, LOG_PERF, "Last instance swaps: %lu", ru.ru_nswap);
+ job_log(j, LOG_PERF, "Last instance input ops: %lu", ru.ru_inblock);
+ job_log(j, LOG_PERF, "Last instance output ops: %lu", ru.ru_oublock);
+ job_log(j, LOG_PERF, "Last instance messages sent: %lu", ru.ru_msgsnd);
+ job_log(j, LOG_PERF, "Last instance messages received: %lu", ru.ru_msgrcv);
+ job_log(j, LOG_PERF, "Last instance signals received: %lu", ru.ru_nsignals);
+ job_log(j, LOG_PERF, "Last instance voluntary context switches: %lu", ru.ru_nvcsw);
+ job_log(j, LOG_PERF, "Last instance involuntary context switches: %lu", ru.ru_nivcsw);
+ }
+ }
+
+ if (j->exit_timeout) {
+ (void)kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
+ }
+
+ LIST_REMOVE(j, pid_hash_sle);
+
+ if (j->sent_signal_time) {
+ uint64_t td_sec, td_usec, td = runtime_get_nanoseconds_since(j->sent_signal_time);
+
+ td_sec = td / NSEC_PER_SEC;
+ td_usec = (td % NSEC_PER_SEC) / NSEC_PER_USEC;
+
+ job_log(j, LOG_DEBUG, "Exited %llu.%06llu seconds after the first signal was sent", td_sec, td_usec);
+ }
+
+ timeradd(&ru.ru_utime, &j->ru.ru_utime, &j->ru.ru_utime);
+ timeradd(&ru.ru_stime, &j->ru.ru_stime, &j->ru.ru_stime);
+ if (j->ru.ru_maxrss < ru.ru_maxrss) {
+ j->ru.ru_maxrss = ru.ru_maxrss;
+ }
+
+ j->ru.ru_ixrss += ru.ru_ixrss;
+ j->ru.ru_idrss += ru.ru_idrss;
+ j->ru.ru_isrss += ru.ru_isrss;
+ j->ru.ru_minflt += ru.ru_minflt;
+ j->ru.ru_majflt += ru.ru_majflt;
+ j->ru.ru_nswap += ru.ru_nswap;
+ j->ru.ru_inblock += ru.ru_inblock;
+ j->ru.ru_oublock += ru.ru_oublock;
+ j->ru.ru_msgsnd += ru.ru_msgsnd;
+ j->ru.ru_msgrcv += ru.ru_msgrcv;
+ j->ru.ru_nsignals += ru.ru_nsignals;
+ j->ru.ru_nvcsw += ru.ru_nvcsw;
+ j->ru.ru_nivcsw += ru.ru_nivcsw;
+ job_log_perf_statistics(j);
+
+ int exit_status = WEXITSTATUS(status);
+ if (WIFEXITED(status) && exit_status != 0) {
+ if (!j->did_exec && _launchd_support_system) {
+ xpc_object_t event = NULL;
+ switch (exit_status) {
+ case ENOENT:
+ case ENOTDIR:
+ case ESRCH:
+ job_log(j, LOG_NOTICE, "Job failed to exec(3). Setting up event to tell us when to try again: %d: %s", exit_status, strerror(exit_status));
+ event = xpc_dictionary_create(NULL, NULL, 0);
+ xpc_dictionary_set_string(event, "Executable", j->prog ? j->prog : j->argv[0]);
+ if (j->mach_uid) {
+ xpc_dictionary_set_uint64(event, "UID", j->mach_uid);
+ } else if (j->username) {
+ xpc_dictionary_set_string(event, "UserName", j->username);
+ }
+
+ if (j->groupname) {
+ xpc_dictionary_set_string(event, "GroupName", j->groupname);
+ }
+
+ (void)externalevent_new(j, _launchd_support_system, j->label, event);
+ xpc_release(event);
+
+ j->waiting4ok = true;
+ default:
+ job_log(j, LOG_NOTICE, "Job failed to exec(3) for weird reason: %d", exit_status);
+ }
+ } else {
+ int level = LOG_INFO;
+ if (exit_status != 0) {
+ level = LOG_ERR;
+ }
+
+ job_log(j, level, "Exited with code: %d", exit_status);
+ }
+ }
+
+ if (WIFSIGNALED(status)) {
+ int s = WTERMSIG(status);
+ if ((SIGKILL == s || SIGTERM == s) && !j->stopped) {
+ job_log(j, LOG_NOTICE, "Exited: %s", strsignal(s));
+ } else if (!j->stopped && !j->clean_kill) {
+ switch (s) {
+ // Signals which indicate a crash.
+ case SIGILL:
+ case SIGABRT:
+ case SIGFPE:
+ case SIGBUS:
+ case SIGSEGV:
+ case SIGSYS:
+ /* If the kernel has posted NOTE_EXIT and the signal sent to the process was
+ * SIGTRAP, assume that it's a crash.
+ */
+ case SIGTRAP:
+ j->crashed = true;
+ job_log(j, LOG_WARNING, "Job appears to have crashed: %s", strsignal(s));
+ break;
+ default:
+ job_log(j, LOG_WARNING, "Exited abnormally: %s", strsignal(s));
+ break;
+ }
+
+ if (is_system_bootstrapper && j->crashed) {
+ job_log(j, LOG_ERR | LOG_CONSOLE, "The %s bootstrapper has crashed: %s", j->mgr->name, strsignal(s));
+ }
+ }
+ }
+
+ j->reaped = true;
+
+ struct machservice *msi = NULL;
+ if (j->crashed || !(j->did_exec || j->anonymous)) {
+ SLIST_FOREACH(msi, &j->machservices, sle) {
+ if (j->crashed && !msi->isActive && (msi->drain_one_on_crash || msi->drain_all_on_crash)) {
+ machservice_drain_port(msi);
+ }
+
+ if (!j->did_exec && msi->reset && job_assumes(j, !msi->isActive)) {
+ machservice_resetport(j, msi);
+ }
+ }
+ }
+
+ /* HACK: Essentially duplicating the logic directly above. But this has
+ * gotten really hairy, and I don't want to try consolidating it right now.
+ */
+ if (j->xpc_service && !j->xpcproxy_did_exec) {
+ job_log(j, LOG_ERR, "XPC Service could not exec(3). Resetting port.");
+ SLIST_FOREACH(msi, &j->machservices, sle) {
+ /* Drain the messages but do not reset the port. If xpcproxy could
+ * not exec(3), then we don't want to continue trying, since there
+ * is very likely a serious configuration error with the service.
+ *
+ * The above comment is weird. I originally said we should drain
+ * messages but not reset the port, but that's exactly what we do
+ * below, and I'm not sure which is the mistake, the comment or the
+ * actual behavior.
+ *
+ * Since it's always been this way, I'll assume that the comment is
+ * incorrect, but I'll leave it in place just to remind myself to
+ * actually look into it at some point.
+ *
+ * <rdar://problem/8986802>
+ */
+ if (msi->upfront && job_assumes(j, !msi->isActive)) {
+ machservice_resetport(j, msi);
+ }
+ }
+ }
+
+ struct suspended_peruser *spi = NULL;
+ while ((spi = LIST_FIRST(&j->suspended_perusers))) {
+ job_log(j, LOG_ERR, "Job exited before resuming per-user launchd for UID %u. Will forcibly resume.", spi->j->mach_uid);
+ spi->j->peruser_suspend_count--;
+ if (spi->j->peruser_suspend_count == 0) {
+ job_dispatch(spi->j, false);
+ }
+ LIST_REMOVE(spi, sle);
+ free(spi);
+ }
+
+ j->last_exit_status = status;
+
+ if (j->exit_status_dest) {
+ errno = helper_downcall_wait(j->exit_status_dest, j->last_exit_status);
+ if (errno && errno != MACH_SEND_INVALID_DEST) {
+ (void)job_assumes_zero(j, errno);
+ }
+
+ j->exit_status_dest = MACH_PORT_NULL;
+ }
+
+ if (j->spawn_reply_port) {
+ /* If the child never called exec(3), we must send a spawn() reply so
+ * that the requestor can get exit status from it. If we fail to send
+ * the reply for some reason, we have to deallocate the exit status port
+ * ourselves.
+ */
+ kern_return_t kr = job_mig_spawn2_reply(j->spawn_reply_port, BOOTSTRAP_SUCCESS, j->p, j->exit_status_port);
+ if (kr) {
+ if (kr != MACH_SEND_INVALID_DEST) {
+ (void)job_assumes_zero(j, kr);
+ }
+
+ (void)job_assumes_zero(j, launchd_mport_close_recv(j->exit_status_port));
+ }
+
+ j->exit_status_port = MACH_PORT_NULL;
+ j->spawn_reply_port = MACH_PORT_NULL;
+ }
+
+ if (j->anonymous) {
+ total_anon_children--;
+ if (j->holds_ref) {
+ job_log(j, LOG_PERF, "Anonymous job exited holding reference.");
+ runtime_del_ref();
+ }
+ } else {
+ job_log(j, LOG_PERF, "Job exited.");
+ runtime_del_ref();
+ total_children--;
+ }
+
+ if (j->has_console) {
+ launchd_wsp = 0;
+ }
+
+ if (j->shutdown_monitor) {
+ job_log(j, LOG_NOTICE | LOG_CONSOLE, "Shutdown monitor has exited.");
+ _launchd_shutdown_monitor = NULL;
+ j->shutdown_monitor = false;
+ }
+
+ if (!j->anonymous) {
+ j->mgr->normal_active_cnt--;
+ }
+ j->sent_signal_time = 0;
+ j->sent_sigkill = false;
+ j->clean_kill = false;
+ j->event_monitor_ready2signal = false;
+ j->p = 0;
+}
+
+void
+jobmgr_dispatch_all(jobmgr_t jm, bool newmounthack)
+{
+ jobmgr_t jmi, jmn;
+ job_t ji, jn;
+
+ if (jm->shutting_down) {
+ return;
+ }
+
+ SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) {
+ jobmgr_dispatch_all(jmi, newmounthack);
+ }
+
+ LIST_FOREACH_SAFE(ji, &jm->jobs, sle, jn) {
+ if (newmounthack && ji->start_on_mount) {
+ ji->start_pending = true;
+ }
+
+ job_dispatch(ji, false);
+ }
+}
+
+void
+job_dispatch_curious_jobs(job_t j)
+{
+ job_t ji = NULL, jt = NULL;
+ SLIST_FOREACH_SAFE(ji, &s_curious_jobs, curious_jobs_sle, jt) {
+ struct semaphoreitem *si = NULL;
+ SLIST_FOREACH(si, &ji->semaphores, sle) {
+ if (!(si->why == OTHER_JOB_ENABLED || si->why == OTHER_JOB_DISABLED)) {
+ continue;
+ }
+
+ if (strcmp(si->what, j->label) == 0) {
+ job_log(ji, LOG_DEBUG, "Dispatching out of interest in \"%s\".", j->label);
+
+ if (!ji->removing) {
+ job_dispatch(ji, false);
+ } else {
+ job_log(ji, LOG_NOTICE, "The following job is circularly dependent upon this one: %s", j->label);
+ }
+
+ /* ji could be removed here, so don't do anything with it or its semaphores
+ * after this point.
+ */
+ break;
+ }
+ }
+ }
+}
+
+job_t
+job_dispatch(job_t j, bool kickstart)
+{
+ // Don't dispatch a job if it has no audit session set.
+ if (!uuid_is_null(j->expected_audit_uuid)) {
+ job_log(j, LOG_DEBUG, "Job is still awaiting its audit session UUID. Not dispatching.");
+ return NULL;
+ }
+ if (j->alias) {
+ job_log(j, LOG_DEBUG, "Job is an alias. Not dispatching.");
+ return NULL;
+ }
+
+ if (j->waiting4ok) {
+ job_log(j, LOG_DEBUG, "Job cannot exec(3). Not dispatching.");
+ return NULL;
+ }
+
+#if TARGET_OS_EMBEDDED
+ if (launchd_embedded_handofgod && _launchd_embedded_god) {
+ if (!job_assumes(j, _launchd_embedded_god->username != NULL && j->username != NULL)) {
+ errno = EPERM;
+ return NULL;
+ }
+
+ if (strcmp(j->username, _launchd_embedded_god->username) != 0) {
+ errno = EPERM;
+ return NULL;
+ }
+ } else if (launchd_embedded_handofgod) {
+ errno = EINVAL;
+ return NULL;
+ }
+#endif
+
+ /*
+ * The whole job removal logic needs to be consolidated. The fact that
+ * a job can be removed from just about anywhere makes it easy to have
+ * stale pointers left behind somewhere on the stack that might get
+ * used after the deallocation. In particular, during job iteration.
+ *
+ * This is a classic example. The act of dispatching a job may delete it.
+ */
+ if (!job_active(j)) {
+ if (job_useless(j)) {
+ job_log(j, LOG_DEBUG, "Job is useless. Removing.");
+ job_remove(j);
+ return NULL;
+ }
+ if (unlikely(j->per_user && j->peruser_suspend_count > 0)) {
+ job_log(j, LOG_DEBUG, "Per-user launchd is suspended. Not dispatching.");
+ return NULL;
+ }
+
+ if (kickstart || job_keepalive(j)) {
+ job_log(j, LOG_DEBUG, "%starting job", kickstart ? "Kicks" : "S");
+ job_start(j);
+ } else {
+ job_log(j, LOG_DEBUG, "Watching job.");
+ job_watch(j);
+
+ /*
+ * 5455720
+ *
+ * Path checking and monitoring is really racy right now.
+ * We should clean this up post Leopard.
+ */
+ if (job_keepalive(j)) {
+ job_start(j);
+ }
+ }
+ } else {
+ job_log(j, LOG_DEBUG, "Tried to dispatch an already active job: %s.", job_active(j));
+ }
+
+ return j;
+}
+
+void
+job_kill(job_t j)
+{
+ if (unlikely(!j->p || j->anonymous)) {
+ return;
+ }
+
+ (void)job_assumes_zero_p(j, kill2(j->p, SIGKILL));
+
+ j->sent_sigkill = true;
+ (void)job_assumes_zero_p(j, kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, LAUNCHD_SIGKILL_TIMER, j));
+
+ job_log(j, LOG_DEBUG, "Sent SIGKILL signal");
+}
+
+void
+job_open_shutdown_transaction(job_t j)
+{
+ int rv = proc_set_dirty(j->p, true);
+ if (rv != 0) {
+ job_log(j, LOG_DEBUG, "Job wants to be dirty at shutdown, but it is not Instant Off-compliant. Treating normally.");
+ j->dirty_at_shutdown = false;
+ }
+}
+
+void
+job_close_shutdown_transaction(job_t j)
+{
+ if (j->dirty_at_shutdown) {
+ job_log(j, LOG_DEBUG, "Closing shutdown transaction for job.");
+ (void)job_assumes_zero(j, proc_set_dirty(j->p, false));
+ j->dirty_at_shutdown = false;
+ }
+}
+
+void
+job_log_children_without_exec(job_t j)
+{
+ pid_t *pids = NULL;
+ size_t len = sizeof(pid_t) * get_kern_max_proc();
+ int i = 0, kp_cnt = 0;
+
+ if (!launchd_apple_internal || j->anonymous || j->per_user) {
+ return;
+ }
+
+ if (!job_assumes(j, (pids = malloc(len)) != NULL)) {
+ return;
+ }
+ if (job_assumes_zero_p(j, (kp_cnt = proc_listchildpids(j->p, pids, len))) == -1) {
+ goto out;
+ }
+
+ for (i = 0; i < kp_cnt; i++) {
+ struct proc_bsdshortinfo proc;
+ if (proc_pidinfo(pids[i], PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
+ if (errno != ESRCH) {
+ (void)job_assumes_zero(j, errno);
+ }
+ continue;
+ }
+ if (proc.pbsi_flags & P_EXEC) {
+ continue;
+ }
+
+ job_log(j, LOG_DEBUG, "Called *fork(). Please switch to posix_spawn*(), pthreads or launchd. Child PID %u", pids[i]);
+ }
+
+out:
+ free(pids);
+}
+
+void
+job_cleanup_after_tracer(job_t j)
+{
+ j->tracing_pid = 0;
+ if (j->reap_after_trace) {
+ job_log(j, LOG_DEBUG | LOG_CONSOLE, "Reaping job now that attached tracer is gone.");
+ struct kevent kev;
+ EV_SET(&kev, j->p, 0, 0, NOTE_EXIT, 0, 0);
+
+ // Fake a kevent to keep our logic consistent.
+ job_callback_proc(j, &kev);
+
+ /* Normally, after getting a EVFILT_PROC event, we do garbage collection
+ * on the root job manager. To make our fakery complete, we will do garbage
+ * collection at the beginning of the next run loop cycle (after we're done
+ * draining the current queue of kevents).
+ */
+ (void)job_assumes_zero_p(j, kevent_mod((uintptr_t)&root_jobmgr->reboot_flags, EVFILT_TIMER, EV_ADD | EV_ONESHOT, NOTE_NSECONDS, 1, root_jobmgr));
+ }
+}
+
+void
+job_callback_proc(job_t j, struct kevent *kev)
+{
+ bool program_changed = false;
+ int fflags = kev->fflags;
+
+ job_log(j, LOG_DEBUG, "EVFILT_PROC event for job.");
+ log_kevent_struct(LOG_DEBUG, kev, 0);
+
+ if (fflags & NOTE_EXIT) {
+ if (j->p == (pid_t)kev->ident && !j->anonymous) {
+ /* Note that the third argument to proc_pidinfo() is a magic
+ * argument for PROC_PIDT_SHORTBSDINFO. Specifically, passing 1
+ * means "don't fail on a zombie PID".
+ */
+ struct proc_bsdshortinfo proc;
+ if (job_assumes(j, proc_pidinfo(j->p, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) > 0)) {
+ if (!job_assumes(j, (pid_t)proc.pbsi_ppid == getpid())) {
+ /* Someone has attached to the process with ptrace().
+ * There's a race here. If we determine that we are not the
+ * parent process and then fail to attach a kevent to the
+ * parent PID (who is probably using ptrace()), we can take
+ * that as an indication that the parent exited between
+ * sysctl(3) and kevent_mod(). The reparenting of the PID
+ * should be atomic to us, so in that case, we reap the job
+ * as normal.
+ *
+ * Otherwise, we wait for the death of the parent tracer and
+ * then reap, just as we would if a job died while we were
+ * sampling it at shutdown.
+ *
+ * Note that we foolishly assume that in the process *tree*
+ * a node cannot be its own parent. Apparently, that is not
+ * correct. If this is the case, we forsake the process to
+ * its own devices. Let it reap itself.
+ */
+ if (!job_assumes(j, proc.pbsi_ppid != kev->ident)) {
+ job_log(j, LOG_WARNING, "Job is its own parent and has (somehow) exited. Leaving it to waste away.");
+ return;
+ }
+ if (job_assumes_zero_p(j, kevent_mod(proc.pbsi_ppid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, j)) != -1) {
+ j->tracing_pid = proc.pbsi_ppid;
+ j->reap_after_trace = true;
+ return;
+ }
+ }
+ }
+ } else if (!j->anonymous) {
+ if (j->tracing_pid == (pid_t)kev->ident) {
+ job_cleanup_after_tracer(j);
+
+ return;
+ } else if (j->tracing_pid && !j->reap_after_trace) {
+ // The job exited before our sample completed.
+ job_log(j, LOG_DEBUG | LOG_CONSOLE, "Job has exited. Will reap after tracing PID %i exits.", j->tracing_pid);
+ j->reap_after_trace = true;
+ return;
+ }
+ }
+ }
+
+ if (fflags & NOTE_EXEC) {
+ program_changed = true;
+
+ if (j->anonymous) {
+ struct proc_bsdshortinfo proc;
+ if (proc_pidinfo(j->p, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) > 0) {
+ char newlabel[1000];
+
+ snprintf(newlabel, sizeof(newlabel), "%p.anonymous.%s", j, proc.pbsi_comm);
+
+ job_log(j, LOG_INFO, "Program changed. Updating the label to: %s", newlabel);
+
+ LIST_REMOVE(j, label_hash_sle);
+ strcpy((char *)j->label, newlabel);
+
+ jobmgr_t where2put = root_jobmgr;
+ if (j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) {
+ where2put = j->mgr;
+ }
+ LIST_INSERT_HEAD(&where2put->label_hash[hash_label(j->label)], j, label_hash_sle);
+ } else if (errno != ESRCH) {
+ (void)job_assumes_zero(j, errno);
+ }
+ } else {
+ if (j->spawn_reply_port) {
+ errno = job_mig_spawn2_reply(j->spawn_reply_port, BOOTSTRAP_SUCCESS, j->p, j->exit_status_port);
+ if (errno) {
+ if (errno != MACH_SEND_INVALID_DEST) {
+ (void)job_assumes_zero(j, errno);
+ }
+ (void)job_assumes_zero(j, launchd_mport_close_recv(j->exit_status_port));
+ }
+
+ j->spawn_reply_port = MACH_PORT_NULL;
+ j->exit_status_port = MACH_PORT_NULL;
+ }
+
+ if (j->xpc_service && j->did_exec) {
+ j->xpcproxy_did_exec = true;
+ }
+
+ j->did_exec = true;
+ job_log(j, LOG_DEBUG, "Program changed");
+ }
+ }
+
+ if (fflags & NOTE_FORK) {
+ job_log(j, LOG_DEBUG, "fork()ed%s", program_changed ? ". For this message only: We don't know whether this event happened before or after execve()." : "");
+ job_log_children_without_exec(j);
+ }
+
+ if (fflags & NOTE_EXIT) {
+ job_reap(j);
+
+ if (j->anonymous) {
+ job_remove(j);
+ j = NULL;
+ } else {
+ (void)job_dispatch(j, false);
+ }
+ }
+}
+
+void
+job_callback_timer(job_t j, void *ident)
+{
+ if (j == ident) {
+ job_log(j, LOG_DEBUG, "j == ident (%p)", ident);
+ job_dispatch(j, true);
+ } else if (&j->semaphores == ident) {
+ job_log(j, LOG_DEBUG, "&j->semaphores == ident (%p)", ident);
+ job_dispatch(j, false);
+ } else if (&j->start_interval == ident) {
+ job_log(j, LOG_DEBUG, "&j->start_interval == ident (%p)", ident);
+ j->start_pending = true;
+ job_dispatch(j, false);
+ } else if (&j->exit_timeout == ident) {
+ if (!job_assumes(j, j->p != 0)) {
+ return;
+ }
+
+ if (j->sent_sigkill) {
+ uint64_t td = runtime_get_nanoseconds_since(j->sent_signal_time);
+
+ td /= NSEC_PER_SEC;
+ td -= j->clean_kill ? 0 : j->exit_timeout;
+
+ job_log(j, LOG_WARNING | LOG_CONSOLE, "Job has not died after being %skilled %llu seconds ago. Simulating exit.", j->clean_kill ? "cleanly " : "", td);
+ j->workaround9359725 = true;
+
+ if (launchd_trap_sigkill_bugs) {
+ job_log(j, LOG_NOTICE | LOG_CONSOLE, "Trapping into kernel debugger. You can continue the machine after it has been debugged, and shutdown will proceed normally.");
+ (void)job_assumes_zero(j, host_reboot(mach_host_self(), HOST_REBOOT_DEBUGGER));
+ }
+
+ /* We've simulated the exit, so we have to cancel the kevent for
+ * this job, otherwise we may get a kevent later down the road that
+ * has a stale context pointer (if we've removed the job). Or worse,
+ * it'll corrupt our data structures if the job still exists or the
+ * allocation was recycled.
+ *
+ * If the failing process had a tracer attached to it, we need to
+ * remove out NOTE_EXIT for that tracer too, otherwise the same
+ * thing might happen.
+ *
+ * Note that, if we're not shutting down, this will result in a
+ * zombie process just hanging around forever. But if the process
+ * didn't exit after receiving SIGKILL, odds are it would've just
+ * stuck around forever anyway.
+ *
+ * See <rdar://problem/9481630>.
+ */
+ (void)kevent_mod((uintptr_t)j->p, EVFILT_PROC, EV_DELETE, 0, 0, NULL);
+ if (j->tracing_pid) {
+ (void)kevent_mod((uintptr_t)j->tracing_pid, EVFILT_PROC, EV_DELETE, 0, 0, NULL);
+ }
+
+ struct kevent bogus_exit;
+ EV_SET(&bogus_exit, j->p, EVFILT_PROC, 0, NOTE_EXIT, 0, 0);
+ jobmgr_callback(j->mgr, &bogus_exit);
+ } else {
+ if (unlikely(j->debug_before_kill)) {
+ job_log(j, LOG_NOTICE, "Exit timeout elapsed. Entering the kernel debugger");
+ (void)job_assumes_zero(j, host_reboot(mach_host_self(), HOST_REBOOT_DEBUGGER));
+ }
+
+ job_log(j, LOG_WARNING | LOG_CONSOLE, "Exit timeout elapsed (%u seconds). Killing", j->exit_timeout);
+ job_kill(j);
+ }
+ } else {
+ job_log(j, LOG_ERR, "Unrecognized job timer callback: %p", ident);
+ }
+}
+
+void
+job_callback_read(job_t j, int ident)
+{
+ if (ident == j->stdin_fd) {
+ job_dispatch(j, true);
+ } else {
+ socketgroup_callback(j);
+ }
+}
+
+void
+jobmgr_reap_bulk(jobmgr_t jm, struct kevent *kev)
+{
+ jobmgr_t jmi;
+ job_t j;
+
+ SLIST_FOREACH(jmi, &jm->submgrs, sle) {
+ jobmgr_reap_bulk(jmi, kev);
+ }
+
+ if ((j = jobmgr_find_by_pid(jm, (pid_t)kev->ident, false))) {
+ kev->udata = j;
+ job_callback(j, kev);
+ }
+}
+
+void
+jobmgr_callback(void *obj, struct kevent *kev)
+{
+ jobmgr_t jm = obj;
+
+#if TARGET_OS_EMBEDDED
+ int flag2check = VQ_MOUNT;
+#else
+ int flag2check = VQ_UPDATE;
+#endif
+
+ switch (kev->filter) {
+ case EVFILT_PROC:
+ jobmgr_reap_bulk(jm, kev);
+ root_jobmgr = jobmgr_do_garbage_collection(root_jobmgr);
+ break;
+ case EVFILT_SIGNAL:
+ switch (kev->ident) {
+ case SIGTERM:
+ jobmgr_log(jm, LOG_DEBUG, "Got SIGTERM. Shutting down.");
+ return launchd_shutdown();
+ case SIGUSR1:
+ return calendarinterval_callback();
+ case SIGUSR2:
+ // Turn on all logging.
+ launchd_log_perf = true;
+ launchd_log_debug = true;
+ launchd_log_shutdown = true;
+ /* Hopefully /var is available by this point. If not, uh, oh well.
+ * It's just a debugging facility.
+ */
+ return jobmgr_log_perf_statistics(jm);
+ default:
+ jobmgr_log(jm, LOG_ERR, "Unrecognized signal: %lu: %s", kev->ident, strsignal(kev->ident));
+ }
+ break;
+ case EVFILT_FS:
+ if (kev->fflags & flag2check) {
+ if (!launchd_var_available) {
+ struct stat sb;
+ if (stat("/var/log", &sb) == 0 && (sb.st_mode & S_IWUSR)) {
+ launchd_var_available = true;
+ }
+ }
+ } else if (kev->fflags & VQ_MOUNT) {
+ jobmgr_dispatch_all(jm, true);
+ }
+ jobmgr_dispatch_all_semaphores(jm);
+ break;
+ case EVFILT_TIMER:
+ if (kev->ident == (uintptr_t)&sorted_calendar_events) {
+ calendarinterval_callback();
+ } else if (kev->ident == (uintptr_t)jm) {
+ jobmgr_log(jm, LOG_DEBUG, "Shutdown timer firing.");
+ jobmgr_still_alive_with_check(jm);
+ } else if (kev->ident == (uintptr_t)&jm->reboot_flags) {
+ jobmgr_do_garbage_collection(jm);
+ } else if (kev->ident == (uintptr_t)&launchd_runtime_busy_time) {
+ jobmgr_log(jm, LOG_DEBUG, "Idle exit timer fired. Shutting down.");
+ if (jobmgr_assumes_zero(jm, runtime_busy_cnt) == 0) {
+ return launchd_shutdown();
+ }
+ }
+ break;
+ case EVFILT_VNODE:
+ if (kev->ident == (uintptr_t)s_no_hang_fd) {
+ int _no_hang_fd = open("/dev/autofs_nowait", O_EVTONLY | O_NONBLOCK);
+ if (unlikely(_no_hang_fd != -1)) {
+ jobmgr_log(root_jobmgr, LOG_DEBUG, "/dev/autofs_nowait has appeared!");
+ (void)jobmgr_assumes_zero_p(root_jobmgr, kevent_mod((uintptr_t)s_no_hang_fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL));
+ (void)jobmgr_assumes_zero_p(root_jobmgr, runtime_close(s_no_hang_fd));
+ s_no_hang_fd = _fd(_no_hang_fd);
+ }
+ } else if (pid1_magic && launchd_console && kev->ident == (uintptr_t)fileno(launchd_console)) {
+ int cfd = -1;
+ if (jobmgr_assumes_zero_p(jm, cfd = open(_PATH_CONSOLE, O_WRONLY | O_NOCTTY)) != -1) {
+ _fd(cfd);
+ if (!(launchd_console = fdopen(cfd, "w"))) {
+ (void)jobmgr_assumes_zero(jm, errno);
+ (void)close(cfd);
+ }
+ }
+ }
+ break;
+ default:
+ jobmgr_log(jm, LOG_ERR, "Unrecognized kevent filter: %hd", kev->filter);
+ }
+}
+
+void
+job_callback(void *obj, struct kevent *kev)
+{
+ job_t j = obj;
+
+ job_log(j, LOG_DEBUG, "Dispatching kevent callback.");
+
+ switch (kev->filter) {
+ case EVFILT_PROC:
+ return job_callback_proc(j, kev);
+ case EVFILT_TIMER:
+ return job_callback_timer(j, (void *) kev->ident);
+ case EVFILT_READ:
+ return job_callback_read(j, (int) kev->ident);
+ case EVFILT_MACHPORT:
+ return (void)job_dispatch(j, true);
+ default:
+ job_log(j, LOG_ERR, "Unrecognized job callback filter: %hd", kev->filter);
+ }
+}
+
+void
+job_start(job_t j)
+{
+ uint64_t td;
+ int spair[2];
+ int execspair[2];
+ char nbuf[64];
+ pid_t c;
+ bool sipc = false;
+ u_int proc_fflags = NOTE_EXIT|NOTE_FORK|NOTE_EXEC;
+
+ if (!job_assumes(j, j->mgr != NULL)) {
+ return;
+ }
+
+ if (unlikely(job_active(j))) {
+ job_log(j, LOG_DEBUG, "Already started");
+ return;
+ }
+
+ /*
+ * Some users adjust the wall-clock and then expect software to not notice.
+ * Therefore, launchd must use an absolute clock instead of the wall clock
+ * wherever possible.
+ */
+ td = runtime_get_nanoseconds_since(j->start_time);
+ td /= NSEC_PER_SEC;
+
+ if (j->start_time && (td < j->min_run_time) && !j->legacy_mach_job && !j->inetcompat) {
+ time_t respawn_delta = j->min_run_time - (uint32_t)td;
+
+ /* We technically should ref-count throttled jobs to prevent idle exit,
+ * but we're not directly tracking the 'throttled' state at the moment.
+ */
+ job_log(j, LOG_NOTICE, "Throttling respawn: Will start in %ld seconds", respawn_delta);
+ (void)job_assumes_zero_p(j, kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, respawn_delta, j));
+ job_ignore(j);
+ return;
+ }
+
+ if (likely(!j->legacy_mach_job)) {
+ sipc = ((!SLIST_EMPTY(&j->sockets) || !SLIST_EMPTY(&j->machservices)) && !j->deny_job_creation) || j->embedded_god;
+ }
+
+ if (sipc) {
+ (void)job_assumes_zero_p(j, socketpair(AF_UNIX, SOCK_STREAM, 0, spair));
+ }
+
+ (void)job_assumes_zero_p(j, socketpair(AF_UNIX, SOCK_STREAM, 0, execspair));
+
+ switch (c = runtime_fork(j->weird_bootstrap ? j->j_port : j->mgr->jm_port)) {
+ case -1:
+ job_log_error(j, LOG_ERR, "fork() failed, will try again in one second");
+ (void)job_assumes_zero_p(j, kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, 1, j));
+ job_ignore(j);
+
+ (void)job_assumes_zero(j, runtime_close(execspair[0]));
+ (void)job_assumes_zero(j, runtime_close(execspair[1]));
+ if (sipc) {
+ (void)job_assumes_zero(j, runtime_close(spair[0]));
+ (void)job_assumes_zero(j, runtime_close(spair[1]));
+ }
+ break;
+ case 0:
+ if (unlikely(_vproc_post_fork_ping())) {
+ _exit(EXIT_FAILURE);
+ }
+
+ (void)job_assumes_zero(j, runtime_close(execspair[0]));
+ // wait for our parent to say they've attached a kevent to us
+ read(_fd(execspair[1]), &c, sizeof(c));
+
+ if (sipc) {
+ (void)job_assumes_zero(j, runtime_close(spair[0]));
+ snprintf(nbuf, sizeof(nbuf), "%d", spair[1]);
+ setenv(LAUNCHD_TRUSTED_FD_ENV, nbuf, 1);
+ }
+ job_start_child(j);
+ break;
+ default:
+ j->start_time = runtime_get_opaque_time();
+
+ job_log(j, LOG_DEBUG, "Started as PID: %u", c);
+
+ j->did_exec = false;
+ j->xpcproxy_did_exec = false;
+ j->checkedin = false;
+ j->start_pending = false;
+ j->reaped = false;
+ j->crashed = false;
+ j->stopped = false;
+ j->workaround9359725 = false;
+ if (j->needs_kickoff) {
+ j->needs_kickoff = false;
+
+ if (SLIST_EMPTY(&j->semaphores)) {
+ j->ondemand = false;
+ }
+ }
+
+ if (j->has_console) {
+ launchd_wsp = c;
+ }
+
+ job_log(j, LOG_PERF, "Job started.");
+ runtime_add_ref();
+ total_children++;
+ LIST_INSERT_HEAD(&j->mgr->active_jobs[ACTIVE_JOB_HASH(c)], j, pid_hash_sle);
+ j->p = c;
+
+ j->mgr->normal_active_cnt++;
+ j->fork_fd = _fd(execspair[0]);
+ (void)job_assumes_zero(j, runtime_close(execspair[1]));
+ if (sipc) {
+ (void)job_assumes_zero(j, runtime_close(spair[1]));
+ ipc_open(_fd(spair[0]), j);
+ }
+ if (kevent_mod(c, EVFILT_PROC, EV_ADD, proc_fflags, 0, root_jobmgr ? root_jobmgr : j->mgr) != -1) {
+ job_ignore(j);
+ } else {
+ if (errno == ESRCH) {
+ job_log(j, LOG_ERR, "Child was killed before we could attach a kevent.");
+ } else {
+ (void)job_assumes(j, errno == ESRCH);
+ }
+ job_reap(j);
+
+ /* If we have reaped this job within this same run loop pass, then
+ * it will be currently ignored. So if there's a failure to attach a
+ * kevent, we need to make sure that we watch the job so that we can
+ * respawn it.
+ *
+ * See <rdar://problem/10140809>.
+ */
+ job_watch(j);
+ }
+
+ j->wait4debugger_oneshot = false;
+ if (likely(!j->stall_before_exec)) {
+ job_uncork_fork(j);
+ }
+ break;
+ }
+}
+
+void
+job_start_child(job_t j)
+{
+ typeof(posix_spawn) *psf;
+ const char *file2exec = "/usr/libexec/launchproxy";
+ const char **argv;
+ posix_spawnattr_t spattr;
+ int gflags = GLOB_NOSORT|GLOB_NOCHECK|GLOB_TILDE|GLOB_DOOFFS;
+ glob_t g;
+ short spflags = POSIX_SPAWN_SETEXEC;
+ size_t binpref_out_cnt = 0;
+ size_t i;
+
+ (void)job_assumes_zero(j, posix_spawnattr_init(&spattr));
+
+ job_setup_attributes(j);
+
+ if (unlikely(j->argv && j->globargv)) {
+ g.gl_offs = 1;
+ for (i = 0; i < j->argc; i++) {
+ if (i > 0) {
+ gflags |= GLOB_APPEND;
+ }
+ if (glob(j->argv[i], gflags, NULL, &g) != 0) {
+ job_log_error(j, LOG_ERR, "glob(\"%s\")", j->argv[i]);
+ exit(EXIT_FAILURE);
+ }
+ }
+ g.gl_pathv[0] = (char *)file2exec;
+ argv = (const char **)g.gl_pathv;
+ } else if (likely(j->argv)) {
+ argv = alloca((j->argc + 2) * sizeof(char *));
+ argv[0] = file2exec;
+ for (i = 0; i < j->argc; i++) {
+ argv[i + 1] = j->argv[i];
+ }
+ argv[i + 1] = NULL;
+ } else {
+ argv = alloca(3 * sizeof(char *));
+ argv[0] = file2exec;
+ argv[1] = j->prog;
+ argv[2] = NULL;
+ }
+
+ if (likely(!j->inetcompat)) {
+ argv++;
+ }
+
+ if (unlikely(j->wait4debugger || j->wait4debugger_oneshot)) {
+ if (!j->legacy_LS_job) {
+ job_log(j, LOG_WARNING, "Spawned and waiting for the debugger to attach before continuing...");
+ }
+ spflags |= POSIX_SPAWN_START_SUSPENDED;
+ }
+
+#if !TARGET_OS_EMBEDDED
+ if (unlikely(j->disable_aslr)) {
+ spflags |= _POSIX_SPAWN_DISABLE_ASLR;
+ }
+#endif
+ spflags |= j->pstype;
+
+ (void)job_assumes_zero(j, posix_spawnattr_setflags(&spattr, spflags));
+ if (unlikely(j->j_binpref_cnt)) {
+ (void)job_assumes_zero(j, posix_spawnattr_setbinpref_np(&spattr, j->j_binpref_cnt, j->j_binpref, &binpref_out_cnt));
+ (void)job_assumes(j, binpref_out_cnt == j->j_binpref_cnt);
+ }
+
+#if TARGET_OS_EMBEDDED
+ /* Set jetsam attributes. POSIX_SPAWN_JETSAM_USE_EFFECTIVE_PRIORITY guards
+ * against a race which arises if, during spawn, an initial jetsam property
+ * update occurs before the values below are applied. In this case, the flag
+ * ensures that the subsequent change is ignored; the explicit update should
+ * be given priority.
+ */
+ short flags = 0;
+ if (j->jetsam_properties) {
+ flags = POSIX_SPAWN_JETSAM_USE_EFFECTIVE_PRIORITY;
+ }
+
+ (void)job_assumes_zero(j, posix_spawnattr_setjetsam(&spattr, flags, j->jetsam_priority, j->jetsam_memlimit));
+#endif
+
+ if (!j->app) {
+ (void)job_assumes_zero(j, posix_spawnattr_setcpumonitor(&spattr, 85, 5 * 60));
+ }
+
+#if HAVE_QUARANTINE
+ if (j->quarantine_data) {
+ qtn_proc_t qp;
+
+ if (job_assumes(j, qp = qtn_proc_alloc())) {
+ if (job_assumes_zero(j, qtn_proc_init_with_data(qp, j->quarantine_data, j->quarantine_data_sz) == 0)) {
+ (void)job_assumes_zero(j, qtn_proc_apply_to_self(qp));
+ }
+ }
+ }
+#endif
+
+#if HAVE_SANDBOX
+ if (j->seatbelt_profile) {
+ char *seatbelt_err_buf = NULL;
+
+ if (job_assumes_zero_p(j, sandbox_init(j->seatbelt_profile, j->seatbelt_flags, &seatbelt_err_buf)) == -1) {
+ if (seatbelt_err_buf) {
+ job_log(j, LOG_ERR, "Sandbox failed to init: %s", seatbelt_err_buf);
+ }
+ goto out_bad;
+ }
+ }
+#endif
+
+ psf = j->prog ? posix_spawn : posix_spawnp;
+
+ if (likely(!j->inetcompat)) {
+ file2exec = j->prog ? j->prog : argv[0];
+ }
+
+ errno = psf(NULL, file2exec, NULL, &spattr, (char *const *)argv, environ);
+
+#if HAVE_SANDBOX
+out_bad:
+#endif
+ _exit(errno);
+}
+
+void
+jobmgr_export_env_from_other_jobs(jobmgr_t jm, launch_data_t dict)
+{
+ launch_data_t tmp;
+ struct envitem *ei;
+ job_t ji;
+
+ if (jm->parentmgr) {
+ jobmgr_export_env_from_other_jobs(jm->parentmgr, dict);
+ } else {
+ char **tmpenviron = environ;
+ for (; *tmpenviron; tmpenviron++) {
+ char envkey[1024];
+ launch_data_t s = launch_data_alloc(LAUNCH_DATA_STRING);
+ launch_data_set_string(s, strchr(*tmpenviron, '=') + 1);
+ strncpy(envkey, *tmpenviron, sizeof(envkey));
+ *(strchr(envkey, '=')) = '\0';
+ launch_data_dict_insert(dict, s, envkey);
+ }
+ }
+
+ LIST_FOREACH(ji, &jm->jobs, sle) {
+ SLIST_FOREACH(ei, &ji->global_env, sle) {
+ if ((tmp = launch_data_new_string(ei->value))) {
+ launch_data_dict_insert(dict, tmp, ei->key);
+ }
+ }
+ }
+}
+
+void
+jobmgr_setup_env_from_other_jobs(jobmgr_t jm)
+{
+ struct envitem *ei;
+ job_t ji;
+
+ if (jm->parentmgr) {
+ jobmgr_setup_env_from_other_jobs(jm->parentmgr);
+ }
+
+ LIST_FOREACH(ji, &jm->global_env_jobs, global_env_sle) {
+ SLIST_FOREACH(ei, &ji->global_env, sle) {
+ setenv(ei->key, ei->value, 1);
+ }
+ }
+}
+
+void
+job_log_pids_with_weird_uids(job_t j)
+{
+ size_t len = sizeof(pid_t) * get_kern_max_proc();
+ pid_t *pids = NULL;
+ uid_t u = j->mach_uid;
+ int i = 0, kp_cnt = 0;
+
+ if (!launchd_apple_internal) {
+ return;
+ }
+
+ pids = malloc(len);
+ if (!job_assumes(j, pids != NULL)) {
+ return;
+ }
+
+ runtime_ktrace(RTKT_LAUNCHD_FINDING_WEIRD_UIDS, j->p, u, 0);
+
+ /* libproc actually has some serious performance drawbacks when used over sysctl(3) in
+ * scenarios like this. Whereas sysctl(3) can give us back all the kinfo_proc's in
+ * one kernel call, libproc requires that we get a list of PIDs we're interested in
+ * (in this case, all PIDs on the system) and then get a single proc_bsdshortinfo
+ * struct back in a single call for each one.
+ *
+ * This kind of thing is also more inherently racy than sysctl(3). While sysctl(3)
+ * returns a snapshot, it returns the whole shebang at once. Any PIDs given to us by
+ * libproc could go stale before we call proc_pidinfo().
+ *
+ * Note that proc_list*() APIs return the number of PIDs given back, not the number
+ * of bytes written to the buffer.
+ */
+ if (job_assumes_zero_p(j, (kp_cnt = proc_listallpids(pids, len))) == -1) {
+ goto out;
+ }
+
+ for (i = 0; i < kp_cnt; i++) {
+ struct proc_bsdshortinfo proc;
+ /* We perhaps should not log a bug here if we get ESRCH back, due to the race
+ * detailed above.
+ */
+ if (proc_pidinfo(pids[i], PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
+ if (errno != ESRCH) {
+ (void)job_assumes_zero(j, errno);
+ }
+ continue;
+ }
+
+ uid_t i_euid = proc.pbsi_uid;
+ uid_t i_uid = proc.pbsi_ruid;
+ uid_t i_svuid = proc.pbsi_svuid;
+ pid_t i_pid = pids[i];
+
+ if (i_euid != u && i_uid != u && i_svuid != u) {
+ continue;
+ }
+
+ job_log(j, LOG_ERR, "PID %u \"%s\" has no account to back it! Real/effective/saved UIDs: %u/%u/%u", i_pid, proc.pbsi_comm, i_uid, i_euid, i_svuid);
+
+// Temporarily disabled due to 5423935 and 4946119.
+#if 0
+ // Ask the accountless process to exit.
+ (void)job_assumes_zero_p(j, kill2(i_pid, SIGTERM));
+#endif
+ }
+
+out:
+ free(pids);
+}
+
+static struct passwd *
+job_getpwnam(job_t j, const char *name)
+{
+ /*
+ * methodology for system daemons
+ *
+ * first lookup user record without any opendirectoryd interaction,
+ * we don't know what interprocess dependencies might be in flight.
+ * if that fails, we re-enable opendirectoryd interaction and
+ * re-issue the lookup. We have to disable the libinfo L1 cache
+ * otherwise libinfo will return the negative cache entry on the retry
+ */
+#if !TARGET_OS_EMBEDDED
+ struct passwd *pw = NULL;
+
+ if (pid1_magic && j->mgr == root_jobmgr) {
+ // 1 == SEARCH_MODULE_FLAG_DISABLED
+ si_search_module_set_flags("ds", 1);
+ gL1CacheEnabled = false;
+
+ pw = getpwnam(name);
+ si_search_module_set_flags("ds", 0);
+ }
+
+ if (pw == NULL) {
+ pw = getpwnam(name);
+ }
+
+ return pw;
+#else
+#pragma unused (j)
+ return getpwnam(name);
+#endif
+}
+
+static struct group *
+job_getgrnam(job_t j, const char *name)
+{
+#if !TARGET_OS_EMBEDDED
+ struct group *gr = NULL;
+
+ if (pid1_magic && j->mgr == root_jobmgr) {
+ si_search_module_set_flags("ds", 1);
+ gL1CacheEnabled = false;
+
+ gr = getgrnam(name);
+
+ si_search_module_set_flags("ds", 0);
+ }
+
+ if (gr == NULL) {
+ gr = getgrnam(name);
+ }
+
+ return gr;
+#else
+#pragma unused (j)
+ return getgrnam(name);
+#endif
+}
+
+void
+job_postfork_test_user(job_t j)
+{
+ // This function is all about 5201578
+
+ const char *home_env_var = getenv("HOME");
+ const char *user_env_var = getenv("USER");
+ const char *logname_env_var = getenv("LOGNAME");
+ uid_t tmp_uid, local_uid = getuid();
+ gid_t tmp_gid, local_gid = getgid();
+ char shellpath[PATH_MAX];
+ char homedir[PATH_MAX];
+ char loginname[2000];
+ struct passwd *pwe;
+
+
+ if (!job_assumes(j, home_env_var && user_env_var && logname_env_var
+ && strcmp(user_env_var, logname_env_var) == 0)) {
+ goto out_bad;
+ }
+
+ if ((pwe = job_getpwnam(j, user_env_var)) == NULL) {
+ job_log(j, LOG_ERR, "The account \"%s\" has been deleted out from under us!", user_env_var);
+ goto out_bad;
+ }
+
+ /*
+ * We must copy the results of getpw*().
+ *
+ * Why? Because subsequent API calls may call getpw*() as a part of
+ * their implementation. Since getpw*() returns a [now thread scoped]
+ * global, we must therefore cache the results before continuing.
+ */
+
+ tmp_uid = pwe->pw_uid;
+ tmp_gid = pwe->pw_gid;
+
+ strlcpy(shellpath, pwe->pw_shell, sizeof(shellpath));
+ strlcpy(loginname, pwe->pw_name, sizeof(loginname));
+ strlcpy(homedir, pwe->pw_dir, sizeof(homedir));
+
+ if (strcmp(loginname, logname_env_var) != 0) {
+ job_log(j, LOG_ERR, "The %s environmental variable changed out from under us!", "USER");
+ goto out_bad;
+ }
+ if (strcmp(homedir, home_env_var) != 0) {
+ job_log(j, LOG_ERR, "The %s environmental variable changed out from under us!", "HOME");
+ goto out_bad;
+ }
+ if (local_uid != tmp_uid) {
+ job_log(j, LOG_ERR, "The %cID of the account (%u) changed out from under us (%u)!",
+ 'U', tmp_uid, local_uid);
+ goto out_bad;
+ }
+ if (local_gid != tmp_gid) {
+ job_log(j, LOG_ERR, "The %cID of the account (%u) changed out from under us (%u)!",
+ 'G', tmp_gid, local_gid);
+ goto out_bad;
+ }
+
+ return;
+out_bad:
+#if 0
+ (void)job_assumes_zero_p(j, kill2(getppid(), SIGTERM));
+ _exit(EXIT_FAILURE);
+#else
+ job_log(j, LOG_WARNING, "In a future build of the OS, this error will be fatal.");
+#endif
+}
+
+void
+job_postfork_become_user(job_t j)
+{
+ char loginname[2000];
+ char tmpdirpath[PATH_MAX];
+ char shellpath[PATH_MAX];
+ char homedir[PATH_MAX];
+ struct passwd *pwe;
+ size_t r;
+ gid_t desired_gid = -1;
+ uid_t desired_uid = -1;
+
+ if (getuid() != 0) {
+ return job_postfork_test_user(j);
+ }
+
+ /*
+ * I contend that having UID == 0 and GID != 0 is of dubious value.
+ * Nevertheless, this used to work in Tiger. See: 5425348
+ */
+ if (j->groupname && !j->username) {
+ j->username = "root";
+ }
+
+ if (j->username) {
+ if ((pwe = job_getpwnam(j, j->username)) == NULL) {
+ job_log(j, LOG_ERR, "getpwnam(\"%s\") failed", j->username);
+ _exit(ESRCH);
+ }
+ } else if (j->mach_uid) {
+ if ((pwe = getpwuid(j->mach_uid)) == NULL) {
+ job_log(j, LOG_ERR, "getpwuid(\"%u\") failed", j->mach_uid);
+ job_log_pids_with_weird_uids(j);
+ _exit(ESRCH);
+ }
+ } else {
+ return;
+ }
+
+ /*
+ * We must copy the results of getpw*().
+ *
+ * Why? Because subsequent API calls may call getpw*() as a part of
+ * their implementation. Since getpw*() returns a [now thread scoped]
+ * global, we must therefore cache the results before continuing.
+ */
+
+ desired_uid = pwe->pw_uid;
+ desired_gid = pwe->pw_gid;
+
+ strlcpy(shellpath, pwe->pw_shell, sizeof(shellpath));
+ strlcpy(loginname, pwe->pw_name, sizeof(loginname));
+ strlcpy(homedir, pwe->pw_dir, sizeof(homedir));
+
+ if (unlikely(pwe->pw_expire && time(NULL) >= pwe->pw_expire)) {
+ job_log(j, LOG_ERR, "Expired account");
+ _exit(EXIT_FAILURE);
+ }
+
+
+ if (unlikely(j->username && strcmp(j->username, loginname) != 0)) {
+ job_log(j, LOG_WARNING, "Suspicious setup: User \"%s\" maps to user: %s", j->username, loginname);
+ } else if (unlikely(j->mach_uid && (j->mach_uid != desired_uid))) {
+ job_log(j, LOG_WARNING, "Suspicious setup: UID %u maps to UID %u", j->mach_uid, desired_uid);
+ }
+
+ if (j->groupname) {
+ struct group *gre;
+
+ if (unlikely((gre = job_getgrnam(j, j->groupname)) == NULL)) {
+ job_log(j, LOG_ERR, "getgrnam(\"%s\") failed", j->groupname);
+ _exit(ESRCH);
+ }
+
+ desired_gid = gre->gr_gid;
+ }
+
+ if (job_assumes_zero_p(j, setlogin(loginname)) == -1) {
+ _exit(EXIT_FAILURE);
+ }
+
+ if (job_assumes_zero_p(j, setgid(desired_gid)) == -1) {
+ _exit(EXIT_FAILURE);
+ }
+
+ /*
+ * The kernel team and the DirectoryServices team want initgroups()
+ * called after setgid(). See 4616864 for more information.
+ */
+
+ if (likely(!j->no_init_groups)) {
+#if 1
+ if (job_assumes_zero_p(j, initgroups(loginname, desired_gid)) == -1) {
+ _exit(EXIT_FAILURE);
+ }
+#else
+ /* Do our own little initgroups(). We do this to guarantee that we're
+ * always opted into dynamic group resolution in the kernel. initgroups(3)
+ * does not make this guarantee.
+ */
+ int groups[NGROUPS], ngroups;
+
+ // A failure here isn't fatal, and we'll still get data we can use.
+ (void)job_assumes_zero_p(j, getgrouplist(j->username, desired_gid, groups, &ngroups));
+
+ if (job_assumes_zero_p(j, syscall(SYS_initgroups, ngroups, groups, desired_uid)) == -1) {
+ _exit(EXIT_FAILURE);
+ }
+#endif
+ }
+
+ if (job_assumes_zero_p(j, setuid(desired_uid)) == -1) {
+ _exit(EXIT_FAILURE);
+ }
+
+ r = confstr(_CS_DARWIN_USER_TEMP_DIR, tmpdirpath, sizeof(tmpdirpath));
+
+ if (likely(r > 0 && r < sizeof(tmpdirpath))) {
+ setenv("TMPDIR", tmpdirpath, 0);
+ }
+
+ setenv("SHELL", shellpath, 0);
+ setenv("HOME", homedir, 0);
+ setenv("USER", loginname, 0);
+ setenv("LOGNAME", loginname, 0);
+}
+
+void
+job_setup_attributes(job_t j)
+{
+ struct limititem *li;
+ struct envitem *ei;
+
+ if (unlikely(j->setnice)) {
+ (void)job_assumes_zero_p(j, setpriority(PRIO_PROCESS, 0, j->nice));
+ }
+
+ SLIST_FOREACH(li, &j->limits, sle) {
+ struct rlimit rl;
+
+ if (job_assumes_zero_p(j, getrlimit(li->which, &rl) == -1)) {
+ continue;
+ }
+
+ if (li->sethard) {
+ rl.rlim_max = li->lim.rlim_max;
+ }
+ if (li->setsoft) {
+ rl.rlim_cur = li->lim.rlim_cur;
+ }
+
+ if (setrlimit(li->which, &rl) == -1) {
+ job_log_error(j, LOG_WARNING, "setrlimit()");
+ }
+ }
+
+ if (unlikely(!j->inetcompat && j->session_create)) {
+ launchd_SessionCreate();
+ }
+
+ if (unlikely(j->low_pri_io)) {
+ (void)job_assumes_zero_p(j, setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_THROTTLE));
+ }
+ if (unlikely(j->rootdir)) {
+ (void)job_assumes_zero_p(j, chroot(j->rootdir));
+ (void)job_assumes_zero_p(j, chdir("."));
+ }
+
+ job_postfork_become_user(j);
+
+ if (unlikely(j->workingdir)) {
+ if (chdir(j->workingdir) == -1) {
+ if (errno == ENOENT || errno == ENOTDIR) {
+ job_log(j, LOG_ERR, "Job specified non-existent working directory: %s", j->workingdir);
+ } else {
+ (void)job_assumes_zero(j, errno);
+ }
+ }
+ }
+
+ if (unlikely(j->setmask)) {
+ umask(j->mask);
+ }
+
+ if (j->stdin_fd) {
+ (void)job_assumes_zero_p(j, dup2(j->stdin_fd, STDIN_FILENO));
+ } else {
+ job_setup_fd(j, STDIN_FILENO, j->stdinpath, O_RDONLY|O_CREAT);
+ }
+ job_setup_fd(j, STDOUT_FILENO, j->stdoutpath, O_WRONLY|O_CREAT|O_APPEND);
+ job_setup_fd(j, STDERR_FILENO, j->stderrpath, O_WRONLY|O_CREAT|O_APPEND);
+
+ jobmgr_setup_env_from_other_jobs(j->mgr);
+
+ SLIST_FOREACH(ei, &j->env, sle) {
+ setenv(ei->key, ei->value, 1);
+ }
+
+#if !TARGET_OS_EMBEDDED
+ if (j->jetsam_properties) {
+ (void)job_assumes_zero(j, proc_setpcontrol(PROC_SETPC_TERMINATE));
+ }
+#endif
+
+#if TARGET_OS_EMBEDDED
+ if (j->main_thread_priority != 0) {
+ struct sched_param params;
+ bzero(¶ms, sizeof(params));
+ params.sched_priority = j->main_thread_priority;
+ (void)job_assumes_zero_p(j, pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶ms));
+ }
+#endif
+
+ /*
+ * We'd like to call setsid() unconditionally, but we have reason to
+ * believe that prevents launchd from being able to send signals to
+ * setuid children. We'll settle for process-groups.
+ */
+ if (getppid() != 1) {
+ (void)job_assumes_zero_p(j, setpgid(0, 0));
+ } else {
+ (void)job_assumes_zero_p(j, setsid());
+ }
+}
+
+void
+job_setup_fd(job_t j, int target_fd, const char *path, int flags)
+{
+ int fd;
+
+ if (!path) {
+ return;
+ }
+
+ if ((fd = open(path, flags|O_NOCTTY, DEFFILEMODE)) == -1) {
+ job_log_error(j, LOG_WARNING, "open(\"%s\", ...)", path);
+ return;
+ }
+
+ (void)job_assumes_zero_p(j, dup2(fd, target_fd));
+ (void)job_assumes_zero(j, runtime_close(fd));
+}
+
+void
+calendarinterval_setalarm(job_t j, struct calendarinterval *ci)
+{
+ struct calendarinterval *ci_iter, *ci_prev = NULL;
+ time_t later, head_later;
+
+ later = cronemu(ci->when.tm_mon, ci->when.tm_mday, ci->when.tm_hour, ci->when.tm_min);
+
+ if (ci->when.tm_wday != -1) {
+ time_t otherlater = cronemu_wday(ci->when.tm_wday, ci->when.tm_hour, ci->when.tm_min);
+
+ if (ci->when.tm_mday == -1) {
+ later = otherlater;
+ } else {
+ later = later < otherlater ? later : otherlater;
+ }
+ }
+
+ ci->when_next = later;
+
+ LIST_FOREACH(ci_iter, &sorted_calendar_events, global_sle) {
+ if (ci->when_next < ci_iter->when_next) {
+ LIST_INSERT_BEFORE(ci_iter, ci, global_sle);
+ break;
+ }
+
+ ci_prev = ci_iter;
+ }
+
+ if (ci_iter == NULL) {
+ // ci must want to fire after every other timer, or there are no timers
+
+ if (LIST_EMPTY(&sorted_calendar_events)) {
+ LIST_INSERT_HEAD(&sorted_calendar_events, ci, global_sle);
+ } else {
+ LIST_INSERT_AFTER(ci_prev, ci, global_sle);
+ }
+ }
+
+ head_later = LIST_FIRST(&sorted_calendar_events)->when_next;
+
+ if (job_assumes_zero_p(j, kevent_mod((uintptr_t)&sorted_calendar_events, EVFILT_TIMER, EV_ADD, NOTE_ABSOLUTE|NOTE_SECONDS, head_later, root_jobmgr)) != -1) {
+ char time_string[100];
+ size_t time_string_len;
+
+ ctime_r(&later, time_string);
+ time_string_len = strlen(time_string);
+
+ if (likely(time_string_len && time_string[time_string_len - 1] == '\n')) {
+ time_string[time_string_len - 1] = '\0';
+ }
+
+ job_log(j, LOG_INFO, "Scheduled to run again at %s", time_string);
+ }
+}
+
+bool
+jobmgr_log_bug(aslmsg asl_message __attribute__((unused)), void *ctx, const char *message)
+{
+ jobmgr_t jm = ctx;
+ jobmgr_log(jm, LOG_ERR, "%s", message);
+
+ return true;
+}
+
+bool
+job_log_bug(aslmsg asl_message __attribute__((unused)), void *ctx, const char *message)
+{
+ job_t j = ctx;
+ job_log(j, LOG_ERR, "%s", message);
+
+ return true;
+}
+
+void
+job_log_perf_statistics(job_t j)
+{
+ if (j->anonymous) {
+ return;
+ }
+ if (!launchd_log_perf) {
+ return;
+ }
+
+ job_log(j, LOG_PERF, "Job is currently %srunning.", j->p ? "" : "not ");
+ job_log(j, LOG_PERF, "Number of runs: %u", j->nruns);
+ if (j->nruns) {
+ job_log(j, LOG_PERF, "Total runtime: %06f.", (double)j->trt / (double)NSEC_PER_SEC);
+ job_log(j, LOG_PERF, "Total user time: %ld.%06u", j->ru.ru_utime.tv_sec, j->ru.ru_utime.tv_usec);
+ job_log(j, LOG_PERF, "Total system time: %ld.%06u", j->ru.ru_stime.tv_sec, j->ru.ru_stime.tv_usec);
+ job_log(j, LOG_PERF, "Largest maximum resident size: %lu", j->ru.ru_maxrss);
+ job_log(j, LOG_PERF, "Total integral shared memory size: %lu", j->ru.ru_ixrss);
+ job_log(j, LOG_PERF, "Total integral unshared data size: %lu", j->ru.ru_idrss);
+ job_log(j, LOG_PERF, "Total integral unshared stack size: %lu", j->ru.ru_isrss);
+ job_log(j, LOG_PERF, "Total page reclaims: %lu", j->ru.ru_minflt);
+ job_log(j, LOG_PERF, "Total page faults: %lu", j->ru.ru_majflt);
+ job_log(j, LOG_PERF, "Total swaps: %lu", j->ru.ru_nswap);
+ job_log(j, LOG_PERF, "Total input ops: %lu", j->ru.ru_inblock);
+ job_log(j, LOG_PERF, "Total output ops: %lu", j->ru.ru_oublock);
+ job_log(j, LOG_PERF, "Total messages sent: %lu", j->ru.ru_msgsnd);
+ job_log(j, LOG_PERF, "Total messages received: %lu", j->ru.ru_msgrcv);
+ job_log(j, LOG_PERF, "Total signals received: %lu", j->ru.ru_nsignals);
+ job_log(j, LOG_PERF, "Total voluntary context switches: %lu", j->ru.ru_nvcsw);
+ job_log(j, LOG_PERF, "Total involuntary context switches: %lu", j->ru.ru_nivcsw);
+ }
+
+ if (j->p) {
+ uint64_t rt = runtime_get_nanoseconds_since(j->start_time);
+ job_log(j, LOG_PERF, "Current instance wall time: %06f", (double)rt / (double)NSEC_PER_SEC);
+
+ struct proc_taskinfo ti;
+ int r = proc_pidinfo(j->p, PROC_PIDTASKINFO, 1, &ti, PROC_PIDTASKINFO_SIZE);
+ if (r > 0) {
+ job_log(j, LOG_PERF, "Current instance virtual size: %llu", ti.pti_virtual_size);
+ job_log(j, LOG_PERF, "Current instance resident size: %llu", ti.pti_resident_size);
+ job_log(j, LOG_PERF, "Current instance user time: %06f", (double)ti.pti_total_user / (double)NSEC_PER_SEC);
+ job_log(j, LOG_PERF, "Current instance system time: %06f", (double)ti.pti_total_system / (double)NSEC_PER_SEC);
+ job_log(j, LOG_PERF, "Current instance number of user threads: %llu", ti.pti_threads_user);
+ job_log(j, LOG_PERF, "Current instance number of system threads: %llu", ti.pti_threads_system);
+ job_log(j, LOG_PERF, "Current instance default thread policy: %d", ti.pti_policy);
+ job_log(j, LOG_PERF, "Current instance number of page faults: %d", ti.pti_faults);
+ job_log(j, LOG_PERF, "Current instance number of page-ins: %d", ti.pti_pageins);
+ job_log(j, LOG_PERF, "Current instance number of COW faults: %d", ti.pti_cow_faults);
+ job_log(j, LOG_PERF, "Current instance number of Mach syscalls: %d", ti.pti_syscalls_mach);
+ job_log(j, LOG_PERF, "Current instance number of Unix syscalls: %d", ti.pti_syscalls_unix);
+ job_log(j, LOG_PERF, "Current instance number of threads: %d", ti.pti_threadnum);
+ job_log(j, LOG_PERF, "Current instance number of running threads: %d", ti.pti_numrunning);
+ job_log(j, LOG_PERF, "Current instance task priority: %d", ti.pti_priority);
+ } else {
+ job_log(j, LOG_PERF, "proc_pidinfo(%d): %d: %s", j->p, errno, strerror(errno));
+ }
+ }
+
+ if (!j->ondemand) {
+ job_log(j, LOG_PERF, "Job is configured to always run.");
+ }
+
+ struct machservice *msi = NULL;
+ SLIST_FOREACH(msi, &j->machservices, sle) {
+ if (msi->upfront) {
+ job_log(j, LOG_PERF, "Job advertises service in plist: %s", msi->name);
+ } else if (!(msi->event_channel || msi->per_pid)) {
+ job_log(j, LOG_PERF, "Job has dynamically registered service: %s", msi->name);
+ } else if (msi->per_pid) {
+ job_log(j, LOG_PERF, "Job advertises per-PID service: %s", msi->name);
+ }
+ }
+}
+
+void
+job_logv(job_t j, int pri, int err, const char *msg, va_list ap)
+{
+ const char *label2use = j ? j->label : "com.apple.launchd.job-unknown";
+ const char *mgr2use = j ? j->mgr->name : "com.apple.launchd.jobmanager-unknown";
+ char *newmsg;
+ int oldmask = 0;
+ size_t newmsgsz;
+
+ struct launchd_syslog_attr attr = {
+ .from_name = launchd_label,
+ .about_name = label2use,
+ .session_name = mgr2use,
+ .priority = pri,
+ .from_uid = getuid(),
+ .from_pid = getpid(),
+ .about_pid = j ? j->p : 0,
+ };
+
+ /* Hack: If bootstrap_port is set, we must be on the child side of a
+ * fork(2), but before the exec*(3). Let's route the log message back to
+ * launchd proper.
+ */
+ if (bootstrap_port) {
+ return _vproc_logv(pri, err, msg, ap);
+ }
+
+ newmsgsz = strlen(msg) + 200;
+ newmsg = alloca(newmsgsz);
+
+ if (err) {
+#if !TARGET_OS_EMBEDDED
+ snprintf(newmsg, newmsgsz, "%s: %d: %s", msg, err, strerror(err));
+#else
+ snprintf(newmsg, newmsgsz, "(%s) %s: %d: %s", label2use, msg, err, strerror(err));
+#endif
+ } else {
+#if !TARGET_OS_EMBEDDED
+ snprintf(newmsg, newmsgsz, "%s", msg);
+#else
+ snprintf(newmsg, newmsgsz, "(%s) %s", label2use, msg);
+#endif
+ }
+
+ if (j && unlikely(j->debug)) {
+ oldmask = setlogmask(LOG_UPTO(LOG_DEBUG));
+ }
+
+ launchd_vsyslog(&attr, newmsg, ap);
+
+ if (j && unlikely(j->debug)) {
+ setlogmask(oldmask);
+ }
+}
+
+void
+job_log_error(job_t j, int pri, const char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ job_logv(j, pri, errno, msg, ap);
+ va_end(ap);
+}
+
+void
+job_log(job_t j, int pri, const char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ job_logv(j, pri, 0, msg, ap);
+ va_end(ap);
+}
+
+#if 0
+void
+jobmgr_log_error(jobmgr_t jm, int pri, const char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ jobmgr_logv(jm, pri, errno, msg, ap);
+ va_end(ap);
+}
+#endif
+
+void
+jobmgr_log_perf_statistics(jobmgr_t jm)
+{
+ jobmgr_t jmi = NULL;
+ SLIST_FOREACH(jmi, &jm->submgrs, sle) {
+ jobmgr_log_perf_statistics(jmi);
+ }
+
+ if (jm->xpc_singleton) {
+ jobmgr_log(jm, LOG_PERF, "XPC Singleton Domain: %s", jm->shortdesc);
+ } else if (jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) {
+ jobmgr_log(jm, LOG_PERF, "XPC Private Domain: %s", jm->owner);
+ } else if (jm->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET) {
+ jobmgr_log(jm, LOG_PERF, "Created via bootstrap_subset()");
+ }
+
+ jobmgr_log(jm, LOG_PERF, "Jobs in job manager:");
+
+ job_t ji = NULL;
+ LIST_FOREACH(ji, &jm->jobs, sle) {
+ job_log_perf_statistics(ji);
+ }
+
+ jobmgr_log(jm, LOG_PERF, "End of job list.");
+}
+
+void
+jobmgr_log(jobmgr_t jm, int pri, const char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ jobmgr_logv(jm, pri, 0, msg, ap);
+ va_end(ap);
+}
+
+void
+jobmgr_logv(jobmgr_t jm, int pri, int err, const char *msg, va_list ap)
+{
+ if (!jm) {
+ jm = root_jobmgr;
+ }
+
+ char *newmsg;
+ char *newname;
+ size_t i, o, jmname_len = strlen(jm->name), newmsgsz;
+
+ newname = alloca((jmname_len + 1) * 2);
+ newmsgsz = (jmname_len + 1) * 2 + strlen(msg) + 100;
+ newmsg = alloca(newmsgsz);
+
+ for (i = 0, o = 0; i < jmname_len; i++, o++) {
+ if (jm->name[i] == '%') {
+ newname[o] = '%';
+ o++;
+ }
+ newname[o] = jm->name[i];
+ }
+ newname[o] = '\0';
+
+ if (err) {
+ snprintf(newmsg, newmsgsz, "%s: %s: %s", newname, msg, strerror(err));
+ } else {
+ snprintf(newmsg, newmsgsz, "%s: %s", newname, msg);
+ }
+
+ if (jm->parentmgr) {
+ jobmgr_logv(jm->parentmgr, pri, 0, newmsg, ap);
+ } else {
+ struct launchd_syslog_attr attr = {
+ .from_name = launchd_label,
+ .about_name = launchd_label,
+ .session_name = jm->name,
+ .priority = pri,
+ .from_uid = getuid(),
+ .from_pid = getpid(),
+ .about_pid = getpid(),
+ };
+
+ launchd_vsyslog(&attr, newmsg, ap);
+ }
+}
+
+struct cal_dict_walk {
+ job_t j;
+ struct tm tmptm;
+};
+
+void
+calendarinterval_new_from_obj_dict_walk(launch_data_t obj, const char *key, void *context)
+{
+ struct cal_dict_walk *cdw = context;
+ struct tm *tmptm = &cdw->tmptm;
+ job_t j = cdw->j;
+ int64_t val;
+
+ if (unlikely(LAUNCH_DATA_INTEGER != launch_data_get_type(obj))) {
+ // hack to let caller know something went wrong
+ tmptm->tm_sec = -1;
+ return;
+ }
+
+ val = launch_data_get_integer(obj);
+
+ if (val < 0) {
+ job_log(j, LOG_WARNING, "The interval for key \"%s\" is less than zero.", key);
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_MINUTE) == 0) {
+ if (val > 59) {
+ job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 0 and 59 (inclusive).", key);
+ tmptm->tm_sec = -1;
+ } else {
+ tmptm->tm_min = (typeof(tmptm->tm_min)) val;
+ }
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_HOUR) == 0) {
+ if (val > 23) {
+ job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 0 and 23 (inclusive).", key);
+ tmptm->tm_sec = -1;
+ } else {
+ tmptm->tm_hour = (typeof(tmptm->tm_hour)) val;
+ }
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_DAY) == 0) {
+ if (val < 1 || val > 31) {
+ job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 1 and 31 (inclusive).", key);
+ tmptm->tm_sec = -1;
+ } else {
+ tmptm->tm_mday = (typeof(tmptm->tm_mday)) val;
+ }
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_WEEKDAY) == 0) {
+ if (val > 7) {
+ job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 0 and 7 (inclusive).", key);
+ tmptm->tm_sec = -1;
+ } else {
+ tmptm->tm_wday = (typeof(tmptm->tm_wday)) val;
+ }
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_MONTH) == 0) {
+ if (val > 12) {
+ job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 0 and 12 (inclusive).", key);
+ tmptm->tm_sec = -1;
+ } else {
+ tmptm->tm_mon = (typeof(tmptm->tm_mon)) val;
+ tmptm->tm_mon -= 1; // 4798263 cron compatibility
+ }
+ }
+}
+
+bool
+calendarinterval_new_from_obj(job_t j, launch_data_t obj)
+{
+ struct cal_dict_walk cdw;
+
+ cdw.j = j;
+ memset(&cdw.tmptm, 0, sizeof(0));
+
+ cdw.tmptm.tm_min = -1;
+ cdw.tmptm.tm_hour = -1;
+ cdw.tmptm.tm_mday = -1;
+ cdw.tmptm.tm_wday = -1;
+ cdw.tmptm.tm_mon = -1;
+
+ if (!job_assumes(j, obj != NULL)) {
+ return false;
+ }
+
+ if (unlikely(LAUNCH_DATA_DICTIONARY != launch_data_get_type(obj))) {
+ return false;
+ }
+
+ launch_data_dict_iterate(obj, calendarinterval_new_from_obj_dict_walk, &cdw);
+
+ if (unlikely(cdw.tmptm.tm_sec == -1)) {
+ return false;
+ }
+
+ return calendarinterval_new(j, &cdw.tmptm);
+}
+
+bool
+calendarinterval_new(job_t j, struct tm *w)
+{
+ struct calendarinterval *ci = calloc(1, sizeof(struct calendarinterval));
+
+ if (!job_assumes(j, ci != NULL)) {
+ return false;
+ }
+
+ ci->when = *w;
+ ci->job = j;
+
+ SLIST_INSERT_HEAD(&j->cal_intervals, ci, sle);
+
+ calendarinterval_setalarm(j, ci);
+
+ runtime_add_weak_ref();
+
+ return true;
+}
+
+void
+calendarinterval_delete(job_t j, struct calendarinterval *ci)
+{
+ SLIST_REMOVE(&j->cal_intervals, ci, calendarinterval, sle);
+ LIST_REMOVE(ci, global_sle);
+
+ free(ci);
+
+ runtime_del_weak_ref();
+}
+
+void
+calendarinterval_sanity_check(void)
+{
+ struct calendarinterval *ci = LIST_FIRST(&sorted_calendar_events);
+ time_t now = time(NULL);
+
+ if (unlikely(ci && (ci->when_next < now))) {
+ (void)jobmgr_assumes_zero_p(root_jobmgr, raise(SIGUSR1));
+ }
+}
+
+void
+calendarinterval_callback(void)
+{
+ struct calendarinterval *ci, *ci_next;
+ time_t now = time(NULL);
+
+ LIST_FOREACH_SAFE(ci, &sorted_calendar_events, global_sle, ci_next) {
+ job_t j = ci->job;
+
+ if (ci->when_next > now) {
+ break;
+ }
+
+ LIST_REMOVE(ci, global_sle);
+ calendarinterval_setalarm(j, ci);
+
+ j->start_pending = true;
+ job_dispatch(j, false);
+ }
+}
+
+bool
+socketgroup_new(job_t j, const char *name, int *fds, size_t fd_cnt)
+{
+ struct socketgroup *sg = calloc(1, sizeof(struct socketgroup) + strlen(name) + 1);
+
+ if (!job_assumes(j, sg != NULL)) {
+ return false;
+ }
+
+ sg->fds = calloc(1, fd_cnt * sizeof(int));
+ sg->fd_cnt = fd_cnt;
+
+ if (!job_assumes(j, sg->fds != NULL)) {
+ free(sg);
+ return false;
+ }
+
+ memcpy(sg->fds, fds, fd_cnt * sizeof(int));
+ strcpy(sg->name_init, name);
+
+ SLIST_INSERT_HEAD(&j->sockets, sg, sle);
+
+ runtime_add_weak_ref();
+
+ return true;
+}
+
+void
+socketgroup_delete(job_t j, struct socketgroup *sg)
+{
+ unsigned int i;
+
+ for (i = 0; i < sg->fd_cnt; i++) {
+#if 0
+ struct sockaddr_storage ss;
+ struct sockaddr_un *sun = (struct sockaddr_un *)&ss;
+ socklen_t ss_len = sizeof(ss);
+
+ // 5480306
+ if (job_assumes_zero(j, getsockname(sg->fds[i], (struct sockaddr *)&ss, &ss_len) != -1)
+ && job_assumes(j, ss_len > 0) && (ss.ss_family == AF_UNIX)) {
+ (void)job_assumes(j, unlink(sun->sun_path) != -1);
+ // We might conditionally need to delete a directory here
+ }
+#endif
+ (void)job_assumes_zero_p(j, runtime_close(sg->fds[i]));
+ }
+
+ SLIST_REMOVE(&j->sockets, sg, socketgroup, sle);
+
+ free(sg->fds);
+ free(sg);
+
+ runtime_del_weak_ref();
+}
+
+void
+socketgroup_kevent_mod(job_t j, struct socketgroup *sg, bool do_add)
+{
+ struct kevent kev[sg->fd_cnt];
+ char buf[10000];
+ unsigned int i, buf_off = 0;
+
+ for (i = 0; i < sg->fd_cnt; i++) {
+ EV_SET(&kev[i], sg->fds[i], EVFILT_READ, do_add ? EV_ADD : EV_DELETE, 0, 0, j);
+ buf_off += snprintf(buf + buf_off, sizeof(buf) - buf_off, " %d", sg->fds[i]);
+ }
+
+ job_log(j, LOG_DEBUG, "%s Sockets:%s", do_add ? "Watching" : "Ignoring", buf);
+
+ (void)job_assumes_zero_p(j, kevent_bulk_mod(kev, sg->fd_cnt));
+
+ for (i = 0; i < sg->fd_cnt; i++) {
+ (void)job_assumes(j, kev[i].flags & EV_ERROR);
+ errno = (typeof(errno)) kev[i].data;
+ (void)job_assumes_zero(j, kev[i].data);
+ }
+}
+
+void
+socketgroup_ignore(job_t j, struct socketgroup *sg)
+{
+ socketgroup_kevent_mod(j, sg, false);
+}
+
+void
+socketgroup_watch(job_t j, struct socketgroup *sg)
+{
+ socketgroup_kevent_mod(j, sg, true);
+}
+
+void
+socketgroup_callback(job_t j)
+{
+ job_dispatch(j, true);
+}
+
+bool
+envitem_new(job_t j, const char *k, const char *v, bool global)
+{
+ if (global && !launchd_allow_global_dyld_envvars) {
+ if (strncmp("DYLD_", k, sizeof("DYLD_") - 1) == 0) {
+ job_log(j, LOG_ERR, "Ignoring global environment variable submitted by job (variable=value): %s=%s", k, v);
+ return false;
+ }
+ }
+
+ struct envitem *ei = calloc(1, sizeof(struct envitem) + strlen(k) + 1 + strlen(v) + 1);
+
+ if (!job_assumes(j, ei != NULL)) {
+ return false;
+ }
+
+ strcpy(ei->key_init, k);
+ ei->value = ei->key_init + strlen(k) + 1;
+ strcpy(ei->value, v);
+
+ if (global) {
+ if (SLIST_EMPTY(&j->global_env)) {
+ LIST_INSERT_HEAD(&j->mgr->global_env_jobs, j, global_env_sle);
+ }
+ SLIST_INSERT_HEAD(&j->global_env, ei, sle);
+ } else {
+ SLIST_INSERT_HEAD(&j->env, ei, sle);
+ }
+
+ job_log(j, LOG_DEBUG, "Added environmental variable: %s=%s", k, v);
+
+ return true;
+}
+
+void
+envitem_delete(job_t j, struct envitem *ei, bool global)
+{
+ if (global) {
+ SLIST_REMOVE(&j->global_env, ei, envitem, sle);
+ if (SLIST_EMPTY(&j->global_env)) {
+ LIST_REMOVE(j, global_env_sle);
+ }
+ } else {
+ SLIST_REMOVE(&j->env, ei, envitem, sle);
+ }
+
+ free(ei);
+}
+
+void
+envitem_setup(launch_data_t obj, const char *key, void *context)
+{
+ job_t j = context;
+
+ if (launch_data_get_type(obj) != LAUNCH_DATA_STRING) {
+ return;
+ }
+
+ if (strncmp(LAUNCHD_TRUSTED_FD_ENV, key, sizeof(LAUNCHD_TRUSTED_FD_ENV) - 1) != 0) {
+ envitem_new(j, key, launch_data_get_string(obj), j->importing_global_env);
+ } else {
+ job_log(j, LOG_DEBUG, "Ignoring reserved environmental variable: %s", key);
+ }
+}
+
+bool
+limititem_update(job_t j, int w, rlim_t r)
+{
+ struct limititem *li;
+
+ SLIST_FOREACH(li, &j->limits, sle) {
+ if (li->which == w) {
+ break;
+ }
+ }
+
+ if (li == NULL) {
+ li = calloc(1, sizeof(struct limititem));
+
+ if (!job_assumes(j, li != NULL)) {
+ return false;
+ }
+
+ SLIST_INSERT_HEAD(&j->limits, li, sle);
+
+ li->which = w;
+ }
+
+ if (j->importing_hard_limits) {
+ li->lim.rlim_max = r;
+ li->sethard = true;
+ } else {
+ li->lim.rlim_cur = r;
+ li->setsoft = true;
+ }
+
+ return true;
+}
+
+void
+limititem_delete(job_t j, struct limititem *li)
+{
+ SLIST_REMOVE(&j->limits, li, limititem, sle);
+
+ free(li);
+}
+
+#if HAVE_SANDBOX
+void
+seatbelt_setup_flags(launch_data_t obj, const char *key, void *context)
+{
+ job_t j = context;
+
+ if (launch_data_get_type(obj) != LAUNCH_DATA_BOOL) {
+ job_log(j, LOG_WARNING, "Sandbox flag value must be boolean: %s", key);
+ return;
+ }
+
+ if (launch_data_get_bool(obj) == false) {
+ return;
+ }
+
+ if (strcasecmp(key, LAUNCH_JOBKEY_SANDBOX_NAMED) == 0) {
+ j->seatbelt_flags |= SANDBOX_NAMED;
+ }
+}
+#endif
+
+void
+limititem_setup(launch_data_t obj, const char *key, void *context)
+{
+ job_t j = context;
+ size_t i, limits_cnt = (sizeof(launchd_keys2limits) / sizeof(launchd_keys2limits[0]));
+ rlim_t rl;
+
+ if (launch_data_get_type(obj) != LAUNCH_DATA_INTEGER) {
+ return;
+ }
+
+ rl = launch_data_get_integer(obj);
+
+ for (i = 0; i < limits_cnt; i++) {
+ if (strcasecmp(launchd_keys2limits[i].key, key) == 0) {
+ break;
+ }
+ }
+
+ if (i == limits_cnt) {
+ return;
+ }
+
+ limititem_update(j, launchd_keys2limits[i].val, rl);
+}
+
+bool
+job_useless(job_t j)
+{
+ if ((j->legacy_LS_job || j->only_once) && j->start_time != 0) {
+ if (j->legacy_LS_job && j->j_port) {
+ return false;
+ }
+ job_log(j, LOG_INFO, "Exited. Was only configured to run once.");
+ return true;
+ } else if (j->removal_pending) {
+ job_log(j, LOG_DEBUG, "Exited while removal was pending.");
+ return true;
+ } else if (j->shutdown_monitor) {
+ return false;
+ } else if (j->mgr->shutting_down && !j->mgr->parentmgr) {
+ job_log(j, LOG_DEBUG, "Exited while shutdown in progress. Processes remaining: %lu/%lu", total_children, total_anon_children);
+ if (total_children == 0 && !j->anonymous) {
+ job_log(j, LOG_DEBUG | LOG_CONSOLE, "Job was last to exit during shutdown of: %s.", j->mgr->name);
+ }
+ return true;
+ } else if (j->legacy_mach_job) {
+ if (SLIST_EMPTY(&j->machservices)) {
+ job_log(j, LOG_INFO, "Garbage collecting");
+ return true;
+ } else if (!j->checkedin) {
+ job_log(j, LOG_WARNING, "Failed to check-in!");
+ return true;
+ }
+ } else {
+ /* If the job's executable does not have any valid architectures (for
+ * example, if it's a PowerPC-only job), then we don't even bother
+ * trying to relaunch it, as we have no reasonable expectation that
+ * the situation will change.
+ *
+ * <rdar://problem/9106979>
+ */
+ if (!j->did_exec && WEXITSTATUS(j->last_exit_status) == EBADARCH) {
+ job_log(j, LOG_ERR, "Job executable does not contain supported architectures. Unloading it. Its plist should be removed.");
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+job_keepalive(job_t j)
+{
+ mach_msg_type_number_t statusCnt;
+ mach_port_status_t status;
+ struct semaphoreitem *si;
+ struct machservice *ms;
+ bool good_exit = (WIFEXITED(j->last_exit_status) && WEXITSTATUS(j->last_exit_status) == 0);
+ bool is_not_kextd = (launchd_apple_internal || (strcmp(j->label, "com.apple.kextd") != 0));
+
+ if (unlikely(j->mgr->shutting_down)) {
+ return false;
+ }
+
+ /*
+ * 5066316
+ *
+ * We definitely need to revisit this after Leopard ships. Please see
+ * launchctl.c for the other half of this hack.
+ */
+ if (unlikely((j->mgr->global_on_demand_cnt > 0) && is_not_kextd)) {
+ return false;
+ }
+
+ if (unlikely(j->needs_kickoff)) {
+ job_log(j, LOG_DEBUG, "KeepAlive check: Job needs to be kicked off on-demand before KeepAlive sets in.");
+ return false;
+ }
+
+ if (j->start_pending) {
+ job_log(j, LOG_DEBUG, "KeepAlive check: Pent-up non-IPC launch criteria.");
+ return true;
+ }
+
+ if (!j->ondemand) {
+ job_log(j, LOG_DEBUG, "KeepAlive check: job configured to run continuously.");
+ return true;
+ }
+
+ SLIST_FOREACH(ms, &j->machservices, sle) {
+ statusCnt = MACH_PORT_RECEIVE_STATUS_COUNT;
+ if (mach_port_get_attributes(mach_task_self(), ms->port, MACH_PORT_RECEIVE_STATUS,
+ (mach_port_info_t)&status, &statusCnt) != KERN_SUCCESS) {
+ continue;
+ }
+ if (status.mps_msgcount) {
+ job_log(j, LOG_DEBUG, "KeepAlive check: %d queued Mach messages on service: %s",
+ status.mps_msgcount, ms->name);
+ return true;
+ }
+ }
+
+ /* TODO: Coalesce external events and semaphore items, since they're basically
+ * the same thing.
+ */
+ struct externalevent *ei = NULL;
+ LIST_FOREACH(ei, &j->events, job_le) {
+ if (ei->state == ei->wanted_state) {
+ return true;
+ }
+ }
+
+ SLIST_FOREACH(si, &j->semaphores, sle) {
+ bool wanted_state = false;
+ job_t other_j;
+
+ switch (si->why) {
+ case NETWORK_UP:
+ wanted_state = true;
+ case NETWORK_DOWN:
+ if (network_up == wanted_state) {
+ job_log(j, LOG_DEBUG, "KeepAlive: The network is %s.", wanted_state ? "up" : "down");
+ return true;
+ }
+ break;
+ case SUCCESSFUL_EXIT:
+ wanted_state = true;
+ case FAILED_EXIT:
+ if (good_exit == wanted_state) {
+ job_log(j, LOG_DEBUG, "KeepAlive: The exit state was %s.", wanted_state ? "successful" : "failure");
+ return true;
+ }
+ break;
+ case CRASHED:
+ wanted_state = true;
+ case DID_NOT_CRASH:
+ if (j->crashed == wanted_state) {
+ return true;
+ }
+ break;
+ case OTHER_JOB_ENABLED:
+ wanted_state = true;
+ case OTHER_JOB_DISABLED:
+ if ((bool)job_find(NULL, si->what) == wanted_state) {
+ job_log(j, LOG_DEBUG, "KeepAlive: The following job is %s: %s", wanted_state ? "enabled" : "disabled", si->what);
+ return true;
+ }
+ break;
+ case OTHER_JOB_ACTIVE:
+ wanted_state = true;
+ case OTHER_JOB_INACTIVE:
+ if ((other_j = job_find(NULL, si->what))) {
+ if ((bool)other_j->p == wanted_state) {
+ job_log(j, LOG_DEBUG, "KeepAlive: The following job is %s: %s", wanted_state ? "active" : "inactive", si->what);
+ return true;
+ }
+ }
+ break;
+ }
+ }
+
+ return false;
+}
+
+const char *
+job_active(job_t j)
+{
+ struct machservice *ms;
+ if (j->p && j->shutdown_monitor) {
+ return "Monitoring shutdown";
+ }
+ if (j->p) {
+ return "PID is still valid";
+ }
+
+ if (j->priv_port_has_senders) {
+ return "Privileged Port still has outstanding senders";
+ }
+
+ SLIST_FOREACH(ms, &j->machservices, sle) {
+ /* If we've simulated an exit, we mark the job as non-active, even
+ * though doing so will leave it in an unsafe state. We do this so that
+ * shutdown can proceed. See <rdar://problem/11126530>.
+ *
+ * For a more sustainable solution, see <rdar://problem/11131336>.
+ */
+ if (!j->workaround9359725 && ms->recv && machservice_active(ms)) {
+ job_log(j, LOG_INFO, "Mach service is still active: %s", ms->name);
+ return "Mach service is still active";
+ }
+ }
+
+ return NULL;
+}
+
+void
+machservice_watch(job_t j, struct machservice *ms)
+{
+ if (ms->recv) {
+ (void)job_assumes_zero(j, runtime_add_mport(ms->port, NULL));
+ }
+}
+
+void
+machservice_ignore(job_t j, struct machservice *ms)
+{
+ /* We only add ports whose receive rights we control into the port set, so
+ * don't attempt to remove te service from the port set if we didn't put it
+ * there in the first place. Otherwise, we could wind up trying to access a
+ * bogus index (like MACH_PORT_DEAD) or zeroing a valid one out.
+ *
+ * <rdar://problem/10898014>
+ */
+ if (ms->recv) {
+ (void)job_assumes_zero(j, runtime_remove_mport(ms->port));
+ }
+}
+
+void
+machservice_resetport(job_t j, struct machservice *ms)
+{
+ LIST_REMOVE(ms, port_hash_sle);
+ (void)job_assumes_zero(j, launchd_mport_close_recv(ms->port));
+ (void)job_assumes_zero(j, launchd_mport_deallocate(ms->port));
+
+ ms->gen_num++;
+ (void)job_assumes_zero(j, launchd_mport_create_recv(&ms->port));
+ (void)job_assumes_zero(j, launchd_mport_make_send(ms->port));
+ LIST_INSERT_HEAD(&port_hash[HASH_PORT(ms->port)], ms, port_hash_sle);
+}
+
+void
+machservice_stamp_port(job_t j, struct machservice *ms)
+{
+ mach_port_context_t ctx = 0;
+ char *where2get = j->prog ? j->prog : j->argv[0];
+
+ char *prog = NULL;
+ if ((prog = strrchr(where2get, '/'))) {
+ prog++;
+ } else {
+ prog = where2get;
+ }
+
+ (void)strncpy((char *)&ctx, prog, sizeof(ctx));
+#if __LITTLE_ENDIAN__
+#if __LP64__
+ ctx = OSSwapBigToHostInt64(ctx);
+#else
+ ctx = OSSwapBigToHostInt32(ctx);
+#endif
+#endif
+
+ (void)job_assumes_zero(j, mach_port_set_context(mach_task_self(), ms->port, ctx));
+}
+
+struct machservice *
+machservice_new(job_t j, const char *name, mach_port_t *serviceport, bool pid_local)
+{
+ /* Don't create new MachServices for dead ports. This is primarily for
+ * clients who use bootstrap_register2(). They can pass in a send right, but
+ * then that port can immediately go dead. Hilarity ensues.
+ *
+ * <rdar://problem/10898014>
+ */
+ if (*serviceport == MACH_PORT_DEAD) {
+ return NULL;
+ }
+
+ struct machservice *ms = calloc(1, sizeof(struct machservice) + strlen(name) + 1);
+ if (!job_assumes(j, ms != NULL)) {
+ return NULL;
+ }
+
+ strcpy((char *)ms->name, name);
+ ms->job = j;
+ ms->gen_num = 1;
+ ms->per_pid = pid_local;
+
+ if (likely(*serviceport == MACH_PORT_NULL)) {
+ if (job_assumes_zero(j, launchd_mport_create_recv(&ms->port)) != KERN_SUCCESS) {
+ goto out_bad;
+ }
+
+ if (job_assumes_zero(j, launchd_mport_make_send(ms->port)) != KERN_SUCCESS) {
+ goto out_bad2;
+ }
+ *serviceport = ms->port;
+ ms->recv = true;
+ } else {
+ ms->port = *serviceport;
+ ms->isActive = true;
+ }
+
+ SLIST_INSERT_HEAD(&j->machservices, ms, sle);
+
+ jobmgr_t where2put = j->mgr;
+ // XPC domains are separate from Mach bootstraps.
+ if (!(j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
+ if (launchd_flat_mach_namespace && !(j->mgr->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET)) {
+ where2put = root_jobmgr;
+ }
+ }
+
+ /* Don't allow MachServices added by multiple-instance jobs to be looked up
+ * by others. We could just do this with a simple bit, but then we'd have to
+ * uniquify the names ourselves to avoid collisions. This is just easier.
+ */
+ if (!j->dedicated_instance) {
+ LIST_INSERT_HEAD(&where2put->ms_hash[hash_ms(ms->name)], ms, name_hash_sle);
+ }
+ LIST_INSERT_HEAD(&port_hash[HASH_PORT(ms->port)], ms, port_hash_sle);
+
+ if (ms->recv) {
+ machservice_stamp_port(j, ms);
+ }
+
+ job_log(j, LOG_DEBUG, "Mach service added%s: %s", (j->mgr->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET) ? " to private namespace" : "", name);
+
+ return ms;
+out_bad2:
+ (void)job_assumes_zero(j, launchd_mport_close_recv(ms->port));
+out_bad:
+ free(ms);
+ return NULL;
+}
+
+struct machservice *
+machservice_new_alias(job_t j, struct machservice *orig)
+{
+ struct machservice *ms = calloc(1, sizeof(struct machservice) + strlen(orig->name) + 1);
+ if (job_assumes(j, ms != NULL)) {
+ strcpy((char *)ms->name, orig->name);
+ ms->alias = orig;
+ ms->job = j;
+
+ LIST_INSERT_HEAD(&j->mgr->ms_hash[hash_ms(ms->name)], ms, name_hash_sle);
+ SLIST_INSERT_HEAD(&j->machservices, ms, sle);
+ jobmgr_log(j->mgr, LOG_DEBUG, "Service aliased into job manager: %s", orig->name);
+ }
+
+ return ms;
+}
+
+bootstrap_status_t
+machservice_status(struct machservice *ms)
+{
+ ms = ms->alias ? ms->alias : ms;
+ if (ms->isActive) {
+ return BOOTSTRAP_STATUS_ACTIVE;
+ } else if (ms->job->ondemand) {
+ return BOOTSTRAP_STATUS_ON_DEMAND;
+ } else {
+ return BOOTSTRAP_STATUS_INACTIVE;
+ }
+}
+
+void
+job_setup_exception_port(job_t j, task_t target_task)
+{
+ struct machservice *ms;
+ thread_state_flavor_t f = 0;
+ mach_port_t exc_port = the_exception_server;
+
+ if (unlikely(j->alt_exc_handler)) {
+ ms = jobmgr_lookup_service(j->mgr, j->alt_exc_handler, true, 0);
+ if (likely(ms)) {
+ exc_port = machservice_port(ms);
+ } else {
+ job_log(j, LOG_WARNING, "Falling back to default Mach exception handler. Could not find: %s", j->alt_exc_handler);
+ }
+ } else if (unlikely(j->internal_exc_handler)) {
+ exc_port = runtime_get_kernel_port();
+ } else if (unlikely(!exc_port)) {
+ return;
+ }
+
+#if defined (__ppc__) || defined(__ppc64__)
+ f = PPC_THREAD_STATE64;
+#elif defined(__i386__) || defined(__x86_64__)
+ f = x86_THREAD_STATE;
+#elif defined(__arm__)
+ f = ARM_THREAD_STATE;
+#else
+#error "unknown architecture"
+#endif
+
+ if (likely(target_task)) {
+ kern_return_t kr = task_set_exception_ports(target_task, EXC_MASK_CRASH | EXC_MASK_RESOURCE, exc_port, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, f);
+ if (kr) {
+ if (kr != MACH_SEND_INVALID_DEST) {
+ (void)job_assumes_zero(j, kr);
+ } else {
+ job_log(j, LOG_WARNING, "Task died before exception port could be set.");
+ }
+ }
+ } else if (pid1_magic && the_exception_server) {
+ mach_port_t mhp = mach_host_self();
+ (void)job_assumes_zero(j, host_set_exception_ports(mhp, EXC_MASK_CRASH | EXC_MASK_RESOURCE, the_exception_server, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, f));
+ (void)job_assumes_zero(j, launchd_mport_deallocate(mhp));
+ }
+}
+
+void
+job_set_exception_port(job_t j, mach_port_t port)
+{
+ if (unlikely(!the_exception_server)) {
+ the_exception_server = port;
+ job_setup_exception_port(j, 0);
+ } else {
+ job_log(j, LOG_WARNING, "The exception server is already claimed!");
+ }
+}
+
+void
+machservice_setup_options(launch_data_t obj, const char *key, void *context)
+{
+ struct machservice *ms = context;
+ mach_port_t mhp = mach_host_self();
+ int which_port;
+ bool b;
+
+ if (!job_assumes(ms->job, mhp != MACH_PORT_NULL)) {
+ return;
+ }
+
+ switch (launch_data_get_type(obj)) {
+ case LAUNCH_DATA_INTEGER:
+ which_port = (int)launch_data_get_integer(obj); // XXX we should bound check this...
+ if (strcasecmp(key, LAUNCH_JOBKEY_MACH_TASKSPECIALPORT) == 0) {
+ switch (which_port) {
+ case TASK_KERNEL_PORT:
+ case TASK_HOST_PORT:
+ case TASK_NAME_PORT:
+ case TASK_BOOTSTRAP_PORT:
+ /* I find it a little odd that zero isn't reserved in the header.
+ * Normally Mach is fairly good about this convention...
+ */
+ case 0:
+ job_log(ms->job, LOG_WARNING, "Tried to set a reserved task special port: %d", which_port);
+ break;
+ default:
+ ms->special_port_num = which_port;
+ SLIST_INSERT_HEAD(&special_ports, ms, special_port_sle);
+ break;
+ }
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_HOSTSPECIALPORT) == 0 && pid1_magic) {
+ if (which_port > HOST_MAX_SPECIAL_KERNEL_PORT) {
+ (void)job_assumes_zero(ms->job, (errno = host_set_special_port(mhp, which_port, ms->port)));
+ } else {
+ job_log(ms->job, LOG_WARNING, "Tried to set a reserved host special port: %d", which_port);
+ }
+ }
+ case LAUNCH_DATA_BOOL:
+ b = launch_data_get_bool(obj);
+ if (strcasecmp(key, LAUNCH_JOBKEY_MACH_ENTERKERNELDEBUGGERONCLOSE) == 0) {
+ ms->debug_on_close = b;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_RESETATCLOSE) == 0) {
+ ms->reset = b;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_HIDEUNTILCHECKIN) == 0) {
+ ms->hide = b;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_EXCEPTIONSERVER) == 0) {
+ job_set_exception_port(ms->job, ms->port);
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_KUNCSERVER) == 0) {
+ ms->kUNCServer = b;
+ (void)job_assumes_zero(ms->job, host_set_UNDServer(mhp, ms->port));
+ }
+ break;
+ case LAUNCH_DATA_STRING:
+ if (strcasecmp(key, LAUNCH_JOBKEY_MACH_DRAINMESSAGESONCRASH) == 0) {
+ const char *option = launch_data_get_string(obj);
+ if (strcasecmp(option, "One") == 0) {
+ ms->drain_one_on_crash = true;
+ } else if (strcasecmp(option, "All") == 0) {
+ ms->drain_all_on_crash = true;
+ }
+ }
+ break;
+ case LAUNCH_DATA_DICTIONARY:
+ job_set_exception_port(ms->job, ms->port);
+ break;
+ default:
+ break;
+ }
+
+ (void)job_assumes_zero(ms->job, launchd_mport_deallocate(mhp));
+}
+
+void
+machservice_setup(launch_data_t obj, const char *key, void *context)
+{
+ job_t j = context;
+ struct machservice *ms;
+ mach_port_t p = MACH_PORT_NULL;
+
+ if (unlikely(ms = jobmgr_lookup_service(j->mgr, key, false, 0))) {
+ job_log(j, LOG_WARNING, "Conflict with job: %s over Mach service: %s", ms->job->label, key);
+ return;
+ }
+
+ if (!job_assumes(j, (ms = machservice_new(j, key, &p, false)) != NULL)) {
+ return;
+ }
+
+ ms->isActive = false;
+ ms->upfront = true;
+
+ if (launch_data_get_type(obj) == LAUNCH_DATA_DICTIONARY) {
+ launch_data_dict_iterate(obj, machservice_setup_options, ms);
+ }
+}
+
+jobmgr_t
+jobmgr_do_garbage_collection(jobmgr_t jm)
+{
+ jobmgr_t jmi = NULL, jmn = NULL;
+ SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) {
+ jobmgr_do_garbage_collection(jmi);
+ }
+
+ if (!jm->shutting_down) {
+ return jm;
+ }
+
+ if (SLIST_EMPTY(&jm->submgrs)) {
+ jobmgr_log(jm, LOG_DEBUG, "No submanagers left.");
+ } else {
+ jobmgr_log(jm, LOG_DEBUG, "Still have submanagers.");
+ SLIST_FOREACH(jmi, &jm->submgrs, sle) {
+ jobmgr_log(jm, LOG_DEBUG, "Submanager: %s", jmi->name);
+ }
+ }
+
+ size_t actives = 0;
+ job_t ji = NULL, jn = NULL;
+ LIST_FOREACH_SAFE(ji, &jm->jobs, sle, jn) {
+ if (ji->anonymous) {
+ continue;
+ }
+
+ // Let the shutdown monitor be up until the very end.
+ if (ji->shutdown_monitor) {
+ continue;
+ }
+
+ /* On our first pass through, open a transaction for all the jobs that
+ * need to be dirty at shutdown. We'll close these transactions once the
+ * jobs that do not need to be dirty at shutdown have all exited.
+ */
+ if (ji->dirty_at_shutdown && !jm->shutdown_jobs_dirtied) {
+ job_open_shutdown_transaction(ji);
+ }
+
+ const char *active = job_active(ji);
+ if (!active) {
+ job_remove(ji);
+ } else {
+ job_log(ji, LOG_DEBUG, "Job is active: %s", active);
+ job_stop(ji);
+
+ if (!ji->dirty_at_shutdown) {
+ actives++;
+ }
+
+ if (ji->clean_kill) {
+ job_log(ji, LOG_DEBUG, "Job was killed cleanly.");
+ } else {
+ job_log(ji, LOG_DEBUG, "Job was sent SIGTERM%s.", ji->sent_sigkill ? " and SIGKILL" : "");
+ }
+ }
+ }
+
+ jm->shutdown_jobs_dirtied = true;
+ if (actives == 0) {
+ if (!jm->shutdown_jobs_cleaned) {
+ /* Once all normal jobs have exited, we clean the dirty-at-shutdown
+ * jobs and make them into normal jobs so that the above loop will
+ * handle them appropriately.
+ */
+ LIST_FOREACH(ji, &jm->jobs, sle) {
+ if (ji->anonymous) {
+ continue;
+ }
+
+ if (!job_active(ji)) {
+ continue;
+ }
+
+ if (ji->shutdown_monitor) {
+ continue;
+ }
+
+ job_close_shutdown_transaction(ji);
+ actives++;
+ }
+
+ jm->shutdown_jobs_cleaned = true;
+ }
+
+ if (SLIST_EMPTY(&jm->submgrs) && actives == 0) {
+ /* We may be in a situation where the shutdown monitor is all that's
+ * left, in which case we want to stop it. Like dirty-at-shutdown
+ * jobs, we turn it back into a normal job so that the main loop
+ * treats it appropriately.
+ *
+ * See:
+ * <rdar://problem/10756306>
+ * <rdar://problem/11034971>
+ * <rdar://problem/11549541>
+ */
+ if (jm->monitor_shutdown && _launchd_shutdown_monitor) {
+ /* The rest of shutdown has completed, so we can kill the shutdown
+ * monitor now like it was any other job.
+ */
+ _launchd_shutdown_monitor->shutdown_monitor = false;
+
+ job_log(_launchd_shutdown_monitor, LOG_NOTICE | LOG_CONSOLE, "Stopping shutdown monitor.");
+ job_stop(_launchd_shutdown_monitor);
+ _launchd_shutdown_monitor = NULL;
+ } else {
+ jobmgr_log(jm, LOG_DEBUG, "Removing.");
+ jobmgr_remove(jm);
+ return NULL;
+ }
+ }
+ }
+
+ return jm;
+}
+
+void
+jobmgr_kill_stray_children(jobmgr_t jm, pid_t *p, size_t np)
+{
+ /* I maintain that stray processes should be at the mercy of launchd during
+ * shutdown, but nevertheless, things like diskimages-helper can stick
+ * around, and SIGKILLing them can result in data loss. So we send SIGTERM
+ * to all the strays and don't wait for them to exit before moving on.
+ *
+ * See rdar://problem/6562592
+ */
+ size_t i = 0;
+ for (i = 0; i < np; i++) {
+ if (p[i] != 0) {
+ jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Sending SIGTERM to PID %u and continuing...", p[i]);
+ (void)jobmgr_assumes_zero_p(jm, kill2(p[i], SIGTERM));
+ }
+ }
+}
+
+void
+jobmgr_log_stray_children(jobmgr_t jm, bool kill_strays)
+{
+ size_t kp_skipped = 0, len = sizeof(pid_t) * get_kern_max_proc();
+ pid_t *pids = NULL;
+ int i = 0, kp_cnt = 0;
+
+ if (likely(jm->parentmgr || !pid1_magic)) {
+ return;
+ }
+
+ if (!jobmgr_assumes(jm, (pids = malloc(len)) != NULL)) {
+ return;
+ }
+
+ runtime_ktrace0(RTKT_LAUNCHD_FINDING_ALL_STRAYS);
+
+ if (jobmgr_assumes_zero_p(jm, (kp_cnt = proc_listallpids(pids, len))) == -1) {
+ goto out;
+ }
+
+ pid_t *ps = (pid_t *)calloc(sizeof(pid_t), kp_cnt);
+ for (i = 0; i < kp_cnt; i++) {
+ struct proc_bsdshortinfo proc;
+ if (proc_pidinfo(pids[i], PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
+ if (errno != ESRCH) {
+ (void)jobmgr_assumes_zero(jm, errno);
+ }
+
+ kp_skipped++;
+ continue;
+ }
+
+ pid_t p_i = pids[i];
+ pid_t pp_i = proc.pbsi_ppid;
+ pid_t pg_i = proc.pbsi_pgid;
+ const char *z = (proc.pbsi_status == SZOMB) ? "zombie " : "";
+ const char *n = proc.pbsi_comm;
+
+ if (unlikely(p_i == 0 || p_i == 1)) {
+ kp_skipped++;
+ continue;
+ }
+
+ if (_launchd_shutdown_monitor && pp_i == _launchd_shutdown_monitor->p) {
+ kp_skipped++;
+ continue;
+ }
+
+ // We might have some jobs hanging around that we've decided to shut down in spite of.
+ job_t j = jobmgr_find_by_pid(jm, p_i, false);
+ if (!j || (j && j->anonymous)) {
+ jobmgr_log(jm, LOG_INFO | LOG_CONSOLE, "Stray %s%s at shutdown: PID %u PPID %u PGID %u %s", z, j ? "anonymous job" : "process", p_i, pp_i, pg_i, n);
+
+ int status = 0;
+ if (pp_i == getpid() && !jobmgr_assumes(jm, proc.pbsi_status != SZOMB)) {
+ if (jobmgr_assumes_zero(jm, waitpid(p_i, &status, WNOHANG)) == 0) {
+ jobmgr_log(jm, LOG_INFO | LOG_CONSOLE, "Unreaped zombie stray exited with status %i.", WEXITSTATUS(status));
+ }
+ kp_skipped++;
+ } else {
+ job_t leader = jobmgr_find_by_pid(jm, pg_i, false);
+ /* See rdar://problem/6745714. Some jobs have child processes that back kernel state,
+ * so we don't want to terminate them. Long-term, I'd really like to provide shutdown
+ * hints to the kernel along the way, so that it could shutdown certain subsystems when
+ * their userspace emissaries go away, before the call to reboot(2).
+ */
+ if (leader && leader->ignore_pg_at_shutdown) {
+ kp_skipped++;
+ } else {
+ ps[i] = p_i;
+ }
+ }
+ } else {
+ kp_skipped++;
+ }
+ }
+
+ if ((kp_cnt - kp_skipped > 0) && kill_strays) {
+ jobmgr_kill_stray_children(jm, ps, kp_cnt - kp_skipped);
+ }
+
+ free(ps);
+out:
+ free(pids);
+}
+
+jobmgr_t
+jobmgr_parent(jobmgr_t jm)
+{
+ return jm->parentmgr;
+}
+
+void
+job_uncork_fork(job_t j)
+{
+ pid_t c = j->p;
+
+ job_log(j, LOG_DEBUG, "Uncorking the fork().");
+ /* this unblocks the child and avoids a race
+ * between the above fork() and the kevent_mod() */
+ (void)job_assumes(j, write(j->fork_fd, &c, sizeof(c)) == sizeof(c));
+ (void)job_assumes_zero_p(j, runtime_close(j->fork_fd));
+ j->fork_fd = 0;
+}
+
+jobmgr_t
+jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bool sflag, const char *name, bool skip_init, mach_port_t asport)
+{
+ job_t bootstrapper = NULL;
+ jobmgr_t jmr;
+
+ __OSX_COMPILETIME_ASSERT__(offsetof(struct jobmgr_s, kqjobmgr_callback) == 0);
+
+ if (unlikely(jm && requestorport == MACH_PORT_NULL)) {
+ jobmgr_log(jm, LOG_ERR, "Mach sub-bootstrap create request requires a requester port");
+ return NULL;
+ }
+
+ jmr = calloc(1, sizeof(struct jobmgr_s) + (name ? (strlen(name) + 1) : NAME_MAX + 1));
+
+ if (!jobmgr_assumes(jm, jmr != NULL)) {
+ return NULL;
+ }
+
+ if (jm == NULL) {
+ root_jobmgr = jmr;
+ }
+
+ jmr->kqjobmgr_callback = jobmgr_callback;
+ strcpy(jmr->name_init, name ? name : "Under construction");
+
+ jmr->req_port = requestorport;
+
+ if ((jmr->parentmgr = jm)) {
+ SLIST_INSERT_HEAD(&jm->submgrs, jmr, sle);
+ }
+
+ if (jm && jobmgr_assumes_zero(jmr, launchd_mport_notify_req(jmr->req_port, MACH_NOTIFY_DEAD_NAME)) != KERN_SUCCESS) {
+ goto out_bad;
+ }
+
+ if (transfer_port != MACH_PORT_NULL) {
+ (void)jobmgr_assumes(jmr, jm != NULL);
+ jmr->jm_port = transfer_port;
+ } else if (!jm && !pid1_magic) {
+ char *trusted_fd = getenv(LAUNCHD_TRUSTED_FD_ENV);
+ name_t service_buf;
+
+ snprintf(service_buf, sizeof(service_buf), "com.apple.launchd.peruser.%u", getuid());
+
+ if (jobmgr_assumes_zero(jmr, bootstrap_check_in(bootstrap_port, service_buf, &jmr->jm_port)) != 0) {
+ goto out_bad;
+ }
+
+ if (trusted_fd) {
+ int dfd, lfd = (int) strtol(trusted_fd, NULL, 10);
+
+ if ((dfd = dup(lfd)) >= 0) {
+ (void)jobmgr_assumes_zero_p(jmr, runtime_close(dfd));
+ (void)jobmgr_assumes_zero_p(jmr, runtime_close(lfd));
+ }
+
+ unsetenv(LAUNCHD_TRUSTED_FD_ENV);
+ }
+
+ // cut off the Libc cache, we don't want to deadlock against ourself
+ inherited_bootstrap_port = bootstrap_port;
+ bootstrap_port = MACH_PORT_NULL;
+ osx_assert_zero(launchd_mport_notify_req(inherited_bootstrap_port, MACH_NOTIFY_DEAD_NAME));
+
+ // We set this explicitly as we start each child
+ osx_assert_zero(launchd_set_bport(MACH_PORT_NULL));
+ } else if (jobmgr_assumes_zero(jmr, launchd_mport_create_recv(&jmr->jm_port)) != KERN_SUCCESS) {
+ goto out_bad;
+ }
+
+ if (!name) {
+ sprintf(jmr->name_init, "%u", MACH_PORT_INDEX(jmr->jm_port));
+ }
+
+ if (!jm) {
+ (void)jobmgr_assumes_zero_p(jmr, kevent_mod(SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, jmr));
+ (void)jobmgr_assumes_zero_p(jmr, kevent_mod(SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, jmr));
+ (void)jobmgr_assumes_zero_p(jmr, kevent_mod(SIGUSR2, EVFILT_SIGNAL, EV_ADD, 0, 0, jmr));
+ (void)jobmgr_assumes_zero_p(jmr, kevent_mod(0, EVFILT_FS, EV_ADD, VQ_MOUNT|VQ_UNMOUNT|VQ_UPDATE, 0, jmr));
+ }
+
+ if (name && !skip_init) {
+ bootstrapper = jobmgr_init_session(jmr, name, sflag);
+ }
+
+ if (!bootstrapper || !bootstrapper->weird_bootstrap) {
+ if (jobmgr_assumes_zero(jmr, runtime_add_mport(jmr->jm_port, job_server)) != KERN_SUCCESS) {
+ goto out_bad;
+ }
+ }
+
+ jobmgr_log(jmr, LOG_DEBUG, "Created job manager%s%s", jm ? " with parent: " : ".", jm ? jm->name : "");
+
+ if (bootstrapper) {
+ bootstrapper->asport = asport;
+
+ jobmgr_log(jmr, LOG_DEBUG, "Bootstrapping new job manager with audit session %u", asport);
+ (void)jobmgr_assumes(jmr, job_dispatch(bootstrapper, true) != NULL);
+ } else {
+ jmr->req_asport = asport;
+ }
+
+ if (asport != MACH_PORT_NULL) {
+ (void)jobmgr_assumes_zero(jmr, launchd_mport_copy_send(asport));
+ }
+
+ if (jmr->parentmgr) {
+ runtime_add_weak_ref();
+ }
+
+ return jmr;
+
+out_bad:
+ if (jmr) {
+ jobmgr_remove(jmr);
+ if (jm == NULL) {
+ root_jobmgr = NULL;
+ }
+ }
+ return NULL;
+}
+
+jobmgr_t
+jobmgr_new_xpc_singleton_domain(jobmgr_t jm, name_t name)
+{
+ jobmgr_t new = NULL;
+
+ /* These job managers are basically singletons, so we use the root Mach
+ * bootstrap port as their requestor ports so they'll never go away.
+ */
+ mach_port_t req_port = root_jobmgr->jm_port;
+ if (jobmgr_assumes_zero(jm, launchd_mport_make_send(req_port)) == KERN_SUCCESS) {
+ new = jobmgr_new(root_jobmgr, req_port, MACH_PORT_NULL, false, name, true, MACH_PORT_NULL);
+ if (new) {
+ new->properties |= BOOTSTRAP_PROPERTY_XPC_SINGLETON;
+ new->properties |= BOOTSTRAP_PROPERTY_XPC_DOMAIN;
+ new->xpc_singleton = true;
+ }
+ }
+
+ return new;
+}
+
+jobmgr_t
+jobmgr_find_xpc_per_user_domain(jobmgr_t jm, uid_t uid)
+{
+ jobmgr_t jmi = NULL;
+ LIST_FOREACH(jmi, &_s_xpc_user_domains, xpc_le) {
+ if (jmi->req_euid == uid) {
+ return jmi;
+ }
+ }
+
+ name_t name;
+ (void)snprintf(name, sizeof(name), "com.apple.xpc.domain.peruser.%u", uid);
+ jmi = jobmgr_new_xpc_singleton_domain(jm, name);
+ if (jobmgr_assumes(jm, jmi != NULL)) {
+ /* We need to create a per-user launchd for this UID if there isn't one
+ * already so we can grab the bootstrap port.
+ */
+ job_t puj = jobmgr_lookup_per_user_context_internal(NULL, uid, &jmi->req_bsport);
+ if (jobmgr_assumes(jmi, puj != NULL)) {
+ (void)jobmgr_assumes_zero(jmi, launchd_mport_copy_send(puj->asport));
+ (void)jobmgr_assumes_zero(jmi, launchd_mport_copy_send(jmi->req_bsport));
+ jmi->shortdesc = "per-user";
+ jmi->req_asport = puj->asport;
+ jmi->req_asid = puj->asid;
+ jmi->req_euid = uid;
+ jmi->req_egid = -1;
+
+ LIST_INSERT_HEAD(&_s_xpc_user_domains, jmi, xpc_le);
+ } else {
+ jobmgr_remove(jmi);
+ }
+ }
+
+ return jmi;
+}
+
+jobmgr_t
+jobmgr_find_xpc_per_session_domain(jobmgr_t jm, au_asid_t asid)
+{
+ jobmgr_t jmi = NULL;
+ LIST_FOREACH(jmi, &_s_xpc_session_domains, xpc_le) {
+ if (jmi->req_asid == asid) {
+ return jmi;
+ }
+ }
+
+ name_t name;
+ (void)snprintf(name, sizeof(name), "com.apple.xpc.domain.persession.%i", asid);
+ jmi = jobmgr_new_xpc_singleton_domain(jm, name);
+ if (jobmgr_assumes(jm, jmi != NULL)) {
+ (void)jobmgr_assumes_zero(jmi, launchd_mport_make_send(root_jobmgr->jm_port));
+ jmi->shortdesc = "per-session";
+ jmi->req_bsport = root_jobmgr->jm_port;
+ (void)jobmgr_assumes_zero(jmi, audit_session_port(asid, &jmi->req_asport));
+ jmi->req_asid = asid;
+ jmi->req_euid = -1;
+ jmi->req_egid = -1;
+
+ LIST_INSERT_HEAD(&_s_xpc_session_domains, jmi, xpc_le);
+ } else {
+ jobmgr_remove(jmi);
+ }
+
+ return jmi;
+}
+
+job_t
+jobmgr_init_session(jobmgr_t jm, const char *session_type, bool sflag)
+{
+ const char *bootstrap_tool[] = { "/bin/launchctl", "bootstrap", "-S", session_type, sflag ? "-s" : NULL, NULL };
+ char thelabel[1000];
+ job_t bootstrapper;
+
+ snprintf(thelabel, sizeof(thelabel), "com.apple.launchctl.%s", session_type);
+ bootstrapper = job_new(jm, thelabel, NULL, bootstrap_tool);
+
+ if (jobmgr_assumes(jm, bootstrapper != NULL) && (jm->parentmgr || !pid1_magic)) {
+ bootstrapper->is_bootstrapper = true;
+ char buf[100];
+
+ // <rdar://problem/5042202> launchd-201: can't ssh in with AFP OD account (hangs)
+ snprintf(buf, sizeof(buf), "0x%X:0:0", getuid());
+ envitem_new(bootstrapper, "__CF_USER_TEXT_ENCODING", buf, false);
+ bootstrapper->weird_bootstrap = true;
+ (void)jobmgr_assumes(jm, job_setup_machport(bootstrapper));
+ } else if (bootstrapper && strncmp(session_type, VPROCMGR_SESSION_SYSTEM, sizeof(VPROCMGR_SESSION_SYSTEM)) == 0) {
+#if TARGET_OS_EMBEDDED
+ bootstrapper->pstype = POSIX_SPAWN_IOS_INTERACTIVE;
+#endif
+ bootstrapper->is_bootstrapper = true;
+ if (jobmgr_assumes(jm, pid1_magic)) {
+ // Have our system bootstrapper print out to the console.
+ bootstrapper->stdoutpath = strdup(_PATH_CONSOLE);
+ bootstrapper->stderrpath = strdup(_PATH_CONSOLE);
+
+ if (launchd_console) {
+ (void)jobmgr_assumes_zero_p(jm, kevent_mod((uintptr_t)fileno(launchd_console), EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_REVOKE, 0, jm));
+ }
+ }
+ }
+
+ jm->session_initialized = true;
+ return bootstrapper;
+}
+
+jobmgr_t
+jobmgr_delete_anything_with_port(jobmgr_t jm, mach_port_t port)
+{
+ struct machservice *ms, *next_ms;
+ jobmgr_t jmi, jmn;
+
+ /* Mach ports, unlike Unix descriptors, are reference counted. In other
+ * words, when some program hands us a second or subsequent send right to a
+ * port we already have open, the Mach kernel gives us the same port number
+ * back and increments an reference count associated with the port. This
+ * This forces us, when discovering that a receive right at the other end
+ * has been deleted, to wander all of our objects to see what weird places
+ * clients might have handed us the same send right to use.
+ */
+
+ if (jm == root_jobmgr) {
+ if (port == inherited_bootstrap_port) {
+ (void)jobmgr_assumes_zero(jm, launchd_mport_deallocate(port));
+ inherited_bootstrap_port = MACH_PORT_NULL;
+
+ return jobmgr_shutdown(jm);
+ }
+
+ LIST_FOREACH_SAFE(ms, &port_hash[HASH_PORT(port)], port_hash_sle, next_ms) {
+ if (ms->port == port && !ms->recv) {
+ machservice_delete(ms->job, ms, true);
+ }
+ }
+ }
+
+ SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) {
+ jobmgr_delete_anything_with_port(jmi, port);
+ }
+
+ if (jm->req_port == port) {
+ jobmgr_log(jm, LOG_DEBUG, "Request port died: %i", MACH_PORT_INDEX(port));
+ return jobmgr_shutdown(jm);
+ }
+
+ return jm;
+}
+
+struct machservice *
+jobmgr_lookup_service(jobmgr_t jm, const char *name, bool check_parent, pid_t target_pid)
+{
+ struct machservice *ms;
+ job_t target_j;
+
+ jobmgr_log(jm, LOG_DEBUG, "Looking up %sservice %s", target_pid ? "per-PID " : "", name);
+
+ if (target_pid) {
+ /* This is a hack to let FileSyncAgent look up per-PID Mach services from the Background
+ * bootstrap in other bootstraps.
+ */
+
+ // Start in the given bootstrap.
+ if (unlikely((target_j = jobmgr_find_by_pid(jm, target_pid, false)) == NULL)) {
+ // If we fail, do a deep traversal.
+ if (unlikely((target_j = jobmgr_find_by_pid_deep(root_jobmgr, target_pid, true)) == NULL)) {
+ jobmgr_log(jm, LOG_DEBUG, "Didn't find PID %i", target_pid);
+ return NULL;
+ }
+ }
+
+ SLIST_FOREACH(ms, &target_j->machservices, sle) {
+ if (ms->per_pid && strcmp(name, ms->name) == 0) {
+ return ms;
+ }
+ }
+
+ job_log(target_j, LOG_DEBUG, "Didn't find per-PID Mach service: %s", name);
+ return NULL;
+ }
+
+ jobmgr_t where2look = jm;
+ // XPC domains are separate from Mach bootstraps.
+ if (!(jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
+ if (launchd_flat_mach_namespace && !(jm->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET)) {
+ where2look = root_jobmgr;
+ }
+ }
+
+ LIST_FOREACH(ms, &where2look->ms_hash[hash_ms(name)], name_hash_sle) {
+ if (!ms->per_pid && strcmp(name, ms->name) == 0) {
+ return ms;
+ }
+ }
+
+ if (jm->parentmgr == NULL || !check_parent) {
+ return NULL;
+ }
+
+ return jobmgr_lookup_service(jm->parentmgr, name, true, 0);
+}
+
+mach_port_t
+machservice_port(struct machservice *ms)
+{
+ return ms->port;
+}
+
+job_t
+machservice_job(struct machservice *ms)
+{
+ return ms->job;
+}
+
+bool
+machservice_hidden(struct machservice *ms)
+{
+ return ms->hide;
+}
+
+bool
+machservice_active(struct machservice *ms)
+{
+ return ms->isActive;
+}
+
+const char *
+machservice_name(struct machservice *ms)
+{
+ return ms->name;
+}
+
+void
+machservice_drain_port(struct machservice *ms)
+{
+ bool drain_one = ms->drain_one_on_crash;
+ bool drain_all = ms->drain_all_on_crash;
+
+ if (!job_assumes(ms->job, (drain_one || drain_all) == true)) {
+ return;
+ }
+
+ job_log(ms->job, LOG_INFO, "Draining %s...", ms->name);
+
+ char req_buff[sizeof(union __RequestUnion__catch_mach_exc_subsystem) * 2];
+ char rep_buff[sizeof(union __ReplyUnion__catch_mach_exc_subsystem)];
+ mig_reply_error_t *req_hdr = (mig_reply_error_t *)&req_buff;
+ mig_reply_error_t *rep_hdr = (mig_reply_error_t *)&rep_buff;
+
+ mach_msg_return_t mr = ~MACH_MSG_SUCCESS;
+
+ do {
+ /* This should be a direct check on the Mach service to see if it's an exception-handling
+ * port, and it will break things if ReportCrash or SafetyNet start advertising other
+ * Mach services. But for now, it should be okay.
+ */
+ if (ms->job->alt_exc_handler || ms->job->internal_exc_handler) {
+ mr = launchd_exc_runtime_once(ms->port, sizeof(req_buff), sizeof(rep_buff), req_hdr, rep_hdr, 0);
+ } else {
+ mach_msg_options_t options = MACH_RCV_MSG |
+ MACH_RCV_TIMEOUT ;
+
+ mr = mach_msg((mach_msg_header_t *)req_hdr, options, 0, sizeof(req_buff), ms->port, 0, MACH_PORT_NULL);
+ switch (mr) {
+ case MACH_MSG_SUCCESS:
+ mach_msg_destroy((mach_msg_header_t *)req_hdr);
+ break;
+ case MACH_RCV_TIMED_OUT:
+ break;
+ case MACH_RCV_TOO_LARGE:
+ launchd_syslog(LOG_WARNING, "Tried to receive message that was larger than %lu bytes", sizeof(req_buff));
+ break;
+ default:
+ break;
+ }
+ }
+ } while (drain_all && mr != MACH_RCV_TIMED_OUT);
+}
+
+void
+machservice_delete(job_t j, struct machservice *ms, bool port_died)
+{
+ if (ms->alias) {
+ /* HACK: Egregious code duplication. But dealing with aliases is a
+ * pretty simple affair since they can't and shouldn't have any complex
+ * behaviors associated with them.
+ */
+ LIST_REMOVE(ms, name_hash_sle);
+ SLIST_REMOVE(&j->machservices, ms, machservice, sle);
+ free(ms);
+ return;
+ }
+
+ if (unlikely(ms->debug_on_close)) {
+ job_log(j, LOG_NOTICE, "About to enter kernel debugger because of Mach port: 0x%x", ms->port);
+ (void)job_assumes_zero(j, host_reboot(mach_host_self(), HOST_REBOOT_DEBUGGER));
+ }
+
+ if (ms->recv && job_assumes(j, !machservice_active(ms))) {
+ job_log(j, LOG_DEBUG, "Closing receive right for %s", ms->name);
+ (void)job_assumes_zero(j, launchd_mport_close_recv(ms->port));
+ }
+
+ (void)job_assumes_zero(j, launchd_mport_deallocate(ms->port));
+
+ if (unlikely(ms->port == the_exception_server)) {
+ the_exception_server = 0;
+ }
+
+ job_log(j, LOG_DEBUG, "Mach service deleted%s: %s", port_died ? " (port died)" : "", ms->name);
+
+ if (ms->special_port_num) {
+ SLIST_REMOVE(&special_ports, ms, machservice, special_port_sle);
+ }
+ SLIST_REMOVE(&j->machservices, ms, machservice, sle);
+
+ if (!(j->dedicated_instance || ms->event_channel)) {
+ LIST_REMOVE(ms, name_hash_sle);
+ }
+ LIST_REMOVE(ms, port_hash_sle);
+
+ free(ms);
+}
+
+void
+machservice_request_notifications(struct machservice *ms)
+{
+ mach_msg_id_t which = MACH_NOTIFY_DEAD_NAME;
+
+ ms->isActive = true;
+
+ if (ms->recv) {
+ which = MACH_NOTIFY_PORT_DESTROYED;
+ job_checkin(ms->job);
+ }
+
+ (void)job_assumes_zero(ms->job, launchd_mport_notify_req(ms->port, which));
+}
+
+#define NELEM(x) (sizeof(x)/sizeof(x[0]))
+#define END_OF(x) (&(x)[NELEM(x)])
+
+char **
+mach_cmd2argv(const char *string)
+{
+ char *argv[100], args[1000];
+ const char *cp;
+ char *argp = args, term, **argv_ret, *co;
+ unsigned int nargs = 0, i;
+
+ for (cp = string; *cp;) {
+ while (isspace(*cp))
+ cp++;
+ term = (*cp == '"') ? *cp++ : '\0';
+ if (nargs < NELEM(argv)) {
+ argv[nargs++] = argp;
+ }
+ while (*cp && (term ? *cp != term : !isspace(*cp)) && argp < END_OF(args)) {
+ if (*cp == '\\') {
+ cp++;
+ }
+ *argp++ = *cp;
+ if (*cp) {
+ cp++;
+ }
+ }
+ *argp++ = '\0';
+ }
+ argv[nargs] = NULL;
+
+ if (nargs == 0) {
+ return NULL;
+ }
+
+ argv_ret = malloc((nargs + 1) * sizeof(char *) + strlen(string) + 1);
+
+ if (!argv_ret) {
+ (void)osx_assumes_zero(errno);
+ return NULL;
+ }
+
+ co = (char *)argv_ret + (nargs + 1) * sizeof(char *);
+
+ for (i = 0; i < nargs; i++) {
+ strcpy(co, argv[i]);
+ argv_ret[i] = co;
+ co += strlen(argv[i]) + 1;
+ }
+ argv_ret[i] = NULL;
+
+ return argv_ret;
+}
+
+void
+job_checkin(job_t j)
+{
+ j->checkedin = true;
+}
+
+bool job_is_god(job_t j)
+{
+ return j->embedded_god;
+}
+
+bool
+job_ack_port_destruction(mach_port_t p)
+{
+ struct machservice *ms;
+ job_t j;
+
+ LIST_FOREACH(ms, &port_hash[HASH_PORT(p)], port_hash_sle) {
+ if (ms->recv && (ms->port == p)) {
+ break;
+ }
+ }
+
+ if (!ms) {
+ launchd_syslog(LOG_WARNING, "Could not find MachService to match receive right: 0x%x", p);
+ return false;
+ }
+
+ j = ms->job;
+
+ jobmgr_log(root_jobmgr, LOG_DEBUG, "Receive right returned to us: %s", ms->name);
+
+ /* Without being the exception handler, NOTE_EXIT is our only way to tell if
+ * the job crashed, and we can't rely on NOTE_EXIT always being processed
+ * after all the job's receive rights have been returned.
+ *
+ * So when we get receive rights back, check to see if the job has been
+ * reaped yet. If not, then we add this service to a list of services to be
+ * drained on crash if it's requested that behavior. So, for a job with N
+ * receive rights all requesting that they be drained on crash, we can
+ * safely handle the following sequence of events.
+ *
+ * ReceiveRight0Returned
+ * ReceiveRight1Returned
+ * ReceiveRight2Returned
+ * NOTE_EXIT (reap, get exit status)
+ * ReceiveRight3Returned
+ * .
+ * .
+ * .
+ * ReceiveRight(N - 1)Returned
+ */
+ if (ms->drain_one_on_crash || ms->drain_all_on_crash) {
+ if (j->crashed && j->reaped) {
+ job_log(j, LOG_DEBUG, "Job has crashed. Draining port...");
+ machservice_drain_port(ms);
+ } else if (!(j->crashed || j->reaped)) {
+ job_log(j, LOG_DEBUG, "Job's exit status is still unknown. Deferring drain.");
+ }
+ }
+
+ ms->isActive = false;
+ if (ms->delete_on_destruction) {
+ machservice_delete(j, ms, false);
+ } else if (ms->reset) {
+ machservice_resetport(j, ms);
+ }
+
+ machservice_stamp_port(j, ms);
+ job_dispatch(j, false);
+
+ root_jobmgr = jobmgr_do_garbage_collection(root_jobmgr);
+
+ return true;
+}
+
+void
+job_ack_no_senders(job_t j)
+{
+ j->priv_port_has_senders = false;
+
+ (void)job_assumes_zero(j, launchd_mport_close_recv(j->j_port));
+ j->j_port = 0;
+
+ job_log(j, LOG_DEBUG, "No more senders on privileged Mach bootstrap port");
+
+ job_dispatch(j, false);
+}
+
+bool
+semaphoreitem_new(job_t j, semaphore_reason_t why, const char *what)
+{
+ struct semaphoreitem *si;
+ size_t alloc_sz = sizeof(struct semaphoreitem);
+
+ if (what) {
+ alloc_sz += strlen(what) + 1;
+ }
+
+ if (job_assumes(j, si = calloc(1, alloc_sz)) == NULL) {
+ return false;
+ }
+
+ si->why = why;
+
+ if (what) {
+ strcpy(si->what_init, what);
+ }
+
+ SLIST_INSERT_HEAD(&j->semaphores, si, sle);
+
+ if ((why == OTHER_JOB_ENABLED || why == OTHER_JOB_DISABLED) && !j->nosy) {
+ job_log(j, LOG_DEBUG, "Job is interested in \"%s\".", what);
+ SLIST_INSERT_HEAD(&s_curious_jobs, j, curious_jobs_sle);
+ j->nosy = true;
+ }
+
+ semaphoreitem_runtime_mod_ref(si, true);
+
+ return true;
+}
+
+void
+semaphoreitem_runtime_mod_ref(struct semaphoreitem *si, bool add)
+{
+ /*
+ * External events need to be tracked.
+ * Internal events do NOT need to be tracked.
+ */
+
+ switch (si->why) {
+ case SUCCESSFUL_EXIT:
+ case FAILED_EXIT:
+ case OTHER_JOB_ENABLED:
+ case OTHER_JOB_DISABLED:
+ case OTHER_JOB_ACTIVE:
+ case OTHER_JOB_INACTIVE:
+ return;
+ default:
+ break;
+ }
+
+ if (add) {
+ runtime_add_weak_ref();
+ } else {
+ runtime_del_weak_ref();
+ }
+}
+
+void
+semaphoreitem_delete(job_t j, struct semaphoreitem *si)
+{
+ semaphoreitem_runtime_mod_ref(si, false);
+
+ SLIST_REMOVE(&j->semaphores, si, semaphoreitem, sle);
+
+ // We'll need to rethink this if it ever becomes possible to dynamically add or remove semaphores.
+ if ((si->why == OTHER_JOB_ENABLED || si->why == OTHER_JOB_DISABLED) && j->nosy) {
+ j->nosy = false;
+ SLIST_REMOVE(&s_curious_jobs, j, job_s, curious_jobs_sle);
+ }
+
+ free(si);
+}
+
+void
+semaphoreitem_setup_dict_iter(launch_data_t obj, const char *key, void *context)
+{
+ struct semaphoreitem_dict_iter_context *sdic = context;
+ semaphore_reason_t why;
+
+ why = launch_data_get_bool(obj) ? sdic->why_true : sdic->why_false;
+
+ semaphoreitem_new(sdic->j, why, key);
+}
+
+void
+semaphoreitem_setup(launch_data_t obj, const char *key, void *context)
+{
+ struct semaphoreitem_dict_iter_context sdic = { context, 0, 0 };
+ job_t j = context;
+ semaphore_reason_t why;
+
+ switch (launch_data_get_type(obj)) {
+ case LAUNCH_DATA_BOOL:
+ if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_NETWORKSTATE) == 0) {
+ why = launch_data_get_bool(obj) ? NETWORK_UP : NETWORK_DOWN;
+ semaphoreitem_new(j, why, NULL);
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT) == 0) {
+ why = launch_data_get_bool(obj) ? SUCCESSFUL_EXIT : FAILED_EXIT;
+ semaphoreitem_new(j, why, NULL);
+ j->start_pending = true;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_AFTERINITIALDEMAND) == 0) {
+ j->needs_kickoff = launch_data_get_bool(obj);
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_CRASHED) == 0) {
+ why = launch_data_get_bool(obj) ? CRASHED : DID_NOT_CRASH;
+ semaphoreitem_new(j, why, NULL);
+ j->start_pending = true;
+ } else {
+ job_log(j, LOG_ERR, "Unrecognized KeepAlive attribute: %s", key);
+ }
+ break;
+ case LAUNCH_DATA_DICTIONARY:
+ if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBACTIVE) == 0) {
+ sdic.why_true = OTHER_JOB_ACTIVE;
+ sdic.why_false = OTHER_JOB_INACTIVE;
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBENABLED) == 0) {
+ sdic.why_true = OTHER_JOB_ENABLED;
+ sdic.why_false = OTHER_JOB_DISABLED;
+ } else {
+ job_log(j, LOG_ERR, "Unrecognized KeepAlive attribute: %s", key);
+ break;
+ }
+
+ launch_data_dict_iterate(obj, semaphoreitem_setup_dict_iter, &sdic);
+ break;
+ default:
+ job_log(j, LOG_ERR, "Unrecognized KeepAlive type: %u", launch_data_get_type(obj));
+ break;
+ }
+}
+
+bool
+externalevent_new(job_t j, struct eventsystem *sys, const char *evname, xpc_object_t event)
+{
+ if (j->event_monitor) {
+ job_log(j, LOG_ERR, "The event monitor job cannot use LaunchEvents or XPC Events.");
+ return false;
+ }
+
+ struct externalevent *ee = (struct externalevent *)calloc(1, sizeof(struct externalevent) + strlen(evname) + 1);
+ if (!ee) {
+ return false;
+ }
+
+ ee->event = xpc_retain(event);
+ (void)strcpy(ee->name, evname);
+ ee->job = j;
+ ee->id = sys->curid;
+ ee->sys = sys;
+ ee->state = false;
+ ee->wanted_state = true;
+ sys->curid++;
+
+ if (sys == _launchd_support_system) {
+ ee->internal = true;
+ }
+
+ LIST_INSERT_HEAD(&j->events, ee, job_le);
+ LIST_INSERT_HEAD(&sys->events, ee, sys_le);
+
+ job_log(j, LOG_DEBUG, "New event: %s/%s", sys->name, evname);
+
+ eventsystem_ping();
+ return true;
+}
+
+void
+externalevent_delete(struct externalevent *ee)
+{
+ xpc_release(ee->event);
+ LIST_REMOVE(ee, job_le);
+ LIST_REMOVE(ee, sys_le);
+
+ free(ee);
+
+ eventsystem_ping();
+}
+
+void
+externalevent_setup(launch_data_t obj, const char *key, void *context)
+{
+ /* This method can ONLY be called on the job_import() path, as it assumes
+ * the input is a launch_data_t.
+ */
+ struct externalevent_iter_ctx *ctx = (struct externalevent_iter_ctx *)context;
+
+ xpc_object_t xobj = ld2xpc(obj);
+ if (xobj) {
+ job_log(ctx->j, LOG_DEBUG, "Importing stream/event: %s/%s", ctx->sys->name, key);
+ externalevent_new(ctx->j, ctx->sys, key, xobj);
+ xpc_release(xobj);
+ } else {
+ job_log(ctx->j, LOG_ERR, "Could not import event for job: %s", key);
+ }
+}
+
+struct externalevent *
+externalevent_find(const char *sysname, uint64_t id)
+{
+ struct externalevent *ei = NULL;
+
+ struct eventsystem *es = eventsystem_find(sysname);
+ if (es != NULL) {
+ LIST_FOREACH(ei, &es->events, sys_le) {
+ if (ei->id == id) {
+ break;
+ }
+ }
+ } else {
+ launchd_syslog(LOG_ERR, "Could not find event system: %s", sysname);
+ }
+
+ return ei;
+}
+
+struct eventsystem *
+eventsystem_new(const char *name)
+{
+ struct eventsystem *es = (struct eventsystem *)calloc(1, sizeof(struct eventsystem) + strlen(name) + 1);
+ if (es != NULL) {
+ es->curid = 1;
+ (void)strcpy(es->name, name);
+ LIST_INSERT_HEAD(&_s_event_systems, es, global_le);
+ } else {
+ (void)osx_assumes_zero(errno);
+ }
+
+ return es;
+}
+
+void
+eventsystem_delete(struct eventsystem *es)
+{
+ struct externalevent *ei = NULL;
+ while ((ei = LIST_FIRST(&es->events))) {
+ externalevent_delete(ei);
+ }
+
+ LIST_REMOVE(es, global_le);
+
+ free(es);
+}
+
+void
+eventsystem_setup(launch_data_t obj, const char *key, void *context)
+{
+ job_t j = (job_t)context;
+ if (!job_assumes(j, launch_data_get_type(obj) == LAUNCH_DATA_DICTIONARY)) {
+ return;
+ }
+
+ struct eventsystem *sys = eventsystem_find(key);
+ if (unlikely(sys == NULL)) {
+ sys = eventsystem_new(key);
+ job_log(j, LOG_DEBUG, "New event system: %s", key);
+ }
+
+ if (job_assumes(j, sys != NULL)) {
+ struct externalevent_iter_ctx ctx = {
+ .j = j,
+ .sys = sys,
+ };
+
+ job_log(j, LOG_DEBUG, "Importing events for stream: %s", key);
+ launch_data_dict_iterate(obj, externalevent_setup, &ctx);
+ }
+}
+
+struct eventsystem *
+eventsystem_find(const char *name)
+{
+ struct eventsystem *esi = NULL;
+ LIST_FOREACH(esi, &_s_event_systems, global_le) {
+ if (strcmp(name, esi->name) == 0) {
+ break;
+ }
+ }
+
+ return esi;
+}
+
+void
+eventsystem_ping(void)
+{
+ if (!_launchd_event_monitor) {
+ return;
+ }
+
+ if (!_launchd_event_monitor->p) {
+ (void)job_dispatch(_launchd_event_monitor, true);
+ } else {
+ if (_launchd_event_monitor->event_monitor_ready2signal) {
+ (void)job_assumes_zero_p(_launchd_event_monitor, kill(_launchd_event_monitor->p, SIGUSR1));
+ }
+ }
+}
+
+void
+jobmgr_dispatch_all_semaphores(jobmgr_t jm)
+{
+ jobmgr_t jmi, jmn;
+ job_t ji, jn;
+
+
+ SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) {
+ jobmgr_dispatch_all_semaphores(jmi);
+ }
+
+ LIST_FOREACH_SAFE(ji, &jm->jobs, sle, jn) {
+ if (!SLIST_EMPTY(&ji->semaphores)) {
+ job_dispatch(ji, false);
+ }
+ }
+}
+
+time_t
+cronemu(int mon, int mday, int hour, int min)
+{
+ struct tm workingtm;
+ time_t now;
+
+ now = time(NULL);
+ workingtm = *localtime(&now);
+
+ workingtm.tm_isdst = -1;
+ workingtm.tm_sec = 0;
+ workingtm.tm_min++;
+
+ while (!cronemu_mon(&workingtm, mon, mday, hour, min)) {
+ workingtm.tm_year++;
+ workingtm.tm_mon = 0;
+ workingtm.tm_mday = 1;
+ workingtm.tm_hour = 0;
+ workingtm.tm_min = 0;
+ mktime(&workingtm);
+ }
+
+ return mktime(&workingtm);
+}
+
+time_t
+cronemu_wday(int wday, int hour, int min)
+{
+ struct tm workingtm;
+ time_t now;
+
+ now = time(NULL);
+ workingtm = *localtime(&now);
+
+ workingtm.tm_isdst = -1;
+ workingtm.tm_sec = 0;
+ workingtm.tm_min++;
+
+ if (wday == 7) {
+ wday = 0;
+ }
+
+ while (!(workingtm.tm_wday == wday && cronemu_hour(&workingtm, hour, min))) {
+ workingtm.tm_mday++;
+ workingtm.tm_hour = 0;
+ workingtm.tm_min = 0;
+ mktime(&workingtm);
+ }
+
+ return mktime(&workingtm);
+}
+
+bool
+cronemu_mon(struct tm *wtm, int mon, int mday, int hour, int min)
+{
+ if (mon == -1) {
+ struct tm workingtm = *wtm;
+ int carrytest;
+
+ while (!cronemu_mday(&workingtm, mday, hour, min)) {
+ workingtm.tm_mon++;
+ workingtm.tm_mday = 1;
+ workingtm.tm_hour = 0;
+ workingtm.tm_min = 0;
+ carrytest = workingtm.tm_mon;
+ mktime(&workingtm);
+ if (carrytest != workingtm.tm_mon) {
+ return false;
+ }
+ }
+ *wtm = workingtm;
+ return true;
+ }
+
+ if (mon < wtm->tm_mon) {
+ return false;
+ }
+
+ if (mon > wtm->tm_mon) {
+ wtm->tm_mon = mon;
+ wtm->tm_mday = 1;
+ wtm->tm_hour = 0;
+ wtm->tm_min = 0;
+ }
+
+ return cronemu_mday(wtm, mday, hour, min);
+}
+
+bool
+cronemu_mday(struct tm *wtm, int mday, int hour, int min)
+{
+ if (mday == -1) {
+ struct tm workingtm = *wtm;
+ int carrytest;
+
+ while (!cronemu_hour(&workingtm, hour, min)) {
+ workingtm.tm_mday++;
+ workingtm.tm_hour = 0;
+ workingtm.tm_min = 0;
+ carrytest = workingtm.tm_mday;
+ mktime(&workingtm);
+ if (carrytest != workingtm.tm_mday) {
+ return false;
+ }
+ }
+ *wtm = workingtm;
+ return true;
+ }
+
+ if (mday < wtm->tm_mday) {
+ return false;
+ }
+
+ if (mday > wtm->tm_mday) {
+ wtm->tm_mday = mday;
+ wtm->tm_hour = 0;
+ wtm->tm_min = 0;
+ }
+
+ return cronemu_hour(wtm, hour, min);
+}
+
+bool
+cronemu_hour(struct tm *wtm, int hour, int min)
+{
+ if (hour == -1) {
+ struct tm workingtm = *wtm;
+ int carrytest;
+
+ while (!cronemu_min(&workingtm, min)) {
+ workingtm.tm_hour++;
+ workingtm.tm_min = 0;
+ carrytest = workingtm.tm_hour;
+ mktime(&workingtm);
+ if (carrytest != workingtm.tm_hour) {
+ return false;
+ }
+ }
+ *wtm = workingtm;
+ return true;
+ }
+
+ if (hour < wtm->tm_hour) {
+ return false;
+ }
+
+ if (hour > wtm->tm_hour) {
+ wtm->tm_hour = hour;
+ wtm->tm_min = 0;
+ }
+
+ return cronemu_min(wtm, min);
+}
+
+bool
+cronemu_min(struct tm *wtm, int min)
+{
+ if (min == -1) {
+ return true;
+ }
+
+ if (min < wtm->tm_min) {
+ return false;
+ }
+
+ if (min > wtm->tm_min) {
+ wtm->tm_min = min;
+ }
+
+ return true;
+}
+
+kern_return_t
+job_mig_create_server(job_t j, cmd_t server_cmd, uid_t server_uid, boolean_t on_demand, mach_port_t *server_portp)
+{
+ struct ldcred *ldc = runtime_get_caller_creds();
+ job_t js;
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (unlikely(j->deny_job_creation)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+#if HAVE_SANDBOX
+ const char **argv = (const char **)mach_cmd2argv(server_cmd);
+ if (unlikely(argv == NULL)) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+ if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_PATH, argv[0]) > 0)) {
+ free(argv);
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ free(argv);
+#endif
+
+ job_log(j, LOG_DEBUG, "Server create attempt: %s", server_cmd);
+
+ if (pid1_magic) {
+ if (ldc->euid || ldc->uid) {
+ job_log(j, LOG_WARNING, "Server create attempt moved to per-user launchd: %s", server_cmd);
+ return VPROC_ERR_TRY_PER_USER;
+ }
+ } else {
+ if (unlikely(server_uid != getuid())) {
+ job_log(j, LOG_WARNING, "Server create: \"%s\": As UID %d, we will not be able to switch to UID %d",
+ server_cmd, getuid(), server_uid);
+ }
+ server_uid = 0; // zero means "do nothing"
+ }
+
+ js = job_new_via_mach_init(j, server_cmd, server_uid, on_demand);
+
+ if (unlikely(js == NULL)) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ *server_portp = js->j_port;
+ return BOOTSTRAP_SUCCESS;
+}
+
+kern_return_t
+job_mig_send_signal(job_t j, mach_port_t srp, name_t targetlabel, int sig)
+{
+ struct ldcred *ldc = runtime_get_caller_creds();
+ job_t otherj;
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (unlikely(ldc->euid != 0 && ldc->euid != getuid()) || j->deny_job_creation) {
+#if TARGET_OS_EMBEDDED
+ if (!j->embedded_god) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+#else
+ return BOOTSTRAP_NOT_PRIVILEGED;
+#endif
+ }
+
+#if HAVE_SANDBOX
+ if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+#endif
+
+ if (unlikely(!(otherj = job_find(NULL, targetlabel)))) {
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+
+#if TARGET_OS_EMBEDDED
+ if (j->embedded_god) {
+ if (j->username && otherj->username) {
+ if (strcmp(j->username, otherj->username) != 0) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ } else {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ }
+#endif
+
+ if (sig == VPROC_MAGIC_UNLOAD_SIGNAL) {
+ bool do_block = otherj->p;
+
+ if (otherj->anonymous) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ job_remove(otherj);
+
+ if (do_block) {
+ job_log(j, LOG_DEBUG, "Blocking MIG return of job_remove(): %s", otherj->label);
+ // this is messy. We shouldn't access 'otherj' after job_remove(), but we check otherj->p first...
+ (void)job_assumes(otherj, waiting4removal_new(otherj, srp));
+ return MIG_NO_REPLY;
+ } else {
+ return 0;
+ }
+ } else if (otherj->p) {
+ (void)job_assumes_zero_p(j, kill2(otherj->p, sig));
+ }
+
+ return 0;
+}
+
+kern_return_t
+job_mig_log_forward(job_t j, vm_offset_t inval, mach_msg_type_number_t invalCnt)
+{
+ struct ldcred *ldc = runtime_get_caller_creds();
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (!job_assumes(j, j->per_user)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ return launchd_log_forward(ldc->euid, ldc->egid, inval, invalCnt);
+}
+
+kern_return_t
+job_mig_log_drain(job_t j, mach_port_t srp, vm_offset_t *outval, mach_msg_type_number_t *outvalCnt)
+{
+ struct ldcred *ldc = runtime_get_caller_creds();
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (unlikely(ldc->euid)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ return launchd_log_drain(srp, outval, outvalCnt);
+}
+
+kern_return_t
+job_mig_swap_complex(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey,
+ vm_offset_t inval, mach_msg_type_number_t invalCnt, vm_offset_t *outval,
+ mach_msg_type_number_t *outvalCnt)
+{
+ const char *action;
+ launch_data_t input_obj = NULL, output_obj = NULL;
+ size_t data_offset = 0;
+ size_t packed_size;
+ struct ldcred *ldc = runtime_get_caller_creds();
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (inkey && ldc->pid != j->p) {
+ if (ldc->euid && ldc->euid != getuid()) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ }
+
+ if (unlikely(inkey && outkey && !job_assumes(j, inkey == outkey))) {
+ return 1;
+ }
+
+ if (inkey && outkey) {
+ action = "Swapping";
+ } else if (inkey) {
+ action = "Setting";
+ } else {
+ action = "Getting";
+ }
+
+ job_log(j, LOG_DEBUG, "%s key: %u", action, inkey ? inkey : outkey);
+
+ *outvalCnt = 20 * 1024 * 1024;
+ mig_allocate(outval, *outvalCnt);
+ if (!job_assumes(j, *outval != 0)) {
+ return 1;
+ }
+
+ /* Note to future maintainers: launch_data_unpack() does NOT return a heap
+ * object. The data is decoded in-place. So do not call launch_data_free()
+ * on input_obj.
+ */
+ runtime_ktrace0(RTKT_LAUNCHD_DATA_UNPACK);
+ if (unlikely(invalCnt && !job_assumes(j, (input_obj = launch_data_unpack((void *)inval, invalCnt, NULL, 0, &data_offset, NULL)) != NULL))) {
+ goto out_bad;
+ }
+
+ char *store = NULL;
+ switch (outkey) {
+ case VPROC_GSK_ENVIRONMENT:
+ if (!job_assumes(j, (output_obj = launch_data_alloc(LAUNCH_DATA_DICTIONARY)))) {
+ goto out_bad;
+ }
+ jobmgr_export_env_from_other_jobs(j->mgr, output_obj);
+ runtime_ktrace0(RTKT_LAUNCHD_DATA_PACK);
+ if (!job_assumes(j, launch_data_pack(output_obj, (void *)*outval, *outvalCnt, NULL, NULL) != 0)) {
+ goto out_bad;
+ }
+ launch_data_free(output_obj);
+ break;
+ case VPROC_GSK_ALLJOBS:
+ if (!job_assumes(j, (output_obj = job_export_all()) != NULL)) {
+ goto out_bad;
+ }
+ ipc_revoke_fds(output_obj);
+ runtime_ktrace0(RTKT_LAUNCHD_DATA_PACK);
+ packed_size = launch_data_pack(output_obj, (void *)*outval, *outvalCnt, NULL, NULL);
+ if (!job_assumes(j, packed_size != 0)) {
+ goto out_bad;
+ }
+ launch_data_free(output_obj);
+ break;
+ case VPROC_GSK_MGR_NAME:
+ if (!job_assumes(j, (output_obj = launch_data_new_string(j->mgr->name)) != NULL)) {
+ goto out_bad;
+ }
+ packed_size = launch_data_pack(output_obj, (void *)*outval, *outvalCnt, NULL, NULL);
+ if (!job_assumes(j, packed_size != 0)) {
+ goto out_bad;
+ }
+
+ launch_data_free(output_obj);
+ break;
+ case VPROC_GSK_JOB_OVERRIDES_DB:
+ store = launchd_copy_persistent_store(LAUNCHD_PERSISTENT_STORE_DB, "overrides.plist");
+ if (!store || !job_assumes(j, (output_obj = launch_data_new_string(store)) != NULL)) {
+ free(store);
+ goto out_bad;
+ }
+
+ free(store);
+ packed_size = launch_data_pack(output_obj, (void *)*outval, *outvalCnt, NULL, NULL);
+ if (!job_assumes(j, packed_size != 0)) {
+ goto out_bad;
+ }
+
+ launch_data_free(output_obj);
+ break;
+ case VPROC_GSK_ZERO:
+ mig_deallocate(*outval, *outvalCnt);
+ *outval = 0;
+ *outvalCnt = 0;
+ break;
+ default:
+ goto out_bad;
+ }
+
+ mig_deallocate(inval, invalCnt);
+ return 0;
+
+out_bad:
+ mig_deallocate(inval, invalCnt);
+ if (*outval) {
+ mig_deallocate(*outval, *outvalCnt);
+ }
+ if (output_obj) {
+ launch_data_free(output_obj);
+ }
+
+ return 1;
+}
+
+kern_return_t
+job_mig_swap_integer(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, int64_t inval, int64_t *outval)
+{
+ const char *action;
+ kern_return_t kr = 0;
+ struct ldcred *ldc = runtime_get_caller_creds();
+ int oldmask;
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (inkey && ldc->pid != j->p) {
+ if (ldc->euid && ldc->euid != getuid()) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ }
+
+ if (unlikely(inkey && outkey && !job_assumes(j, inkey == outkey))) {
+ return 1;
+ }
+
+ if (inkey && outkey) {
+ action = "Swapping";
+ } else if (inkey) {
+ action = "Setting";
+ } else {
+ action = "Getting";
+ }
+
+ job_log(j, LOG_DEBUG, "%s key: %u", action, inkey ? inkey : outkey);
+
+ switch (outkey) {
+ case VPROC_GSK_ABANDON_PROCESS_GROUP:
+ *outval = j->abandon_pg;
+ break;
+ case VPROC_GSK_LAST_EXIT_STATUS:
+ *outval = j->last_exit_status;
+ break;
+ case VPROC_GSK_MGR_UID:
+ *outval = getuid();
+ break;
+ case VPROC_GSK_MGR_PID:
+ *outval = getpid();
+ break;
+ case VPROC_GSK_IS_MANAGED:
+ *outval = j->anonymous ? 0 : 1;
+ break;
+ case VPROC_GSK_BASIC_KEEPALIVE:
+ *outval = !j->ondemand;
+ break;
+ case VPROC_GSK_START_INTERVAL:
+ *outval = j->start_interval;
+ break;
+ case VPROC_GSK_IDLE_TIMEOUT:
+ *outval = j->timeout;
+ break;
+ case VPROC_GSK_EXIT_TIMEOUT:
+ *outval = j->exit_timeout;
+ break;
+ case VPROC_GSK_GLOBAL_LOG_MASK:
+ oldmask = runtime_setlogmask(LOG_UPTO(LOG_DEBUG));
+ *outval = oldmask;
+ runtime_setlogmask(oldmask);
+ break;
+ case VPROC_GSK_GLOBAL_UMASK:
+ oldmask = umask(0);
+ *outval = oldmask;
+ umask(oldmask);
+ break;
+ case VPROC_GSK_TRANSACTIONS_ENABLED:
+ job_log(j, LOG_DEBUG, "Reading EnableTransactions value.");
+ *outval = j->enable_transactions;
+ break;
+ case VPROC_GSK_WAITFORDEBUGGER:
+ *outval = j->wait4debugger;
+ break;
+ case VPROC_GSK_EMBEDDEDROOTEQUIVALENT:
+ *outval = j->embedded_god;
+ break;
+ case VPROC_GSK_ZERO:
+ *outval = 0;
+ break;
+ default:
+ kr = 1;
+ break;
+ }
+
+ switch (inkey) {
+ case VPROC_GSK_ABANDON_PROCESS_GROUP:
+ j->abandon_pg = (bool)inval;
+ break;
+ case VPROC_GSK_GLOBAL_ON_DEMAND:
+ job_log(j, LOG_DEBUG, "Job has set global on-demand mode to: %s", inval ? "true" : "false");
+ kr = job_set_global_on_demand(j, inval);
+ break;
+ case VPROC_GSK_BASIC_KEEPALIVE:
+ j->ondemand = !inval;
+ break;
+ case VPROC_GSK_START_INTERVAL:
+ if (inval > UINT32_MAX || inval < 0) {
+ kr = 1;
+ } else if (inval) {
+ if (j->start_interval == 0) {
+ runtime_add_weak_ref();
+ }
+ j->start_interval = (typeof(j->start_interval)) inval;
+ (void)job_assumes_zero_p(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, j->start_interval, j));
+ } else if (j->start_interval) {
+ (void)job_assumes_zero_p(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_DELETE, 0, 0, NULL));
+ if (j->start_interval != 0) {
+ runtime_del_weak_ref();
+ }
+ j->start_interval = 0;
+ }
+ break;
+ case VPROC_GSK_IDLE_TIMEOUT:
+ if (inval < 0 || inval > UINT32_MAX) {
+ kr = 1;
+ } else {
+ j->timeout = (typeof(j->timeout)) inval;
+ }
+ break;
+ case VPROC_GSK_EXIT_TIMEOUT:
+ if (inval < 0 || inval > UINT32_MAX) {
+ kr = 1;
+ } else {
+ j->exit_timeout = (typeof(j->exit_timeout)) inval;
+ }
+ break;
+ case VPROC_GSK_GLOBAL_LOG_MASK:
+ if (inval < 0 || inval > UINT32_MAX) {
+ kr = 1;
+ } else {
+ runtime_setlogmask((int) inval);
+ }
+ break;
+ case VPROC_GSK_GLOBAL_UMASK:
+ __OSX_COMPILETIME_ASSERT__(sizeof (mode_t) == 2);
+ if (inval < 0 || inval > UINT16_MAX) {
+ kr = 1;
+ } else {
+#if HAVE_SANDBOX
+ if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) {
+ kr = 1;
+ } else {
+ umask((mode_t) inval);
+ }
+#endif
+ }
+ break;
+ case VPROC_GSK_TRANSACTIONS_ENABLED:
+ /* No-op. */
+ break;
+ case VPROC_GSK_WEIRD_BOOTSTRAP:
+ if (job_assumes(j, j->weird_bootstrap)) {
+ job_log(j, LOG_DEBUG, "Unsetting weird bootstrap.");
+
+ mach_msg_size_t mxmsgsz = (typeof(mxmsgsz)) sizeof(union __RequestUnion__job_mig_job_subsystem);
+
+ if (job_mig_job_subsystem.maxsize > mxmsgsz) {
+ mxmsgsz = job_mig_job_subsystem.maxsize;
+ }
+
+ (void)job_assumes_zero(j, runtime_add_mport(j->mgr->jm_port, job_server));
+ j->weird_bootstrap = false;
+ }
+ break;
+ case VPROC_GSK_WAITFORDEBUGGER:
+ j->wait4debugger_oneshot = inval;
+ break;
+ case VPROC_GSK_PERUSER_SUSPEND:
+ if (job_assumes(j, pid1_magic && ldc->euid == 0)) {
+ mach_port_t junk = MACH_PORT_NULL;
+ job_t jpu = jobmgr_lookup_per_user_context_internal(j, (uid_t)inval, &junk);
+ if (job_assumes(j, jpu != NULL)) {
+ struct suspended_peruser *spi = NULL;
+ LIST_FOREACH(spi, &j->suspended_perusers, sle) {
+ if ((int64_t)(spi->j->mach_uid) == inval) {
+ job_log(j, LOG_WARNING, "Job tried to suspend per-user launchd for UID %lli twice.", inval);
+ break;
+ }
+ }
+
+ if (spi == NULL) {
+ job_log(j, LOG_INFO, "Job is suspending the per-user launchd for UID %lli.", inval);
+ spi = (struct suspended_peruser *)calloc(sizeof(struct suspended_peruser), 1);
+ if (job_assumes(j, spi != NULL)) {
+ /* Stop listening for events.
+ *
+ * See <rdar://problem/9014146>.
+ */
+ if (jpu->peruser_suspend_count == 0) {
+ job_ignore(jpu);
+ }
+
+ spi->j = jpu;
+ spi->j->peruser_suspend_count++;
+ LIST_INSERT_HEAD(&j->suspended_perusers, spi, sle);
+ job_stop(spi->j);
+ *outval = jpu->p;
+ } else {
+ kr = BOOTSTRAP_NO_MEMORY;
+ }
+ }
+ }
+ } else {
+ kr = 1;
+ }
+ break;
+ case VPROC_GSK_PERUSER_RESUME:
+ if (job_assumes(j, pid1_magic == true)) {
+ struct suspended_peruser *spi = NULL, *spt = NULL;
+ LIST_FOREACH_SAFE(spi, &j->suspended_perusers, sle, spt) {
+ if ((int64_t)(spi->j->mach_uid) == inval) {
+ spi->j->peruser_suspend_count--;
+ LIST_REMOVE(spi, sle);
+ job_log(j, LOG_INFO, "Job is resuming the per-user launchd for UID %lli.", inval);
+ break;
+ }
+ }
+
+ if (!job_assumes(j, spi != NULL)) {
+ job_log(j, LOG_WARNING, "Job tried to resume per-user launchd for UID %lli that it did not suspend.", inval);
+ kr = BOOTSTRAP_NOT_PRIVILEGED;
+ } else if (spi->j->peruser_suspend_count == 0) {
+ job_watch(spi->j);
+ job_dispatch(spi->j, false);
+ free(spi);
+ }
+ } else {
+ kr = 1;
+ }
+ break;
+ case VPROC_GSK_ZERO:
+ break;
+ default:
+ kr = 1;
+ break;
+ }
+
+ return kr;
+}
+
+kern_return_t
+job_mig_post_fork_ping(job_t j, task_t child_task, mach_port_t *asport)
+{
+ struct machservice *ms;
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ job_log(j, LOG_DEBUG, "Post fork ping.");
+
+ job_setup_exception_port(j, child_task);
+
+ SLIST_FOREACH(ms, &special_ports, special_port_sle) {
+ if (j->per_user && (ms->special_port_num != TASK_ACCESS_PORT)) {
+ // The TASK_ACCESS_PORT funny business is to workaround 5325399.
+ continue;
+ }
+
+ errno = task_set_special_port(child_task, ms->special_port_num, ms->port);
+ if (errno) {
+ if (errno == MACH_SEND_INVALID_DEST) {
+ job_log(j, LOG_WARNING, "Task died before special ports could be set.");
+ break;
+ }
+
+ int desired_log_level = LOG_ERR;
+ if (j->anonymous) {
+ // 5338127
+
+ desired_log_level = LOG_WARNING;
+
+ if (ms->special_port_num == TASK_SEATBELT_PORT) {
+ desired_log_level = LOG_DEBUG;
+ }
+ }
+
+ job_log(j, desired_log_level, "Could not setup Mach task special port %u: %s", ms->special_port_num, mach_error_string(errno));
+ }
+ }
+
+ /* MIG will not zero-initialize this pointer, so we must always do so. See
+ * <rdar://problem/8562593>.
+ */
+ *asport = MACH_PORT_NULL;
+#if !TARGET_OS_EMBEDDED
+ if (!j->anonymous) {
+ /* XPC services will spawn into the root security session by default.
+ * xpcproxy will switch them away if needed.
+ */
+ if (!(j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
+ job_log(j, LOG_DEBUG, "Returning session port: 0x%x", j->asport);
+ *asport = j->asport;
+ }
+ }
+#endif
+ (void)job_assumes_zero(j, launchd_mport_deallocate(child_task));
+
+ return 0;
+}
+
+kern_return_t
+job_mig_reboot2(job_t j, uint64_t flags)
+{
+ char who_started_the_reboot[2048] = "";
+ struct proc_bsdshortinfo proc;
+ struct ldcred *ldc = runtime_get_caller_creds();
+ pid_t pid_to_log;
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (unlikely(!pid1_magic)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+#if !TARGET_OS_EMBEDDED
+ if (unlikely(ldc->euid)) {
+#else
+ if (unlikely(ldc->euid) && !j->embedded_god) {
+#endif
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ for (pid_to_log = ldc->pid; pid_to_log; pid_to_log = proc.pbsi_ppid) {
+ size_t who_offset;
+ if (proc_pidinfo(pid_to_log, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
+ if (errno != ESRCH) {
+ (void)job_assumes_zero(j, errno);
+ }
+ return 1;
+ }
+
+ if (!job_assumes(j, pid_to_log != (pid_t)proc.pbsi_ppid)) {
+ job_log(j, LOG_WARNING, "Job which is its own parent started reboot.");
+ snprintf(who_started_the_reboot, sizeof(who_started_the_reboot), "%s[%u]->%s[%u]->%s[%u]->...", proc.pbsi_comm, pid_to_log, proc.pbsi_comm, pid_to_log, proc.pbsi_comm, pid_to_log);
+ break;
+ }
+
+ who_offset = strlen(who_started_the_reboot);
+ snprintf(who_started_the_reboot + who_offset, sizeof(who_started_the_reboot) - who_offset,
+ " %s[%u]%s", proc.pbsi_comm, pid_to_log, proc.pbsi_ppid ? " ->" : "");
+ }
+
+ root_jobmgr->reboot_flags = (int)flags;
+ job_log(j, LOG_DEBUG, "reboot2() initiated by:%s", who_started_the_reboot);
+ launchd_shutdown();
+
+ return 0;
+}
+
+kern_return_t
+job_mig_getsocket(job_t j, name_t spr)
+{
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (j->deny_job_creation) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+#if HAVE_SANDBOX
+ struct ldcred *ldc = runtime_get_caller_creds();
+ if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+#endif
+
+ ipc_server_init();
+
+ if (unlikely(!sockpath)) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ strncpy(spr, sockpath, sizeof(name_t));
+
+ return BOOTSTRAP_SUCCESS;
+}
+
+kern_return_t
+job_mig_log(job_t j, int pri, int err, logmsg_t msg)
+{
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if ((errno = err)) {
+ job_log_error(j, pri, "%s", msg);
+ } else {
+ job_log(j, pri, "%s", msg);
+ }
+
+ return 0;
+}
+
+void
+job_setup_per_user_directory(job_t j, uid_t uid, const char *path)
+{
+ struct stat sb;
+
+ bool created = false;
+ int r = stat(path, &sb);
+ if ((r == -1 && errno == ENOENT) || (r == 0 && !S_ISDIR(sb.st_mode))) {
+ if (r == 0) {
+ job_log(j, LOG_NOTICE, "File at location of per-user launchd directory is not a directory. Moving aside: %s", path);
+
+ char old[PATH_MAX];
+ snprintf(old, sizeof(old), "%s.movedaside", path);
+ (void)job_assumes_zero_p(j, rename(path, old));
+ }
+
+ (void)job_assumes_zero_p(j, mkdir(path, S_IRWXU));
+ (void)job_assumes_zero_p(j, chown(path, uid, 0));
+ created = true;
+ }
+
+ if (!created) {
+ if (sb.st_uid != uid) {
+ job_log(j, LOG_NOTICE, "Per-user launchd directory has improper user ownership. Repairing: %s", path);
+ (void)job_assumes_zero_p(j, chown(path, uid, 0));
+ }
+ if (sb.st_gid != 0) {
+ job_log(j, LOG_NOTICE, "Per-user launchd directory has improper group ownership. Repairing: %s", path);
+ (void)job_assumes_zero_p(j, chown(path, uid, 0));
+ }
+ if (sb.st_mode != (S_IRWXU | S_IFDIR)) {
+ job_log(j, LOG_NOTICE, "Per-user launchd directory has improper mode. Repairing: %s", path);
+ (void)job_assumes_zero_p(j, chmod(path, S_IRWXU));
+ }
+ }
+}
+
+void
+job_setup_per_user_directories(job_t j, uid_t uid, const char *label)
+{
+ char path[PATH_MAX];
+
+ (void)snprintf(path, sizeof(path), LAUNCHD_DB_PREFIX "/%s", label);
+ job_setup_per_user_directory(j, uid, path);
+
+ (void)snprintf(path, sizeof(path), LAUNCHD_LOG_PREFIX "/%s", label);
+ job_setup_per_user_directory(j, uid, path);
+}
+
+job_t
+jobmgr_lookup_per_user_context_internal(job_t j, uid_t which_user, mach_port_t *mp)
+{
+ job_t ji = NULL;
+ LIST_FOREACH(ji, &root_jobmgr->jobs, sle) {
+ if (!ji->per_user) {
+ continue;
+ }
+ if (ji->mach_uid != which_user) {
+ continue;
+ }
+ if (SLIST_EMPTY(&ji->machservices)) {
+ continue;
+ }
+ if (!SLIST_FIRST(&ji->machservices)->per_user_hack) {
+ continue;
+ }
+ break;
+ }
+
+ if (unlikely(ji == NULL)) {
+ struct machservice *ms;
+ char lbuf[1024];
+
+ job_log(j, LOG_DEBUG, "Creating per user launchd job for UID: %u", which_user);
+
+ sprintf(lbuf, "com.apple.launchd.peruser.%u", which_user);
+
+ ji = job_new(root_jobmgr, lbuf, "/sbin/launchd", NULL);
+
+ if (ji != NULL) {
+ auditinfo_addr_t auinfo = {
+ .ai_termid = {
+ .at_type = AU_IPv4
+ },
+ .ai_auid = which_user,
+ .ai_asid = AU_ASSIGN_ASID,
+ };
+
+ if (setaudit_addr(&auinfo, sizeof(auinfo)) == 0) {
+ job_log(ji, LOG_DEBUG, "Created new security session for per-user launchd: %u", auinfo.ai_asid);
+ (void)job_assumes(ji, (ji->asport = audit_session_self()) != MACH_PORT_NULL);
+
+ /* Kinda lame that we have to do this, but we can't create an
+ * audit session without joining it.
+ */
+ (void)job_assumes(ji, audit_session_join(launchd_audit_port));
+ ji->asid = auinfo.ai_asid;
+ } else {
+ job_log(ji, LOG_WARNING, "Could not set audit session!");
+ job_remove(ji);
+ return NULL;
+ }
+
+ ji->mach_uid = which_user;
+ ji->per_user = true;
+ ji->enable_transactions = true;
+ job_setup_per_user_directories(ji, which_user, lbuf);
+
+ if ((ms = machservice_new(ji, lbuf, mp, false)) == NULL) {
+ job_remove(ji);
+ ji = NULL;
+ } else {
+ ms->upfront = true;
+ ms->per_user_hack = true;
+ ms->hide = true;
+
+ ji = job_dispatch(ji, false);
+ }
+ }
+ } else {
+ *mp = machservice_port(SLIST_FIRST(&ji->machservices));
+ job_log(j, LOG_DEBUG, "Per user launchd job found for UID: %u", which_user);
+ }
+
+ return ji;
+}
+
+kern_return_t
+job_mig_lookup_per_user_context(job_t j, uid_t which_user, mach_port_t *up_cont)
+{
+ struct ldcred *ldc = runtime_get_caller_creds();
+ job_t jpu;
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (launchd_osinstaller) {
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+
+#if TARGET_OS_EMBEDDED
+ // There is no need for per-user launchd's on embedded.
+ job_log(j, LOG_ERR, "Per-user launchds are not supported on this platform.");
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+#endif
+
+#if HAVE_SANDBOX
+ if (unlikely(sandbox_check(ldc->pid, "mach-per-user-lookup", SANDBOX_FILTER_NONE) > 0)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+#endif
+
+ job_log(j, LOG_INFO, "Looking up per user launchd for UID: %u", which_user);
+
+ if (unlikely(!pid1_magic)) {
+ job_log(j, LOG_ERR, "Only PID 1 supports per user launchd lookups.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ if (ldc->euid || ldc->uid) {
+ which_user = ldc->euid ?: ldc->uid;
+ }
+
+ *up_cont = MACH_PORT_NULL;
+
+ jpu = jobmgr_lookup_per_user_context_internal(j, which_user, up_cont);
+
+ return 0;
+}
+
+kern_return_t
+job_mig_check_in2(job_t j, name_t servicename, mach_port_t *serviceportp, uuid_t instance_id, uint64_t flags)
+{
+ bool per_pid_service = flags & BOOTSTRAP_PER_PID_SERVICE;
+ bool strict = flags & BOOTSTRAP_STRICT_CHECKIN;
+ struct ldcred *ldc = runtime_get_caller_creds();
+ struct machservice *ms = NULL;
+ job_t jo;
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (j->dedicated_instance) {
+ struct machservice *msi = NULL;
+ SLIST_FOREACH(msi, &j->machservices, sle) {
+ if (strncmp(servicename, msi->name, sizeof(name_t) - 1) == 0) {
+ uuid_copy(instance_id, j->instance_id);
+ ms = msi;
+ break;
+ }
+ }
+ } else {
+ ms = jobmgr_lookup_service(j->mgr, servicename, false, per_pid_service ? ldc->pid : 0);
+ }
+
+ if (strict) {
+ if (likely(ms != NULL)) {
+ if (ms->job != j) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ } else if (ms->isActive) {
+ return BOOTSTRAP_SERVICE_ACTIVE;
+ }
+ } else {
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+ } else if (ms == NULL) {
+ if (job_assumes(j, !j->dedicated_instance)) {
+ *serviceportp = MACH_PORT_NULL;
+
+ if (unlikely((ms = machservice_new(j, servicename, serviceportp, per_pid_service)) == NULL)) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ // Treat this like a legacy job.
+ if (!j->legacy_mach_job) {
+ ms->isActive = true;
+ ms->recv = false;
+ }
+
+ if (!(j->anonymous || j->legacy_LS_job || j->legacy_mach_job)) {
+ job_log(j, LOG_APPLEONLY, "Please add the following service to the configuration file for this job: %s", servicename);
+ }
+ } else {
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+ } else {
+ if (unlikely((jo = machservice_job(ms)) != j)) {
+ static pid_t last_warned_pid;
+
+ if (last_warned_pid != ldc->pid) {
+ job_log(jo, LOG_WARNING, "The following job tried to hijack the service \"%s\" from this job: %s", servicename, j->label);
+ last_warned_pid = ldc->pid;
+ }
+
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ if (unlikely(machservice_active(ms))) {
+ job_log(j, LOG_WARNING, "Check-in of Mach service failed. Already active: %s", servicename);
+ return BOOTSTRAP_SERVICE_ACTIVE;
+ }
+ }
+
+ job_checkin(j);
+ machservice_request_notifications(ms);
+
+ job_log(j, LOG_INFO, "Check-in of service: %s", servicename);
+
+ *serviceportp = machservice_port(ms);
+ return BOOTSTRAP_SUCCESS;
+}
+
+kern_return_t
+job_mig_register2(job_t j, name_t servicename, mach_port_t serviceport, uint64_t flags)
+{
+ struct machservice *ms;
+ struct ldcred *ldc = runtime_get_caller_creds();
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (!(flags & BOOTSTRAP_PER_PID_SERVICE) && !j->legacy_LS_job) {
+ job_log(j, LOG_APPLEONLY, "Performance: bootstrap_register() is deprecated. Service: %s", servicename);
+ }
+
+ job_log(j, LOG_DEBUG, "%sMach service registration attempt: %s", flags & BOOTSTRAP_PER_PID_SERVICE ? "Per PID " : "", servicename);
+
+ // 5641783 for the embedded hack
+#if !TARGET_OS_EMBEDDED
+ /*
+ * From a per-user/session launchd's perspective, SecurityAgent (UID
+ * 92) is a rogue application (not our UID, not root and not a child of
+ * us). We'll have to reconcile this design friction at a later date.
+ */
+ if (unlikely(j->anonymous && j->mgr->parentmgr == NULL && ldc->uid != 0 && ldc->uid != getuid() && ldc->uid != 92)) {
+ if (pid1_magic) {
+ return VPROC_ERR_TRY_PER_USER;
+ } else {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ }
+#endif
+
+ ms = jobmgr_lookup_service(j->mgr, servicename, false, flags & BOOTSTRAP_PER_PID_SERVICE ? ldc->pid : 0);
+
+ if (unlikely(ms)) {
+ if (machservice_job(ms) != j) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ if (machservice_active(ms)) {
+ job_log(j, LOG_DEBUG, "Mach service registration failed. Already active: %s", servicename);
+ return BOOTSTRAP_SERVICE_ACTIVE;
+ }
+ if (ms->recv && (serviceport != MACH_PORT_NULL)) {
+ job_log(j, LOG_ERR, "bootstrap_register() erroneously called instead of bootstrap_check_in(). Mach service: %s", servicename);
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ job_checkin(j);
+ machservice_delete(j, ms, false);
+ }
+
+ if (likely(serviceport != MACH_PORT_NULL)) {
+ if (likely(ms = machservice_new(j, servicename, &serviceport, flags & BOOTSTRAP_PER_PID_SERVICE ? true : false))) {
+ machservice_request_notifications(ms);
+ } else {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+ }
+
+
+ return BOOTSTRAP_SUCCESS;
+}
+
+kern_return_t
+job_mig_look_up2(job_t j, mach_port_t srp, name_t servicename, mach_port_t *serviceportp, pid_t target_pid, uuid_t instance_id, uint64_t flags)
+{
+ struct machservice *ms = NULL;
+ struct ldcred *ldc = runtime_get_caller_creds();
+ kern_return_t kr;
+ bool per_pid_lookup = flags & BOOTSTRAP_PER_PID_SERVICE;
+ bool specific_instance = flags & BOOTSTRAP_SPECIFIC_INSTANCE;
+ bool strict_lookup = flags & BOOTSTRAP_STRICT_LOOKUP;
+ bool privileged = flags & BOOTSTRAP_PRIVILEGED_SERVER;
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ bool xpc_req = (j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN);
+
+ // 5641783 for the embedded hack
+#if !TARGET_OS_EMBEDDED
+ if (unlikely(pid1_magic && j->anonymous && j->mgr->parentmgr == NULL && ldc->uid != 0 && ldc->euid != 0)) {
+ return VPROC_ERR_TRY_PER_USER;
+ }
+#endif
+
+#if HAVE_SANDBOX
+ /* We don't do sandbox checking for XPC domains because, by definition, all
+ * the services within your domain should be accessible to you.
+ */
+ if (!xpc_req && unlikely(sandbox_check(ldc->pid, "mach-lookup", per_pid_lookup ? SANDBOX_FILTER_LOCAL_NAME : SANDBOX_FILTER_GLOBAL_NAME, servicename) > 0)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+#endif
+
+ if (per_pid_lookup) {
+ ms = jobmgr_lookup_service(j->mgr, servicename, false, target_pid);
+ } else {
+ if (xpc_req) {
+ // Requests from XPC domains stay local.
+ ms = jobmgr_lookup_service(j->mgr, servicename, false, 0);
+ } else {
+ /* A strict lookup which is privileged won't even bother trying to
+ * find a service if we're not hosting the root Mach bootstrap.
+ */
+ if (strict_lookup && privileged) {
+ if (inherited_bootstrap_port == MACH_PORT_NULL) {
+ ms = jobmgr_lookup_service(j->mgr, servicename, true, 0);
+ }
+ } else {
+ ms = jobmgr_lookup_service(j->mgr, servicename, true, 0);
+ }
+ }
+ }
+
+ if (likely(ms)) {
+ ms = ms->alias ? ms->alias : ms;
+ if (unlikely(specific_instance && ms->job->multiple_instances)) {
+ job_t ji = NULL;
+ job_t instance = NULL;
+ LIST_FOREACH(ji, &ms->job->subjobs, subjob_sle) {
+ if (uuid_compare(instance_id, ji->instance_id) == 0) {
+ instance = ji;
+ break;
+ }
+ }
+
+ if (unlikely(instance == NULL)) {
+ job_log(ms->job, LOG_DEBUG, "Creating new instance of job based on lookup of service %s", ms->name);
+ instance = job_new_subjob(ms->job, instance_id);
+ if (job_assumes(j, instance != NULL)) {
+ /* Disable this support for now. We only support having
+ * multi-instance jobs within private XPC domains.
+ */
+#if 0
+ /* If the job is multi-instance, in a singleton XPC domain
+ * and the request is not coming from within that singleton
+ * domain, we need to alias the new job into the requesting
+ * domain.
+ */
+ if (!j->mgr->xpc_singleton && xpc_req) {
+ (void)job_assumes(instance, job_new_alias(j->mgr, instance));
+ }
+#endif
+ job_dispatch(instance, false);
+ }
+ }
+
+ ms = NULL;
+ if (job_assumes(j, instance != NULL)) {
+ struct machservice *msi = NULL;
+ SLIST_FOREACH(msi, &instance->machservices, sle) {
+ /* sizeof(servicename) will return the size of a pointer,
+ * even though it's an array type, because when passing
+ * arrays as parameters in C, they implicitly degrade to
+ * pointers.
+ */
+ if (strncmp(servicename, msi->name, sizeof(name_t) - 1) == 0) {
+ ms = msi;
+ break;
+ }
+ }
+ }
+ } else {
+ if (machservice_hidden(ms) && !machservice_active(ms)) {
+ ms = NULL;
+ } else if (unlikely(ms->per_user_hack)) {
+ ms = NULL;
+ }
+ }
+ }
+
+ if (likely(ms)) {
+ (void)job_assumes(j, machservice_port(ms) != MACH_PORT_NULL);
+ job_log(j, LOG_DEBUG, "%sMach service lookup: %s", per_pid_lookup ? "Per PID " : "", servicename);
+ *serviceportp = machservice_port(ms);
+
+ kr = BOOTSTRAP_SUCCESS;
+ } else if (strict_lookup && !privileged) {
+ /* Hack: We need to simulate XPC's desire not to establish a hierarchy.
+ * So if XPC is doing the lookup, and it's not a privileged lookup, we
+ * won't forward. But if it is a privileged lookup, then we must
+ * forward.
+ */
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ } else if (inherited_bootstrap_port != MACH_PORT_NULL) {
+ // Requests from within an XPC domain don't get forwarded.
+ job_log(j, LOG_DEBUG, "Mach service lookup forwarded: %s", servicename);
+ /* Clients potentially check the audit token of the reply to verify that
+ * the returned send right is trustworthy.
+ */
+ (void)job_assumes_zero(j, vproc_mig_look_up2_forward(inherited_bootstrap_port, srp, servicename, target_pid, instance_id, flags));
+ return MIG_NO_REPLY;
+ } else if (pid1_magic && j->anonymous && ldc->euid >= 500 && strcasecmp(j->mgr->name, VPROCMGR_SESSION_LOGINWINDOW) == 0) {
+ /* 5240036 Should start background session when a lookup of CCacheServer
+ * occurs
+ *
+ * This is a total hack. We sniff out loginwindow session, and attempt
+ * to guess what it is up to. If we find a EUID that isn't root, we
+ * force it over to the per-user context.
+ */
+ return VPROC_ERR_TRY_PER_USER;
+ } else {
+ job_log(j, LOG_DEBUG, "%sMach service lookup failed: %s", per_pid_lookup ? "Per PID " : "", servicename);
+ kr = BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+
+ return kr;
+}
+
+kern_return_t
+job_mig_parent(job_t j, mach_port_t srp, mach_port_t *parentport)
+{
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ job_log(j, LOG_DEBUG, "Requested parent bootstrap port");
+ jobmgr_t jm = j->mgr;
+
+ if (jobmgr_parent(jm)) {
+ *parentport = jobmgr_parent(jm)->jm_port;
+ } else if (MACH_PORT_NULL == inherited_bootstrap_port) {
+ *parentport = jm->jm_port;
+ } else {
+ (void)job_assumes_zero(j, vproc_mig_parent_forward(inherited_bootstrap_port, srp));
+ // The previous routine moved the reply port, we're forced to return MIG_NO_REPLY now
+ return MIG_NO_REPLY;
+ }
+ return BOOTSTRAP_SUCCESS;
+}
+
+kern_return_t
+job_mig_get_root_bootstrap(job_t j, mach_port_t *rootbsp)
+{
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (inherited_bootstrap_port == MACH_PORT_NULL) {
+ *rootbsp = root_jobmgr->jm_port;
+ (void)job_assumes_zero(j, launchd_mport_make_send(root_jobmgr->jm_port));
+ } else {
+ *rootbsp = inherited_bootstrap_port;
+ (void)job_assumes_zero(j, launchd_mport_copy_send(inherited_bootstrap_port));
+ }
+
+ return BOOTSTRAP_SUCCESS;
+}
+
+kern_return_t
+job_mig_info(job_t j, name_array_t *servicenamesp,
+ unsigned int *servicenames_cnt, name_array_t *servicejobsp,
+ unsigned int *servicejobs_cnt, bootstrap_status_array_t *serviceactivesp,
+ unsigned int *serviceactives_cnt, uint64_t flags)
+{
+ name_array_t service_names = NULL;
+ name_array_t service_jobs = NULL;
+ bootstrap_status_array_t service_actives = NULL;
+ unsigned int cnt = 0, cnt2 = 0;
+ jobmgr_t jm;
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (launchd_flat_mach_namespace) {
+ if ((j->mgr->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET) || (flags & BOOTSTRAP_FORCE_LOCAL)) {
+ jm = j->mgr;
+ } else {
+ jm = root_jobmgr;
+ }
+ } else {
+ jm = j->mgr;
+ }
+
+ unsigned int i = 0;
+ struct machservice *msi = NULL;
+ for (i = 0; i < MACHSERVICE_HASH_SIZE; i++) {
+ LIST_FOREACH(msi, &jm->ms_hash[i], name_hash_sle) {
+ cnt += !msi->per_pid ? 1 : 0;
+ }
+ }
+
+ if (cnt == 0) {
+ goto out;
+ }
+
+ mig_allocate((vm_address_t *)&service_names, cnt * sizeof(service_names[0]));
+ if (!job_assumes(j, service_names != NULL)) {
+ goto out_bad;
+ }
+
+ mig_allocate((vm_address_t *)&service_jobs, cnt * sizeof(service_jobs[0]));
+ if (!job_assumes(j, service_jobs != NULL)) {
+ goto out_bad;
+ }
+
+ mig_allocate((vm_address_t *)&service_actives, cnt * sizeof(service_actives[0]));
+ if (!job_assumes(j, service_actives != NULL)) {
+ goto out_bad;
+ }
+
+ for (i = 0; i < MACHSERVICE_HASH_SIZE; i++) {
+ LIST_FOREACH(msi, &jm->ms_hash[i], name_hash_sle) {
+ if (!msi->per_pid) {
+ strlcpy(service_names[cnt2], machservice_name(msi), sizeof(service_names[0]));
+ msi = msi->alias ? msi->alias : msi;
+ if (msi->job->mgr->shortdesc) {
+ strlcpy(service_jobs[cnt2], msi->job->mgr->shortdesc, sizeof(service_jobs[0]));
+ } else {
+ strlcpy(service_jobs[cnt2], msi->job->label, sizeof(service_jobs[0]));
+ }
+ service_actives[cnt2] = machservice_status(msi);
+ cnt2++;
+ }
+ }
+ }
+
+ (void)job_assumes(j, cnt == cnt2);
+
+out:
+ *servicenamesp = service_names;
+ *servicejobsp = service_jobs;
+ *serviceactivesp = service_actives;
+ *servicenames_cnt = *servicejobs_cnt = *serviceactives_cnt = cnt;
+
+ return BOOTSTRAP_SUCCESS;
+
+out_bad:
+ if (service_names) {
+ mig_deallocate((vm_address_t)service_names, cnt * sizeof(service_names[0]));
+ }
+ if (service_jobs) {
+ mig_deallocate((vm_address_t)service_jobs, cnt * sizeof(service_jobs[0]));
+ }
+ if (service_actives) {
+ mig_deallocate((vm_address_t)service_actives, cnt * sizeof(service_actives[0]));
+ }
+
+ return BOOTSTRAP_NO_MEMORY;
+}
+
+kern_return_t
+job_mig_lookup_children(job_t j, mach_port_array_t *child_ports,
+ mach_msg_type_number_t *child_ports_cnt, name_array_t *child_names,
+ mach_msg_type_number_t *child_names_cnt,
+ bootstrap_property_array_t *child_properties,
+ mach_msg_type_number_t *child_properties_cnt)
+{
+ kern_return_t kr = BOOTSTRAP_NO_MEMORY;
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ struct ldcred *ldc = runtime_get_caller_creds();
+
+ /* Only allow root processes to look up children, even if we're in the per-user launchd.
+ * Otherwise, this could be used to cross sessions, which counts as a security vulnerability
+ * in a non-flat namespace.
+ */
+ if (ldc->euid != 0) {
+ job_log(j, LOG_WARNING, "Attempt to look up children of bootstrap by unprivileged job.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ unsigned int cnt = 0;
+
+ jobmgr_t jmr = j->mgr;
+ jobmgr_t jmi = NULL;
+ SLIST_FOREACH(jmi, &jmr->submgrs, sle) {
+ cnt++;
+ }
+
+ // Find our per-user launchds if we're PID 1.
+ job_t ji = NULL;
+ if (pid1_magic) {
+ LIST_FOREACH(ji, &jmr->jobs, sle) {
+ cnt += ji->per_user ? 1 : 0;
+ }
+ }
+
+ if (cnt == 0) {
+ return BOOTSTRAP_NO_CHILDREN;
+ }
+
+ mach_port_array_t _child_ports = NULL;
+ mig_allocate((vm_address_t *)&_child_ports, cnt * sizeof(_child_ports[0]));
+ if (!job_assumes(j, _child_ports != NULL)) {
+ kr = BOOTSTRAP_NO_MEMORY;
+ goto out_bad;
+ }
+
+ name_array_t _child_names = NULL;
+ mig_allocate((vm_address_t *)&_child_names, cnt * sizeof(_child_names[0]));
+ if (!job_assumes(j, _child_names != NULL)) {
+ kr = BOOTSTRAP_NO_MEMORY;
+ goto out_bad;
+ }
+
+ bootstrap_property_array_t _child_properties = NULL;
+ mig_allocate((vm_address_t *)&_child_properties, cnt * sizeof(_child_properties[0]));
+ if (!job_assumes(j, _child_properties != NULL)) {
+ kr = BOOTSTRAP_NO_MEMORY;
+ goto out_bad;
+ }
+
+ unsigned int cnt2 = 0;
+ SLIST_FOREACH(jmi, &jmr->submgrs, sle) {
+ if (jobmgr_assumes_zero(jmi, launchd_mport_make_send(jmi->jm_port)) == KERN_SUCCESS) {
+ _child_ports[cnt2] = jmi->jm_port;
+ } else {
+ _child_ports[cnt2] = MACH_PORT_NULL;
+ }
+
+ strlcpy(_child_names[cnt2], jmi->name, sizeof(_child_names[0]));
+ _child_properties[cnt2] = jmi->properties;
+
+ cnt2++;
+ }
+
+ if (pid1_magic) LIST_FOREACH(ji, &jmr->jobs, sle) {
+ if (ji->per_user) {
+ if (job_assumes(ji, SLIST_FIRST(&ji->machservices)->per_user_hack == true)) {
+ mach_port_t port = machservice_port(SLIST_FIRST(&ji->machservices));
+
+ if (job_assumes_zero(ji, launchd_mport_copy_send(port)) == KERN_SUCCESS) {
+ _child_ports[cnt2] = port;
+ } else {
+ _child_ports[cnt2] = MACH_PORT_NULL;
+ }
+ } else {
+ _child_ports[cnt2] = MACH_PORT_NULL;
+ }
+
+ strlcpy(_child_names[cnt2], ji->label, sizeof(_child_names[0]));
+ _child_properties[cnt2] |= BOOTSTRAP_PROPERTY_PERUSER;
+
+ cnt2++;
+ }
+ }
+
+ *child_names_cnt = cnt;
+ *child_ports_cnt = cnt;
+ *child_properties_cnt = cnt;
+
+ *child_names = _child_names;
+ *child_ports = _child_ports;
+ *child_properties = _child_properties;
+
+ unsigned int i = 0;
+ for (i = 0; i < cnt; i++) {
+ job_log(j, LOG_DEBUG, "child_names[%u] = %s", i, (char *)_child_names[i]);
+ }
+
+ return BOOTSTRAP_SUCCESS;
+out_bad:
+ if (_child_ports) {
+ mig_deallocate((vm_address_t)_child_ports, cnt * sizeof(_child_ports[0]));
+ }
+
+ if (_child_names) {
+ mig_deallocate((vm_address_t)_child_names, cnt * sizeof(_child_ports[0]));
+ }
+
+ if (_child_properties) {
+ mig_deallocate((vm_address_t)_child_properties, cnt * sizeof(_child_properties[0]));
+ }
+
+ return kr;
+}
+
+kern_return_t
+job_mig_pid_is_managed(job_t j __attribute__((unused)), pid_t p, boolean_t *managed)
+{
+ struct ldcred *ldc = runtime_get_caller_creds();
+ if ((ldc->euid != geteuid()) && (ldc->euid != 0)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ /* This is so loginwindow doesn't try to quit GUI apps that have been launched
+ * directly by launchd as agents.
+ */
+ job_t j_for_pid = jobmgr_find_by_pid_deep(root_jobmgr, p, false);
+ if (j_for_pid && !j_for_pid->anonymous && !j_for_pid->legacy_LS_job) {
+ *managed = true;
+ }
+
+ return BOOTSTRAP_SUCCESS;
+}
+
+kern_return_t
+job_mig_port_for_label(job_t j __attribute__((unused)), name_t label, mach_port_t *mp)
+{
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ struct ldcred *ldc = runtime_get_caller_creds();
+ kern_return_t kr = BOOTSTRAP_NOT_PRIVILEGED;
+
+#if HAVE_SANDBOX
+ if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+#endif
+
+ mach_port_t _mp = MACH_PORT_NULL;
+ if (!j->deny_job_creation && (ldc->euid == 0 || ldc->euid == geteuid())) {
+ job_t target_j = job_find(NULL, label);
+ if (jobmgr_assumes(root_jobmgr, target_j != NULL)) {
+ if (target_j->j_port == MACH_PORT_NULL) {
+ (void)job_assumes(target_j, job_setup_machport(target_j) == true);
+ }
+
+ _mp = target_j->j_port;
+ kr = _mp != MACH_PORT_NULL ? BOOTSTRAP_SUCCESS : BOOTSTRAP_NO_MEMORY;
+ } else {
+ kr = BOOTSTRAP_NO_MEMORY;
+ }
+ }
+
+ *mp = _mp;
+ return kr;
+}
+
+kern_return_t
+job_mig_set_security_session(job_t j, uuid_t uuid, mach_port_t asport)
+{
+#if TARGET_OS_EMBEDDED
+ return KERN_SUCCESS;
+#endif
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ uuid_string_t uuid_str;
+ uuid_unparse(uuid, uuid_str);
+ job_log(j, LOG_DEBUG, "Setting session %u for UUID %s...", asport, uuid_str);
+
+ job_t ji = NULL, jt = NULL;
+ LIST_FOREACH_SAFE(ji, &s_needing_sessions, sle, jt) {
+ uuid_string_t uuid_str2;
+ uuid_unparse(ji->expected_audit_uuid, uuid_str2);
+
+ if (uuid_compare(uuid, ji->expected_audit_uuid) == 0) {
+ uuid_clear(ji->expected_audit_uuid);
+ if (asport != MACH_PORT_NULL) {
+ job_log(ji, LOG_DEBUG, "Job should join session with port 0x%x", asport);
+ (void)job_assumes_zero(j, launchd_mport_copy_send(asport));
+ } else {
+ job_log(ji, LOG_DEBUG, "No session to set for job. Using our session.");
+ }
+
+ ji->asport = asport;
+ LIST_REMOVE(ji, needing_session_sle);
+ job_dispatch(ji, false);
+ }
+ }
+
+ /* Each job that the session port was set for holds a reference. At the end of
+ * the loop, there will be one extra reference belonging to this MiG protocol.
+ * We need to release it so that the session goes away when all the jobs
+ * referencing it are unloaded.
+ */
+ (void)job_assumes_zero(j, launchd_mport_deallocate(asport));
+
+ return KERN_SUCCESS;
+}
+
+jobmgr_t
+jobmgr_find_by_name(jobmgr_t jm, const char *where)
+{
+ jobmgr_t jmi, jmi2;
+
+ // NULL is only passed for our custom API for LaunchServices. If that is the case, we do magic.
+ if (where == NULL) {
+ if (strcasecmp(jm->name, VPROCMGR_SESSION_LOGINWINDOW) == 0) {
+ where = VPROCMGR_SESSION_LOGINWINDOW;
+ } else {
+ where = VPROCMGR_SESSION_AQUA;
+ }
+ }
+
+ if (strcasecmp(jm->name, where) == 0) {
+ return jm;
+ }
+
+ if (strcasecmp(where, VPROCMGR_SESSION_BACKGROUND) == 0 && !pid1_magic) {
+ jmi = root_jobmgr;
+ goto jm_found;
+ }
+
+ SLIST_FOREACH(jmi, &root_jobmgr->submgrs, sle) {
+ if (unlikely(jmi->shutting_down)) {
+ continue;
+ } else if (jmi->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) {
+ continue;
+ } else if (strcasecmp(jmi->name, where) == 0) {
+ goto jm_found;
+ } else if (strcasecmp(jmi->name, VPROCMGR_SESSION_BACKGROUND) == 0 && pid1_magic) {
+ SLIST_FOREACH(jmi2, &jmi->submgrs, sle) {
+ if (strcasecmp(jmi2->name, where) == 0) {
+ jmi = jmi2;
+ goto jm_found;
+ }
+ }
+ }
+ }
+
+jm_found:
+ return jmi;
+}
+
+kern_return_t
+job_mig_move_subset(job_t j, mach_port_t target_subset, name_t session_type, mach_port_t asport, uint64_t flags)
+{
+ mach_msg_type_number_t l2l_i, l2l_port_cnt = 0;
+ mach_port_array_t l2l_ports = NULL;
+ mach_port_t reqport, rcvright;
+ kern_return_t kr = 1;
+ launch_data_t out_obj_array = NULL;
+ struct ldcred *ldc = runtime_get_caller_creds();
+ jobmgr_t jmr = NULL;
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (job_mig_intran2(root_jobmgr, target_subset, ldc->pid)) {
+ job_log(j, LOG_ERR, "Moving a session to ourself is bogus.");
+
+ kr = BOOTSTRAP_NOT_PRIVILEGED;
+ goto out;
+ }
+
+ job_log(j, LOG_DEBUG, "Move subset attempt: 0x%x", target_subset);
+
+ kr = _vproc_grab_subset(target_subset, &reqport, &rcvright, &out_obj_array, &l2l_ports, &l2l_port_cnt);
+ if (job_assumes_zero(j, kr) != 0) {
+ goto out;
+ }
+
+ if (launch_data_array_get_count(out_obj_array) != l2l_port_cnt) {
+ osx_assert_zero(l2l_port_cnt);
+ }
+
+ if (!job_assumes(j, (jmr = jobmgr_new(j->mgr, reqport, rcvright, false, session_type, false, asport)) != NULL)) {
+ kr = BOOTSTRAP_NO_MEMORY;
+ goto out;
+ }
+
+ jmr->properties |= BOOTSTRAP_PROPERTY_MOVEDSUBSET;
+
+ /* This is a hack. We should be doing this in jobmgr_new(), but since we're in the middle of
+ * processing an IPC request, we'll do this action before the new job manager can get any IPC
+ * requests. This serialization is guaranteed since we are single-threaded in that respect.
+ */
+ if (flags & LAUNCH_GLOBAL_ON_DEMAND) {
+ // This is so awful.
+ // Remove the job from its current job manager.
+ LIST_REMOVE(j, sle);
+ LIST_REMOVE(j, pid_hash_sle);
+
+ // Put the job into the target job manager.
+ LIST_INSERT_HEAD(&jmr->jobs, j, sle);
+ LIST_INSERT_HEAD(&jmr->active_jobs[ACTIVE_JOB_HASH(j->p)], j, pid_hash_sle);
+
+ j->mgr = jmr;
+ job_set_global_on_demand(j, true);
+
+ if (!j->holds_ref) {
+ job_log(j, LOG_PERF, "Job moved subset into: %s", j->mgr->name);
+ j->holds_ref = true;
+ runtime_add_ref();
+ }
+ }
+
+ for (l2l_i = 0; l2l_i < l2l_port_cnt; l2l_i++) {
+ launch_data_t tmp, obj_at_idx;
+ struct machservice *ms;
+ job_t j_for_service;
+ const char *serv_name;
+ pid_t target_pid;
+ bool serv_perpid;
+
+ (void)job_assumes(j, obj_at_idx = launch_data_array_get_index(out_obj_array, l2l_i));
+ (void)job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_PID));
+ target_pid = (pid_t)launch_data_get_integer(tmp);
+ (void)job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_PERPID));
+ serv_perpid = launch_data_get_bool(tmp);
+ (void)job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_NAME));
+ serv_name = launch_data_get_string(tmp);
+
+ j_for_service = jobmgr_find_by_pid(jmr, target_pid, true);
+
+ if (unlikely(!j_for_service)) {
+ // The PID probably exited
+ (void)job_assumes_zero(j, launchd_mport_deallocate(l2l_ports[l2l_i]));
+ continue;
+ }
+
+ if (likely(ms = machservice_new(j_for_service, serv_name, &l2l_ports[l2l_i], serv_perpid))) {
+ job_log(j, LOG_DEBUG, "Importing %s into new bootstrap.", serv_name);
+ machservice_request_notifications(ms);
+ }
+ }
+
+ kr = 0;
+
+out:
+ if (out_obj_array) {
+ launch_data_free(out_obj_array);
+ }
+
+ if (l2l_ports) {
+ mig_deallocate((vm_address_t)l2l_ports, l2l_port_cnt * sizeof(l2l_ports[0]));
+ }
+
+ if (kr == 0) {
+ if (target_subset) {
+ (void)job_assumes_zero(j, launchd_mport_deallocate(target_subset));
+ }
+ if (asport) {
+ (void)job_assumes_zero(j, launchd_mport_deallocate(asport));
+ }
+ } else if (jmr) {
+ jobmgr_shutdown(jmr);
+ }
+
+ return kr;
+}
+
+kern_return_t
+job_mig_init_session(job_t j, name_t session_type, mach_port_t asport)
+{
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ job_t j2;
+
+ kern_return_t kr = BOOTSTRAP_NO_MEMORY;
+ if (j->mgr->session_initialized) {
+ job_log(j, LOG_ERR, "Tried to initialize an already setup session!");
+ kr = BOOTSTRAP_NOT_PRIVILEGED;
+ } else if (strcmp(session_type, VPROCMGR_SESSION_LOGINWINDOW) == 0) {
+ jobmgr_t jmi;
+
+ /*
+ * 5330262
+ *
+ * We're working around LoginWindow and the WindowServer.
+ *
+ * In practice, there is only one LoginWindow session. Unfortunately, for certain
+ * scenarios, the WindowServer spawns loginwindow, and in those cases, it frequently
+ * spawns a replacement loginwindow session before cleaning up the previous one.
+ *
+ * We're going to use the creation of a new LoginWindow context as a clue that the
+ * previous LoginWindow context is on the way out and therefore we should just
+ * kick-start the shutdown of it.
+ */
+
+ SLIST_FOREACH(jmi, &root_jobmgr->submgrs, sle) {
+ if (unlikely(jmi->shutting_down)) {
+ continue;
+ } else if (strcasecmp(jmi->name, session_type) == 0) {
+ jobmgr_shutdown(jmi);
+ break;
+ }
+ }
+ }
+
+ jobmgr_log(j->mgr, LOG_DEBUG, "Initializing as %s", session_type);
+ strcpy(j->mgr->name_init, session_type);
+
+ if (job_assumes(j, (j2 = jobmgr_init_session(j->mgr, session_type, false)))) {
+ j2->asport = asport;
+ (void)job_assumes(j, job_dispatch(j2, true));
+ kr = BOOTSTRAP_SUCCESS;
+ }
+
+ return kr;
+}
+
+kern_return_t
+job_mig_switch_to_session(job_t j, mach_port_t requestor_port, name_t session_name, mach_port_t asport, mach_port_t *new_bsport)
+{
+ struct ldcred *ldc = runtime_get_caller_creds();
+ if (!jobmgr_assumes(root_jobmgr, j != NULL)) {
+ jobmgr_log(root_jobmgr, LOG_ERR, "%s() called with NULL job: PID %d", __func__, ldc->pid);
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (j->mgr->shutting_down) {
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+
+ job_log(j, LOG_DEBUG, "Job wants to move to %s session.", session_name);
+
+ if (!job_assumes(j, pid1_magic == false)) {
+ job_log(j, LOG_WARNING, "Switching sessions is not allowed in the system Mach bootstrap.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ if (!j->anonymous) {
+ job_log(j, LOG_NOTICE, "Non-anonymous job tried to switch sessions. Please use LimitLoadToSessionType instead.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ jobmgr_t target_jm = jobmgr_find_by_name(root_jobmgr, session_name);
+ if (target_jm == j->mgr) {
+ job_log(j, LOG_DEBUG, "Job is already in its desired session (%s).", session_name);
+ (void)job_assumes_zero(j, launchd_mport_deallocate(asport));
+ (void)job_assumes_zero(j, launchd_mport_deallocate(requestor_port));
+ *new_bsport = target_jm->jm_port;
+ return BOOTSTRAP_SUCCESS;
+ }
+
+ if (!target_jm) {
+ target_jm = jobmgr_new(j->mgr, requestor_port, MACH_PORT_NULL, false, session_name, false, asport);
+ if (target_jm) {
+ target_jm->properties |= BOOTSTRAP_PROPERTY_IMPLICITSUBSET;
+ (void)job_assumes_zero(j, launchd_mport_deallocate(asport));
+ }
+ }
+
+ if (!job_assumes(j, target_jm != NULL)) {
+ job_log(j, LOG_WARNING, "Could not find %s session!", session_name);
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ // Remove the job from it's current job manager.
+ LIST_REMOVE(j, sle);
+ LIST_REMOVE(j, pid_hash_sle);
+
+ job_t ji = NULL, jit = NULL;
+ LIST_FOREACH_SAFE(ji, &j->mgr->global_env_jobs, global_env_sle, jit) {
+ if (ji == j) {
+ LIST_REMOVE(ji, global_env_sle);
+ break;
+ }
+ }
+
+ // Put the job into the target job manager.
+ LIST_INSERT_HEAD(&target_jm->jobs, j, sle);
+ LIST_INSERT_HEAD(&target_jm->active_jobs[ACTIVE_JOB_HASH(j->p)], j, pid_hash_sle);
+
+ if (ji) {
+ LIST_INSERT_HEAD(&target_jm->global_env_jobs, j, global_env_sle);
+ }
+
+ // Move our Mach services over if we're not in a flat namespace.
+ if (!launchd_flat_mach_namespace && !SLIST_EMPTY(&j->machservices)) {
+ struct machservice *msi = NULL, *msit = NULL;
+ SLIST_FOREACH_SAFE(msi, &j->machservices, sle, msit) {
+ LIST_REMOVE(msi, name_hash_sle);
+ LIST_INSERT_HEAD(&target_jm->ms_hash[hash_ms(msi->name)], msi, name_hash_sle);
+ }
+ }
+
+ j->mgr = target_jm;
+
+ if (!j->holds_ref) {
+ /* Anonymous jobs which move around are particularly interesting to us, so we want to
+ * stick around while they're still around.
+ * For example, login calls into the PAM launchd module, which moves the process into
+ * the StandardIO session by default. So we'll hold a reference on that job to prevent
+ * ourselves from going away.
+ */
+ j->holds_ref = true;
+ job_log(j, LOG_PERF, "Job switched into manager: %s", j->mgr->name);
+ runtime_add_ref();
+ }
+
+ *new_bsport = target_jm->jm_port;
+
+ return KERN_SUCCESS;
+}
+
+kern_return_t
+job_mig_take_subset(job_t j, mach_port_t *reqport, mach_port_t *rcvright,
+ vm_offset_t *outdata, mach_msg_type_number_t *outdataCnt,
+ mach_port_array_t *portsp, unsigned int *ports_cnt)
+{
+ launch_data_t tmp_obj, tmp_dict, outdata_obj_array = NULL;
+ mach_port_array_t ports = NULL;
+ unsigned int cnt = 0, cnt2 = 0;
+ size_t packed_size;
+ struct machservice *ms;
+ jobmgr_t jm;
+ job_t ji;
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ jm = j->mgr;
+
+ if (unlikely(!pid1_magic)) {
+ job_log(j, LOG_ERR, "Only the system launchd will transfer Mach sub-bootstraps.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ if (unlikely(jobmgr_parent(jm) == NULL)) {
+ job_log(j, LOG_ERR, "Root Mach bootstrap cannot be transferred.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ if (unlikely(strcasecmp(jm->name, VPROCMGR_SESSION_AQUA) == 0)) {
+ job_log(j, LOG_ERR, "Cannot transfer a setup GUI session.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ if (unlikely(!j->anonymous)) {
+ job_log(j, LOG_ERR, "Only the anonymous job can transfer Mach sub-bootstraps.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ job_log(j, LOG_DEBUG, "Transferring sub-bootstrap to the per session launchd.");
+
+ outdata_obj_array = launch_data_alloc(LAUNCH_DATA_ARRAY);
+ if (!job_assumes(j, outdata_obj_array)) {
+ goto out_bad;
+ }
+
+ *outdataCnt = 20 * 1024 * 1024;
+ mig_allocate(outdata, *outdataCnt);
+ if (!job_assumes(j, *outdata != 0)) {
+ return 1;
+ }
+
+ LIST_FOREACH(ji, &j->mgr->jobs, sle) {
+ if (!ji->anonymous) {
+ continue;
+ }
+ SLIST_FOREACH(ms, &ji->machservices, sle) {
+ cnt++;
+ }
+ }
+
+ mig_allocate((vm_address_t *)&ports, cnt * sizeof(ports[0]));
+ if (!job_assumes(j, ports != NULL)) {
+ goto out_bad;
+ }
+
+ LIST_FOREACH(ji, &j->mgr->jobs, sle) {
+ if (!ji->anonymous) {
+ continue;
+ }
+
+ SLIST_FOREACH(ms, &ji->machservices, sle) {
+ if (job_assumes(j, (tmp_dict = launch_data_alloc(LAUNCH_DATA_DICTIONARY)))) {
+ (void)job_assumes(j, launch_data_array_set_index(outdata_obj_array, tmp_dict, cnt2));
+ } else {
+ goto out_bad;
+ }
+
+ if (job_assumes(j, (tmp_obj = launch_data_new_string(machservice_name(ms))))) {
+ (void)job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_NAME));
+ } else {
+ goto out_bad;
+ }
+
+ if (job_assumes(j, (tmp_obj = launch_data_new_integer((ms->job->p))))) {
+ (void)job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_PID));
+ } else {
+ goto out_bad;
+ }
+
+ if (job_assumes(j, (tmp_obj = launch_data_new_bool((ms->per_pid))))) {
+ (void)job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_PERPID));
+ } else {
+ goto out_bad;
+ }
+
+ ports[cnt2] = machservice_port(ms);
+
+ // Increment the send right by one so we can shutdown the jobmgr cleanly
+ (void)jobmgr_assumes_zero(jm, launchd_mport_copy_send(ports[cnt2]));
+ cnt2++;
+ }
+ }
+
+ (void)job_assumes(j, cnt == cnt2);
+
+ runtime_ktrace0(RTKT_LAUNCHD_DATA_PACK);
+ packed_size = launch_data_pack(outdata_obj_array, (void *)*outdata, *outdataCnt, NULL, NULL);
+ if (!job_assumes(j, packed_size != 0)) {
+ goto out_bad;
+ }
+
+ launch_data_free(outdata_obj_array);
+
+ *portsp = ports;
+ *ports_cnt = cnt;
+
+ *reqport = jm->req_port;
+ *rcvright = jm->jm_port;
+
+ jm->req_port = 0;
+ jm->jm_port = 0;
+
+ workaround_5477111 = j;
+
+ jobmgr_shutdown(jm);
+
+ return BOOTSTRAP_SUCCESS;
+
+out_bad:
+ if (outdata_obj_array) {
+ launch_data_free(outdata_obj_array);
+ }
+ if (*outdata) {
+ mig_deallocate(*outdata, *outdataCnt);
+ }
+ if (ports) {
+ mig_deallocate((vm_address_t)ports, cnt * sizeof(ports[0]));
+ }
+
+ return BOOTSTRAP_NO_MEMORY;
+}
+
+kern_return_t
+job_mig_subset(job_t j, mach_port_t requestorport, mach_port_t *subsetportp)
+{
+ int bsdepth = 0;
+ jobmgr_t jmr;
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+ if (j->mgr->shutting_down) {
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+
+ jmr = j->mgr;
+
+ while ((jmr = jobmgr_parent(jmr)) != NULL) {
+ bsdepth++;
+ }
+
+ // Since we use recursion, we need an artificial depth for subsets
+ if (unlikely(bsdepth > 100)) {
+ job_log(j, LOG_ERR, "Mach sub-bootstrap create request failed. Depth greater than: %d", bsdepth);
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ char name[NAME_MAX];
+ snprintf(name, sizeof(name), "%s[%i].subset.%i", j->anonymous ? j->prog : j->label, j->p, MACH_PORT_INDEX(requestorport));
+
+ if (!job_assumes(j, (jmr = jobmgr_new(j->mgr, requestorport, MACH_PORT_NULL, false, name, true, j->asport)) != NULL)) {
+ if (unlikely(requestorport == MACH_PORT_NULL)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ *subsetportp = jmr->jm_port;
+ jmr->properties |= BOOTSTRAP_PROPERTY_EXPLICITSUBSET;
+
+ /* A job could create multiple subsets, so only add a reference the first time
+ * it does so we don't have to keep a count.
+ */
+ if (j->anonymous && !j->holds_ref) {
+ job_log(j, LOG_PERF, "Job created subset: %s", jmr->name);
+ j->holds_ref = true;
+ runtime_add_ref();
+ }
+
+ job_log(j, LOG_DEBUG, "Job created a subset named \"%s\"", jmr->name);
+ return BOOTSTRAP_SUCCESS;
+}
+
+job_t
+_xpc_domain_import_service(jobmgr_t jm, launch_data_t pload)
+{
+ jobmgr_t where2put = NULL;
+
+ if (launch_data_get_type(pload) != LAUNCH_DATA_DICTIONARY) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ launch_data_t ldlabel = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_LABEL);
+ if (!ldlabel || launch_data_get_type(ldlabel) != LAUNCH_DATA_STRING) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ const char *label = launch_data_get_string(ldlabel);
+ jobmgr_log(jm, LOG_DEBUG, "Importing service: %s", label);
+
+ launch_data_t destname = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_XPCDOMAIN);
+ if (destname) {
+ bool supported_domain = false;
+
+ if (launch_data_get_type(destname) == LAUNCH_DATA_STRING) {
+ const char *str = launch_data_get_string(destname);
+ if (strcmp(str, XPC_DOMAIN_TYPE_SYSTEM) == 0) {
+ where2put = _s_xpc_system_domain;
+ } else if (strcmp(str, XPC_DOMAIN_TYPE_PERUSER) == 0) {
+ where2put = jobmgr_find_xpc_per_user_domain(jm, jm->req_euid);
+ supported_domain = true;
+ } else if (strcmp(str, XPC_DOMAIN_TYPE_PERSESSION) == 0) {
+ where2put = jobmgr_find_xpc_per_session_domain(jm, jm->req_asid);
+ } else {
+ jobmgr_log(jm, LOG_ERR, "Invalid XPC domain type: %s", str);
+ errno = EINVAL;
+ }
+ } else {
+ jobmgr_log(jm, LOG_ERR, "XPC domain type is not a string.");
+ errno = EINVAL;
+ }
+
+ if (where2put && !supported_domain) {
+ launch_data_t mi = NULL;
+ if ((mi = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_MULTIPLEINSTANCES))) {
+ if (launch_data_get_type(mi) == LAUNCH_DATA_BOOL && launch_data_get_bool(mi)) {
+ jobmgr_log(where2put, LOG_ERR, "Multiple-instance services are not supported in this domain.");
+ where2put = NULL;
+ errno = EINVAL;
+ }
+ }
+ }
+ } else {
+ where2put = jm;
+ }
+
+ job_t j = NULL;
+ if (where2put) {
+ /* Gross. If the service already exists in a singleton domain, then
+ * jobmgr_import2() will return the existing job. But if we fail to alias
+ * this job, we will normally want to remove it. But if we did not create
+ * it in the first place, then we need to avoid removing it. So check
+ * errno against EEXIST in the success case and if it's EEXIST, then do
+ * not remove the original job in the event of a failed alias.
+ *
+ * This really needs to be re-thought, but I think it'll require a larger
+ * evaluation of launchd's data structures. Right now, once a job is
+ * imported into a singleton domain, it won't be removed until the system
+ * shuts down, but that may not always be true. If it ever changes, we'll
+ * have a problem because we'll have to account for all existing aliases
+ * and clean them up somehow. Or just start ref-counting. I knew this
+ * aliasing stuff would be trouble...
+ *
+ * <rdar://problem/10646503>
+ */
+ jobmgr_log(where2put, LOG_DEBUG, "Importing service...");
+
+ errno = 0;
+ if ((j = jobmgr_import2(where2put, pload))) {
+ bool created = (errno != EEXIST);
+ j->xpc_service = true;
+
+ if (where2put->xpc_singleton) {
+ /* If the service was destined for one of the global domains,
+ * then we have to alias it into our local domain to reserve the
+ * name.
+ */
+ job_t ja = NULL;
+ if (!(ja = job_new_alias(jm, j))) {
+ /* If we failed to alias the job because of a conflict over
+ * the label, then we remove it from the global domain. We
+ * don't want to risk having imported a malicious job into
+ * one of the global domains.
+ */
+ if (errno != EEXIST) {
+ job_log(j, LOG_ERR, "Failed to alias job into: %s: %d: %s", where2put->name, errno, strerror(errno));
+ } else {
+ errno = 0;
+ }
+
+ if (created) {
+ jobmgr_log(jm, LOG_WARNING, "Singleton service already existed in job-local namespace. Removing: %s", j->label);
+ job_remove(j);
+ }
+
+ j = NULL;
+ } else {
+ jobmgr_log(jm, LOG_DEBUG, "Aliased service into local domain: %s", j->label);
+ (void)job_dispatch(j, false);
+ ja->xpc_service = true;
+ j = ja;
+ }
+ } else {
+ (void)job_dispatch(j, false);
+ }
+ }
+ } else {
+ jobmgr_log(jm, LOG_DEBUG, "Could not find destination for service: %s", label);
+ }
+
+ return j;
+}
+
+int
+_xpc_domain_import_services(job_t j, launch_data_t services)
+{
+ int error = EINVAL;
+ if (launch_data_get_type(services) != LAUNCH_DATA_ARRAY) {
+ return error;
+ }
+
+ size_t i = 0;
+ size_t c = launch_data_array_get_count(services);
+ jobmgr_log(j->mgr, LOG_DEBUG, "Importing new services: %lu", c);
+
+ for (i = 0; i < c; i++) {
+ jobmgr_log(j->mgr, LOG_DEBUG, "Importing service at index: %lu", i);
+
+ job_t nj = NULL;
+ launch_data_t ploadi = launch_data_array_get_index(services, i);
+ if (!(nj = _xpc_domain_import_service(j->mgr, ploadi))) {
+ if (!j->mgr->session_initialized && errno) {
+ /* Service import failures are only fatal if the domain is being
+ * initialized. If we're extending the domain, we can run into
+ * errors with services already existing, so we just ignore them.
+ * In the case of a domain extension, we don't want to halt the
+ * operation if we run into an error with one service.
+ *
+ * <rdar://problem/10842779>
+ */
+ jobmgr_log(j->mgr, LOG_ERR, "Failed to import service at index: %lu: %d: %s", i, errno, strerror(errno));
+ error = errno;
+ break;
+ }
+ } else {
+ jobmgr_log(j->mgr, LOG_DEBUG, "Imported service: %s", nj->label);
+ }
+ }
+
+ if (i == c) {
+ error = 0;
+ }
+
+ return error;
+}
+
+kern_return_t
+xpc_domain_import2(job_t j, mach_port_t reqport, mach_port_t dport)
+{
+ if (unlikely(!pid1_magic)) {
+ job_log(j, LOG_ERR, "XPC domains may only reside in PID 1.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ if (!j || !MACH_PORT_VALID(reqport)) {
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+ if (root_jobmgr->shutting_down) {
+ jobmgr_log(root_jobmgr, LOG_ERR, "Attempt to create new domain while shutting down.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ if (!j->xpc_bootstrapper) {
+ job_log(j, LOG_ERR, "Attempt to create new XPC domain by unprivileged job.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ kern_return_t kr = BOOTSTRAP_NO_MEMORY;
+ /* All XPC domains are children of the root job manager. What we're creating
+ * here is really just a skeleton. By creating it, we're adding reqp to our
+ * port set. It will have two messages on it. The first specifies the
+ * environment of the originator. This is so we can cache it and hand it to
+ * xpcproxy to bootstrap our services. The second is the set of jobs that is
+ * to be bootstrapped in.
+ */
+ jobmgr_t jm = jobmgr_new(root_jobmgr, reqport, dport, false, NULL, true, MACH_PORT_NULL);
+ if (job_assumes(j, jm != NULL)) {
+ jm->properties |= BOOTSTRAP_PROPERTY_XPC_DOMAIN;
+ jm->shortdesc = "private";
+ kr = BOOTSTRAP_SUCCESS;
+ }
+
+ return kr;
+}
+
+kern_return_t
+xpc_domain_set_environment(job_t j, mach_port_t rp, mach_port_t bsport, mach_port_t excport, vm_offset_t ctx, mach_msg_type_number_t ctx_sz)
+{
+ if (!j) {
+ /* Due to the whacky nature of XPC service bootstrapping, we can end up
+ * getting this message long after the requesting process has gone away.
+ * See <rdar://problem/8593143>.
+ */
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+
+ jobmgr_t jm = j->mgr;
+ if (!(jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ if (jm->req_asport != MACH_PORT_NULL) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ struct ldcred *ldc = runtime_get_caller_creds();
+ struct proc_bsdshortinfo proc;
+ if (proc_pidinfo(ldc->pid, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
+ if (errno != ESRCH) {
+ (void)jobmgr_assumes_zero(jm, errno);
+ }
+
+ jm->error = errno;
+ jobmgr_remove(jm);
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+#if !TARGET_OS_EMBEDDED
+ if (jobmgr_assumes_zero(jm, audit_session_port(ldc->asid, &jm->req_asport)) != 0) {
+ jm->error = EPERM;
+ jobmgr_remove(jm);
+ job_log(j, LOG_ERR, "Failed to get port for ASID: %u", ldc->asid);
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+#else
+ jm->req_asport = MACH_PORT_DEAD;
+#endif
+
+ (void)snprintf(jm->name_init, NAME_MAX, "com.apple.xpc.domain.%s[%i]", proc.pbsi_comm, ldc->pid);
+ strlcpy(jm->owner, proc.pbsi_comm, sizeof(jm->owner));
+ jm->req_bsport = bsport;
+ jm->req_excport = excport;
+ jm->req_rport = rp;
+ jm->req_ctx = ctx;
+ jm->req_ctx_sz = ctx_sz;
+ jm->req_pid = ldc->pid;
+ jm->req_euid = ldc->euid;
+ jm->req_egid = ldc->egid;
+ jm->req_asid = ldc->asid;
+
+ return KERN_SUCCESS;
+}
+
+kern_return_t
+xpc_domain_load_services(job_t j, vm_offset_t services_buff, mach_msg_type_number_t services_sz)
+{
+ if (!j) {
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+
+ job_t rootj = jobmgr_find_by_pid(root_jobmgr, j->p, false);
+ if (!(rootj && rootj->xpc_bootstrapper)) {
+ job_log(j, LOG_ERR, "Attempt to load services into XPC domain by unprivileged job.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ // This is just for XPC domains (for now).
+ if (!(j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ if (j->mgr->session_initialized) {
+ jobmgr_log(j->mgr, LOG_ERR, "Attempt to initialize an already-initialized XPC domain.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ size_t offset = 0;
+ launch_data_t services = launch_data_unpack((void *)services_buff, services_sz, NULL, 0, &offset, NULL);
+ if (!services) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ int error = _xpc_domain_import_services(j, services);
+ if (error) {
+ j->mgr->error = error;
+ jobmgr_log(j->mgr, LOG_ERR, "Obliterating domain.");
+ jobmgr_remove(j->mgr);
+ } else {
+ j->mgr->session_initialized = true;
+ (void)jobmgr_assumes_zero(j->mgr, xpc_call_wakeup(j->mgr->req_rport, BOOTSTRAP_SUCCESS));
+ j->mgr->req_rport = MACH_PORT_NULL;
+
+ /* Returning a failure code will destroy the message, whereas returning
+ * success will not, so we need to clean up here.
+ */
+ mig_deallocate(services_buff, services_sz);
+ error = BOOTSTRAP_SUCCESS;
+ }
+
+ return error;
+}
+
+kern_return_t
+xpc_domain_check_in(job_t j, mach_port_t *bsport, mach_port_t *sbsport,
+ mach_port_t *excport, mach_port_t *asport, uint32_t *uid, uint32_t *gid,
+ int32_t *asid, vm_offset_t *ctx, mach_msg_type_number_t *ctx_sz)
+{
+ if (!jobmgr_assumes(root_jobmgr, j != NULL)) {
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+ jobmgr_t jm = j->mgr;
+ if (!(jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ if (jm->req_asport == MACH_PORT_NULL) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ *bsport = jm->req_bsport;
+ *sbsport = root_jobmgr->jm_port;
+ *excport = jm->req_excport;
+ *asport = jm->req_asport;
+ *uid = jm->req_euid;
+ *gid = jm->req_egid;
+ *asid = jm->req_asid;
+
+ *ctx = jm->req_ctx;
+ *ctx_sz = jm->req_ctx_sz;
+
+ return KERN_SUCCESS;
+}
+
+kern_return_t
+xpc_domain_get_service_name(job_t j, event_name_t name)
+{
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+ if (!j->xpc_service) {
+ jobmgr_log(j->mgr, LOG_ERR, "Attempt to get service name by non-XPC service: %s", j->label);
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ struct machservice * ms = SLIST_FIRST(&j->machservices);
+ if (!ms) {
+ jobmgr_log(j->mgr, LOG_ERR, "Attempt to get service name of job with no MachServices: %s", j->label);
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+
+ (void)strlcpy(name, ms->name, sizeof(event_name_t));
+ return BOOTSTRAP_SUCCESS;
+}
+
+#if XPC_LPI_VERSION >= 20111216
+kern_return_t
+xpc_domain_add_services(job_t j, vm_offset_t services_buff, mach_msg_type_number_t services_sz)
+{
+ if (!j) {
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+
+ job_t rootj = jobmgr_find_by_pid(root_jobmgr, j->p, false);
+ if (!(rootj && rootj->xpc_bootstrapper)) {
+ job_log(j, LOG_ERR, "Attempt to add service to XPC domain by unprivileged job.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ if (!(j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ size_t offset = 0;
+ launch_data_t services = launch_data_unpack((void *)services_buff, services_sz, NULL, 0, &offset, NULL);
+ if (!services) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ int error = _xpc_domain_import_services(j, services);
+ if (!error) {
+ mig_deallocate(services_buff, services_sz);
+ }
+
+ return error;
+}
+#endif
+
+#pragma mark XPC Events
+int
+xpc_event_find_channel(job_t j, const char *stream, struct machservice **ms)
+{
+ int error = EXNOMEM;
+ struct machservice *msi = NULL;
+ SLIST_FOREACH(msi, &j->machservices, sle) {
+ if (strcmp(stream, msi->name) == 0) {
+ break;
+ }
+ }
+
+ if (!msi) {
+ mach_port_t sp = MACH_PORT_NULL;
+ msi = machservice_new(j, stream, &sp, false);
+ if (!msi) {
+ return EXNOMEM;
+ }
+
+ job_log(j, LOG_DEBUG, "Creating new MachService for stream: %s", stream);
+ /* Hack to keep this from being publicly accessible through
+ * bootstrap_look_up().
+ */
+ if (!j->dedicated_instance) {
+ LIST_REMOVE(msi, name_hash_sle);
+ }
+ msi->event_channel = true;
+
+ /* If we call job_dispatch() here before the audit session for the job
+ * has been set, we'll end up not watching this service. But we also have
+ * to take care not to watch the port if the job is active.
+ *
+ * See <rdar://problem/10357855>.
+ */
+ if (!j->currently_ignored) {
+ machservice_watch(j, msi);
+ }
+
+ error = 0;
+ *ms = msi;
+ } else if (!msi->event_channel) {
+ job_log(j, LOG_ERR, "This job registered a MachService name identical to the requested event channel name: %s", stream);
+ error = EEXIST;
+ } else {
+ error = 0;
+ *ms = msi;
+ }
+
+ return error;
+}
+
+int
+xpc_event_get_event_name(job_t j, xpc_object_t request, xpc_object_t *reply)
+{
+ const char *stream = xpc_dictionary_get_string(request, XPC_EVENT_ROUTINE_KEY_STREAM);
+ if (!stream) {
+ return EXINVAL;
+ }
+
+ uint64_t token = xpc_dictionary_get_uint64(request, XPC_EVENT_ROUTINE_KEY_TOKEN);
+ if (!token) {
+ return EXINVAL;
+ }
+
+ job_log(j, LOG_DEBUG, "Getting event name for stream/token: %s/0x%llu", stream, token);
+
+ int result = ESRCH;
+ struct externalevent *event = externalevent_find(stream, token);
+ if (event && j->event_monitor) {
+ xpc_object_t reply2 = xpc_dictionary_create_reply(request);
+ xpc_dictionary_set_string(reply2, XPC_EVENT_ROUTINE_KEY_NAME, event->name);
+ *reply = reply2;
+
+ job_log(j, LOG_DEBUG, "Found: %s", event->name);
+ result = 0;
+ }
+
+ return result;
+}
+
+int
+xpc_event_set_event(job_t j, xpc_object_t request, xpc_object_t *reply)
+{
+ const char *stream = xpc_dictionary_get_string(request, XPC_EVENT_ROUTINE_KEY_STREAM);
+ if (!stream) {
+ return EXINVAL;
+ }
+
+ const char *key = xpc_dictionary_get_string(request, XPC_EVENT_ROUTINE_KEY_NAME);
+ if (!key) {
+ return EXINVAL;
+ }
+
+ xpc_object_t event = xpc_dictionary_get_value(request, XPC_EVENT_ROUTINE_KEY_EVENT);
+ if (event && xpc_get_type(event) != XPC_TYPE_DICTIONARY) {
+ return EXINVAL;
+ }
+
+ job_log(j, LOG_DEBUG, "%s event for stream/key: %s/%s", event ? "Setting" : "Removing", stream, key);
+
+ struct externalevent *eei = NULL;
+ LIST_FOREACH(eei, &j->events, job_le) {
+ /* If the event for the given key already exists for the job, we need to
+ * remove the old one first.
+ */
+ if (strcmp(eei->name, key) == 0 && strcmp(eei->sys->name, stream) == 0) {
+ job_log(j, LOG_DEBUG, "Event exists. Removing.");
+ externalevent_delete(eei);
+ break;
+ }
+ }
+
+ int result = EXNOMEM;
+ if (event) {
+ struct eventsystem *es = eventsystem_find(stream);
+ if (!es) {
+ job_log(j, LOG_DEBUG, "Creating stream.");
+ es = eventsystem_new(stream);
+ }
+
+ if (es) {
+ job_log(j, LOG_DEBUG, "Adding event.");
+ if (externalevent_new(j, es, key, event)) {
+ job_log(j, LOG_DEBUG, "Added new event for key: %s", key);
+ result = 0;
+ } else {
+ job_log(j, LOG_ERR, "Could not create event for key: %s", key);
+ }
+ } else {
+ job_log(j, LOG_ERR, "Event stream could not be created: %s", stream);
+ }
+ } else {
+ /* If the event was NULL, then we just remove it and return. */
+ result = 0;
+ }
+
+ if (result == 0) {
+ xpc_object_t reply2 = xpc_dictionary_create_reply(request);
+ *reply = reply2;
+ }
+
+ return result;
+}
+
+int
+xpc_event_copy_event(job_t j, xpc_object_t request, xpc_object_t *reply)
+{
+ const char *stream = xpc_dictionary_get_string(request, XPC_EVENT_ROUTINE_KEY_STREAM);
+ const char *key = xpc_dictionary_get_string(request, XPC_EVENT_ROUTINE_KEY_NAME);
+
+ bool all_streams = (stream == NULL);
+ bool all_events = (key == NULL || strcmp(key, "") == 0); // strcmp for libxpc compatibility
+ xpc_object_t events = NULL;
+
+ if (all_streams && !all_events) {
+ return EXINVAL;
+ }
+
+ if (all_streams || all_events) {
+ job_log(j, LOG_DEBUG, "Fetching all events%s%s", stream ? " for stream: " : "", stream ? stream : "");
+ events = xpc_dictionary_create(NULL, NULL, 0);
+ } else {
+ job_log(j, LOG_DEBUG, "Fetching stream/key: %s/%s", stream, key);
+ }
+
+ int result = ESRCH;
+ struct externalevent *eei = NULL;
+ LIST_FOREACH(eei, &j->events, job_le) {
+ if (all_streams) {
+ xpc_object_t sub = xpc_dictionary_get_value(events, eei->sys->name);
+ if (sub == NULL) {
+ sub = xpc_dictionary_create(NULL, NULL, 0);
+ xpc_dictionary_set_value(events, eei->sys->name, sub);
+ xpc_release(sub);
+ }
+ xpc_dictionary_set_value(sub, eei->name, eei->event);
+ } else if (strcmp(eei->sys->name, stream) == 0) {
+ if (all_events) {
+ xpc_dictionary_set_value(events, eei->name, eei->event);
+ } else if (strcmp(eei->name, key) == 0) {
+ job_log(j, LOG_DEBUG, "Found event.");
+ events = xpc_retain(eei->event);
+ break;
+ }
+ }
+ }
+
+ if (events) {
+ xpc_object_t reply2 = xpc_dictionary_create_reply(request);
+ xpc_dictionary_set_value(reply2, XPC_EVENT_ROUTINE_KEY_EVENT, events);
+ xpc_release(events);
+
+ *reply = reply2;
+ result = 0;
+ }
+
+ return result;
+}
+
+int
+xpc_event_channel_check_in(job_t j, xpc_object_t request, xpc_object_t *reply)
+{
+ const char *stream = xpc_dictionary_get_string(request, XPC_EVENT_ROUTINE_KEY_STREAM);
+ if (!stream) {
+ return EXINVAL;
+ }
+
+ job_log(j, LOG_DEBUG, "Checking in stream: %s", stream);
+
+ struct machservice *ms = NULL;
+ int error = xpc_event_find_channel(j, stream, &ms);
+ if (error) {
+ job_log(j, LOG_ERR, "Failed to check in: 0x%x: %s", error, xpc_strerror(error));
+ } else if (ms->isActive) {
+ job_log(j, LOG_ERR, "Attempt to check in on event channel multiple times: %s", stream);
+ error = EBUSY;
+ } else {
+ machservice_request_notifications(ms);
+
+ xpc_object_t reply2 = xpc_dictionary_create_reply(request);
+ xpc_dictionary_set_mach_recv(reply2, XPC_EVENT_ROUTINE_KEY_PORT, ms->port);
+ *reply = reply2;
+ error = 0;
+ }
+
+ return error;
+}
+
+int
+xpc_event_channel_look_up(job_t j, xpc_object_t request, xpc_object_t *reply)
+{
+ if (!j->event_monitor) {
+ return EPERM;
+ }
+
+ const char *stream = xpc_dictionary_get_string(request, XPC_EVENT_ROUTINE_KEY_STREAM);
+ if (!stream) {
+ return EXINVAL;
+ }
+
+ uint64_t token = xpc_dictionary_get_uint64(request, XPC_EVENT_ROUTINE_KEY_TOKEN);
+ if (!token) {
+ return EXINVAL;
+ }
+
+ job_log(j, LOG_DEBUG, "Looking up channel for stream/token: %s/%llu", stream, token);
+
+ struct externalevent *ee = externalevent_find(stream, token);
+ if (!ee) {
+ return ESRCH;
+ }
+
+ struct machservice *ms = NULL;
+ int error = xpc_event_find_channel(ee->job, stream, &ms);
+ if (!error) {
+ job_log(j, LOG_DEBUG, "Found event channel port: 0x%x", ms->port);
+ xpc_object_t reply2 = xpc_dictionary_create_reply(request);
+ xpc_dictionary_set_mach_send(reply2, XPC_EVENT_ROUTINE_KEY_PORT, ms->port);
+ *reply = reply2;
+ error = 0;
+ } else {
+ job_log(j, LOG_ERR, "Could not find event channel for stream/token: %s/%llu: 0x%x: %s", stream, token, error, xpc_strerror(error));
+ }
+
+ return error;
+}
+
+int
+xpc_event_provider_check_in(job_t j, xpc_object_t request, xpc_object_t *reply)
+{
+ if (!j->event_monitor) {
+ return EPERM;
+ }
+
+ /* This indicates that the event monitor is now safe to signal. This state is
+ * independent of whether this operation actually succeeds; we just need it
+ * to ignore SIGUSR1.
+ */
+ j->event_monitor_ready2signal = true;
+
+ const char *stream = xpc_dictionary_get_string(request, XPC_EVENT_ROUTINE_KEY_STREAM);
+ if (!stream) {
+ return EXINVAL;
+ }
+
+ job_log(j, LOG_DEBUG, "Provider checking in for stream: %s", stream);
+
+ xpc_object_t events = xpc_array_create(NULL, 0);
+ struct eventsystem *es = eventsystem_find(stream);
+ if (!es) {
+ /* If we had to create the event stream, there were no events, so just
+ * give back the empty array.
+ */
+ job_log(j, LOG_DEBUG, "Creating event stream.");
+ es = eventsystem_new(stream);
+ if (!job_assumes(j, es)) {
+ xpc_release(events);
+ return EXNOMEM;
+ }
+
+ if (strcmp(stream, "com.apple.launchd.helper") == 0) {
+ _launchd_support_system = es;
+ }
+ } else {
+ job_log(j, LOG_DEBUG, "Filling event array.");
+
+ struct externalevent *ei = NULL;
+ LIST_FOREACH(ei, &es->events, sys_le) {
+ xpc_array_set_uint64(events, XPC_ARRAY_APPEND, ei->id);
+ xpc_array_append_value(events, ei->event);
+ }
+ }
+
+ xpc_object_t reply2 = xpc_dictionary_create_reply(request);
+ xpc_dictionary_set_value(reply2, XPC_EVENT_ROUTINE_KEY_EVENTS, events);
+ xpc_release(events);
+ *reply = reply2;
+
+ return 0;
+}
+
+int
+xpc_event_provider_set_state(job_t j, xpc_object_t request, xpc_object_t *reply)
+{
+ job_t other_j = NULL;
+
+ if (!j->event_monitor) {
+ return EPERM;
+ }
+
+ const char *stream = xpc_dictionary_get_string(request, XPC_EVENT_ROUTINE_KEY_STREAM);
+ if (!stream) {
+ return EXINVAL;
+ }
+
+ uint64_t token = xpc_dictionary_get_uint64(request, XPC_EVENT_ROUTINE_KEY_TOKEN);
+ if (!token) {
+ return EXINVAL;
+ }
+
+ bool state = false;
+ xpc_object_t xstate = xpc_dictionary_get_value(request, XPC_EVENT_ROUTINE_KEY_STATE);
+ if (!xstate || xpc_get_type(xstate) != XPC_TYPE_BOOL) {
+ return EXINVAL;
+ } else {
+ state = xpc_bool_get_value(xstate);
+ }
+
+ job_log(j, LOG_DEBUG, "Setting event state to %s for stream/token: %s/%llu", state ? "true" : "false", stream, token);
+
+ struct externalevent *ei = externalevent_find(stream, token);
+ if (!ei) {
+ job_log(j, LOG_ERR, "Could not find stream/token: %s/%llu", stream, token);
+ return ESRCH;
+ }
+
+ other_j = ei->job;
+ ei->state = state;
+
+ if (ei->internal) {
+ job_log(ei->job, LOG_NOTICE, "Job should be able to exec(3) now.");
+ ei->job->waiting4ok = false;
+ externalevent_delete(ei);
+ }
+
+ (void)job_dispatch(other_j, false);
+
+ xpc_object_t reply2 = xpc_dictionary_create_reply(request);
+ *reply = reply2;
+
+ return 0;
+}
+
+bool
+xpc_event_demux(mach_port_t p, xpc_object_t request, xpc_object_t *reply)
+{
+ uint64_t op = xpc_dictionary_get_uint64(request, XPC_EVENT_ROUTINE_KEY_OP);
+ if (!op) {
+ return false;
+ }
+
+ audit_token_t token;
+ xpc_dictionary_get_audit_token(request, &token);
+ runtime_record_caller_creds(&token);
+
+ job_t j = job_mig_intran(p);
+ if (!j || j->anonymous) {
+ op = -1;
+ }
+
+ job_log(j, LOG_DEBUG, "Incoming XPC event request: %llu", op);
+
+ int error = -1;
+ switch (op) {
+ case XPC_EVENT_GET_NAME:
+ error = xpc_event_get_event_name(j, request, reply);
+ break;
+ case XPC_EVENT_SET:
+ error = xpc_event_set_event(j, request, reply);
+ break;
+ case XPC_EVENT_COPY:
+ error = xpc_event_copy_event(j, request, reply);
+ break;
+ case XPC_EVENT_CHECK_IN:
+ error = xpc_event_channel_check_in(j, request, reply);
+ break;
+ case XPC_EVENT_LOOK_UP:
+ error = xpc_event_channel_look_up(j, request, reply);
+ break;
+ case XPC_EVENT_PROVIDER_CHECK_IN:
+ error = xpc_event_provider_check_in(j, request, reply);
+ break;
+ case XPC_EVENT_PROVIDER_SET_STATE:
+ error = xpc_event_provider_set_state(j, request, reply);
+ break;
+ case -1:
+ if (j) {
+ job_log(j, LOG_ERR, "Unmanaged jobs may not make XPC Events requests.");
+ }
+ error = EPERM;
+ break;
+ default:
+ job_log(j, LOG_ERR, "Bogus opcode.");
+ error = EDOM;
+ }
+
+ if (error) {
+ xpc_object_t reply2 = xpc_dictionary_create_reply(request);
+ xpc_dictionary_set_uint64(reply2, XPC_EVENT_ROUTINE_KEY_ERROR, error);
+ *reply = reply2;
+ }
+
+ return true;
+}
+
+kern_return_t
+job_mig_kickstart(job_t j, name_t targetlabel, pid_t *out_pid, unsigned int flags)
+{
+ struct ldcred *ldc = runtime_get_caller_creds();
+ job_t otherj;
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (unlikely(!(otherj = job_find(NULL, targetlabel)))) {
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+
+#if TARGET_OS_EMBEDDED
+ bool allow_non_root_kickstart = j->username && otherj->username && (strcmp(j->username, otherj->username) == 0);
+#else
+ bool allow_non_root_kickstart = false;
+#endif
+
+ if (ldc->euid != 0 && ldc->euid != geteuid() && !allow_non_root_kickstart) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+#if HAVE_SANDBOX
+ if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+#endif
+
+ if (otherj->p && (flags & VPROCFLAG_STALL_JOB_EXEC)) {
+ return BOOTSTRAP_SERVICE_ACTIVE;
+ }
+
+ otherj->stall_before_exec = (flags & VPROCFLAG_STALL_JOB_EXEC);
+ otherj = job_dispatch(otherj, true);
+
+ if (!job_assumes(j, otherj && otherj->p)) {
+ // <rdar://problem/6787083> Clear this flag if we failed to start the job.
+ otherj->stall_before_exec = false;
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ *out_pid = otherj->p;
+
+ return 0;
+}
+
+kern_return_t
+job_mig_spawn_internal(job_t j, vm_offset_t indata, mach_msg_type_number_t indataCnt, mach_port_t asport, job_t *outj)
+{
+ launch_data_t jobdata = NULL;
+ size_t data_offset = 0;
+ struct ldcred *ldc = runtime_get_caller_creds();
+ job_t jr;
+
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (unlikely(j->deny_job_creation)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+#if HAVE_SANDBOX
+ if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+#endif
+
+ if (unlikely(pid1_magic && ldc->euid && ldc->uid)) {
+ job_log(j, LOG_DEBUG, "Punting spawn to per-user-context");
+ return VPROC_ERR_TRY_PER_USER;
+ }
+
+ if (!job_assumes(j, indataCnt != 0)) {
+ return 1;
+ }
+
+ runtime_ktrace0(RTKT_LAUNCHD_DATA_UNPACK);
+ if (!job_assumes(j, (jobdata = launch_data_unpack((void *)indata, indataCnt, NULL, 0, &data_offset, NULL)) != NULL)) {
+ return 1;
+ }
+
+ jobmgr_t target_jm = jobmgr_find_by_name(j->mgr, NULL);
+ if (!jobmgr_assumes(j->mgr, target_jm != NULL)) {
+ jobmgr_log(j->mgr, LOG_ERR, "This API can only be used by a process running within an Aqua session.");
+ return 1;
+ }
+
+ jr = jobmgr_import2(target_jm ?: j->mgr, jobdata);
+
+ launch_data_t label = NULL;
+ launch_data_t wait4debugger = NULL;
+ if (!jr) {
+ switch (errno) {
+ case EEXIST:
+ /* If EEXIST was returned, we know that there is a label string in
+ * the dictionary. So we don't need to check the types here; that
+ * has already been done.
+ */
+ label = launch_data_dict_lookup(jobdata, LAUNCH_JOBKEY_LABEL);
+ jr = job_find(NULL, launch_data_get_string(label));
+ if (job_assumes(j, jr != NULL) && !jr->p) {
+ wait4debugger = launch_data_dict_lookup(jobdata, LAUNCH_JOBKEY_WAITFORDEBUGGER);
+ if (wait4debugger && launch_data_get_type(wait4debugger) == LAUNCH_DATA_BOOL) {
+ if (launch_data_get_bool(wait4debugger)) {
+ /* If the job exists, we're going to kick-start it, but
+ * we need to give the caller the opportunity to start
+ * it suspended if it so desires. But this will only
+ * take effect if the job isn't running.
+ */
+ jr->wait4debugger_oneshot = true;
+ }
+ }
+ }
+
+ *outj = jr;
+ return BOOTSTRAP_NAME_IN_USE;
+ default:
+ return BOOTSTRAP_NO_MEMORY;
+ }
+ }
+
+ if (pid1_magic) {
+ jr->mach_uid = ldc->uid;
+ }
+
+ // TODO: Consolidate the app and legacy_LS_job bits.
+ jr->legacy_LS_job = true;
+ jr->abandon_pg = true;
+ jr->asport = asport;
+ jr->app = true;
+ uuid_clear(jr->expected_audit_uuid);
+ jr = job_dispatch(jr, true);
+
+ if (!job_assumes(j, jr != NULL)) {
+ job_remove(jr);
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (!job_assumes(jr, jr->p)) {
+ job_remove(jr);
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ job_log(jr, LOG_DEBUG, "Spawned by PID %u: %s", j->p, j->label);
+ *outj = jr;
+
+ return BOOTSTRAP_SUCCESS;
+}
+
+kern_return_t
+job_mig_spawn2(job_t j, mach_port_t rp, vm_offset_t indata, mach_msg_type_number_t indataCnt, mach_port_t asport, pid_t *child_pid, mach_port_t *obsvr_port)
+{
+ job_t nj = NULL;
+ kern_return_t kr = job_mig_spawn_internal(j, indata, indataCnt, asport, &nj);
+ if (likely(kr == KERN_SUCCESS)) {
+ if (job_setup_exit_port(nj) != KERN_SUCCESS) {
+ job_remove(nj);
+ kr = BOOTSTRAP_NO_MEMORY;
+ } else {
+ /* Do not return until the job has called exec(3), thereby making it
+ * safe for the caller to send it SIGCONT.
+ *
+ * <rdar://problem/9042798>
+ */
+ nj->spawn_reply_port = rp;
+ kr = MIG_NO_REPLY;
+ }
+ } else if (kr == BOOTSTRAP_NAME_IN_USE) {
+ bool was_running = nj->p;
+ if (job_dispatch(nj, true)) {
+ if (!was_running) {
+ job_log(nj, LOG_DEBUG, "Job exists but is not running. Kick-starting.");
+
+ if (job_setup_exit_port(nj) == KERN_SUCCESS) {
+ nj->spawn_reply_port = rp;
+ kr = MIG_NO_REPLY;
+ } else {
+ kr = BOOTSTRAP_NO_MEMORY;
+ }
+ } else {
+ *obsvr_port = MACH_PORT_NULL;
+ *child_pid = nj->p;
+ kr = KERN_SUCCESS;
+ }
+ } else {
+ job_log(nj, LOG_ERR, "Failed to dispatch job, requestor: %s", j->label);
+ kr = BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+ }
+
+ mig_deallocate(indata, indataCnt);
+ return kr;
+}
+
+launch_data_t
+job_do_legacy_ipc_request(job_t j, launch_data_t request, mach_port_t asport __attribute__((unused)))
+{
+ launch_data_t reply = NULL;
+
+ errno = ENOTSUP;
+ if (launch_data_get_type(request) == LAUNCH_DATA_STRING) {
+ if (strcmp(launch_data_get_string(request), LAUNCH_KEY_CHECKIN) == 0) {
+ reply = job_export(j);
+ job_checkin(j);
+ }
+ }
+
+ return reply;
+}
+
+#define LAUNCHD_MAX_LEGACY_FDS 128
+#define countof(x) (sizeof((x)) / sizeof((x[0])))
+
+kern_return_t
+job_mig_legacy_ipc_request(job_t j, vm_offset_t request,
+ mach_msg_type_number_t requestCnt, mach_port_array_t request_fds,
+ mach_msg_type_number_t request_fdsCnt, vm_offset_t *reply,
+ mach_msg_type_number_t *replyCnt, mach_port_array_t *reply_fdps,
+ mach_msg_type_number_t *reply_fdsCnt, mach_port_t asport)
+{
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ /* TODO: Once we support actions other than checking in, we must check the
+ * sandbox capabilities and EUID of the requestort.
+ */
+ size_t nout_fdps = 0;
+ size_t nfds = request_fdsCnt / sizeof(request_fds[0]);
+ if (nfds > LAUNCHD_MAX_LEGACY_FDS) {
+ job_log(j, LOG_ERR, "Too many incoming descriptors: %lu", nfds);
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ int in_fds[LAUNCHD_MAX_LEGACY_FDS];
+ size_t i = 0;
+ for (i = 0; i < nfds; i++) {
+ in_fds[i] = fileport_makefd(request_fds[i]);
+ if (in_fds[i] == -1) {
+ job_log(j, LOG_ERR, "Bad descriptor passed in legacy IPC request at index: %lu", i);
+ }
+ }
+
+ // DON'T goto outbad before this point.
+ *reply = 0;
+ *reply_fdps = NULL;
+ launch_data_t ldreply = NULL;
+
+ size_t dataoff = 0;
+ size_t fdoff = 0;
+ launch_data_t ldrequest = launch_data_unpack((void *)request, requestCnt, in_fds, nfds, &dataoff, &fdoff);
+ if (!ldrequest) {
+ job_log(j, LOG_ERR, "Invalid legacy IPC request passed.");
+ goto out_bad;
+ }
+
+ ldreply = job_do_legacy_ipc_request(j, ldrequest, asport);
+ if (!ldreply) {
+ ldreply = launch_data_new_errno(errno);
+ if (!ldreply) {
+ goto out_bad;
+ }
+ }
+
+ *replyCnt = 10 * 1024 * 1024;
+ mig_allocate(reply, *replyCnt);
+ if (!*reply) {
+ goto out_bad;
+ }
+
+ int out_fds[LAUNCHD_MAX_LEGACY_FDS];
+ size_t nout_fds = 0;
+ size_t sz = launch_data_pack(ldreply, (void *)*reply, *replyCnt, out_fds, &nout_fds);
+ if (!sz) {
+ job_log(j, LOG_ERR, "Could not pack legacy IPC reply.");
+ goto out_bad;
+ }
+
+ if (nout_fds) {
+ if (nout_fds > 128) {
+ job_log(j, LOG_ERR, "Too many outgoing descriptors: %lu", nout_fds);
+ goto out_bad;
+ }
+
+ *reply_fdsCnt = nout_fds * sizeof((*reply_fdps)[0]);
+ mig_allocate((vm_address_t *)reply_fdps, *reply_fdsCnt);
+ if (!*reply_fdps) {
+ goto out_bad;
+ }
+
+ for (i = 0; i < nout_fds; i++) {
+ mach_port_t fp = MACH_PORT_NULL;
+ /* Whatever. Worst case is that we insert MACH_PORT_NULL. Not a big
+ * deal. Note, these get stuffed into an array whose disposition is
+ * mach_port_move_send_t, so we don't have to worry about them after
+ * returning.
+ */
+ if (fileport_makeport(out_fds[i], &fp) != 0) {
+ job_log(j, LOG_ERR, "Could not pack response descriptor at index: %lu: %d: %s", i, errno, strerror(errno));
+ }
+ (*reply_fdps)[i] = fp;
+ }
+
+ nout_fdps = nout_fds;
+ } else {
+ *reply_fdsCnt = 0;
+ }
+
+ mig_deallocate(request, requestCnt);
+ launch_data_free(ldreply);
+ ldreply = NULL;
+
+ // Unused for now.
+ (void)launchd_mport_deallocate(asport);
+
+ return BOOTSTRAP_SUCCESS;
+
+out_bad:
+ for (i = 0; i < nfds; i++) {
+ (void)close(in_fds[i]);
+ }
+
+ for (i = 0; i < nout_fds; i++) {
+ (void)launchd_mport_deallocate((*reply_fdps)[i]);
+ }
+
+ if (*reply) {
+ mig_deallocate(*reply, *replyCnt);
+ }
+
+ /* We should never hit this since the last goto out is in the case that
+ * allocating this fails.
+ */
+ if (*reply_fdps) {
+ mig_deallocate((vm_address_t)*reply_fdps, *reply_fdsCnt);
+ }
+
+ if (ldreply) {
+ launch_data_free(ldreply);
+ }
+
+ return BOOTSTRAP_NO_MEMORY;
+}
+
+void
+jobmgr_init(bool sflag)
+{
+ const char *root_session_type = pid1_magic ? VPROCMGR_SESSION_SYSTEM : VPROCMGR_SESSION_BACKGROUND;
+ SLIST_INIT(&s_curious_jobs);
+ LIST_INIT(&s_needing_sessions);
+
+ osx_assert((root_jobmgr = jobmgr_new(NULL, MACH_PORT_NULL, MACH_PORT_NULL, sflag, root_session_type, false, MACH_PORT_NULL)) != NULL);
+ osx_assert((_s_xpc_system_domain = jobmgr_new_xpc_singleton_domain(root_jobmgr, "com.apple.xpc.system")) != NULL);
+ _s_xpc_system_domain->req_asid = launchd_audit_session;
+ _s_xpc_system_domain->req_asport = launchd_audit_port;
+ _s_xpc_system_domain->shortdesc = "system";
+ if (pid1_magic) {
+ root_jobmgr->monitor_shutdown = true;
+ }
+
+ uint32_t fflags = NOTE_ATTRIB | NOTE_LINK | NOTE_REVOKE | NOTE_EXTEND | NOTE_WRITE;
+ s_no_hang_fd = open("/dev/autofs_nowait", O_EVTONLY | O_NONBLOCK);
+ if (likely(s_no_hang_fd == -1)) {
+ if (jobmgr_assumes_zero_p(root_jobmgr, (s_no_hang_fd = open("/dev", O_EVTONLY | O_NONBLOCK))) != -1) {
+ (void)jobmgr_assumes_zero_p(root_jobmgr, kevent_mod((uintptr_t)s_no_hang_fd, EVFILT_VNODE, EV_ADD, fflags, 0, root_jobmgr));
+ }
+ }
+ s_no_hang_fd = _fd(s_no_hang_fd);
+}
+
+size_t
+our_strhash(const char *s)
+{
+ size_t c, r = 5381;
+
+ /* djb2
+ * This algorithm was first reported by Dan Bernstein many years ago in comp.lang.c
+ */
+
+ while ((c = *s++)) {
+ r = ((r << 5) + r) + c; // hash*33 + c
+ }
+
+ return r;
+}
+
+size_t
+hash_label(const char *label)
+{
+ return our_strhash(label) % LABEL_HASH_SIZE;
+}
+
+size_t
+hash_ms(const char *msstr)
+{
+ return our_strhash(msstr) % MACHSERVICE_HASH_SIZE;
+}
+
+bool
+waiting4removal_new(job_t j, mach_port_t rp)
+{
+ struct waiting_for_removal *w4r;
+
+ if (!job_assumes(j, (w4r = malloc(sizeof(struct waiting_for_removal))) != NULL)) {
+ return false;
+ }
+
+ w4r->reply_port = rp;
+
+ SLIST_INSERT_HEAD(&j->removal_watchers, w4r, sle);
+
+ return true;
+}
+
+void
+waiting4removal_delete(job_t j, struct waiting_for_removal *w4r)
+{
+ (void)job_assumes_zero(j, job_mig_send_signal_reply(w4r->reply_port, 0));
+
+ SLIST_REMOVE(&j->removal_watchers, w4r, waiting_for_removal, sle);
+
+ free(w4r);
+}
+
+size_t
+get_kern_max_proc(void)
+{
+ int mib[] = { CTL_KERN, KERN_MAXPROC };
+ int max = 100;
+ size_t max_sz = sizeof(max);
+
+ (void)posix_assumes_zero(sysctl(mib, 2, &max, &max_sz, NULL, 0));
+
+ return max;
+}
+
+// See rdar://problem/6271234
+void
+eliminate_double_reboot(void)
+{
+ if (unlikely(!pid1_magic)) {
+ return;
+ }
+
+ struct stat sb;
+ const char *argv[] = { _PATH_BSHELL, "/etc/rc.deferred_install", NULL };
+ int result = -1;
+
+ if (unlikely(stat(argv[1], &sb) != -1)) {
+ jobmgr_log(root_jobmgr, LOG_DEBUG | LOG_CONSOLE, "Going to run deferred install script.");
+
+ pid_t p = 0;
+ result = posix_spawnp(&p, argv[0], NULL, NULL, (char **)argv, environ);
+ if (result == -1) {
+ jobmgr_log(root_jobmgr, LOG_WARNING | LOG_CONSOLE, "Couldn't run deferred install script: %d: %s", result, strerror(result));
+ goto out;
+ }
+
+ int wstatus = 0;
+ result = waitpid(p, &wstatus, 0);
+ if (result == -1) {
+ jobmgr_log(root_jobmgr, LOG_WARNING | LOG_CONSOLE, "Failed to reap deferred install script: %d: %s", errno, strerror(errno));
+ goto out;
+ }
+
+ if (WIFEXITED(wstatus)) {
+ if ((result = WEXITSTATUS(wstatus)) == 0) {
+ jobmgr_log(root_jobmgr, LOG_DEBUG | LOG_CONSOLE, "Deferred install script completed successfully.");
+ } else {
+ jobmgr_log(root_jobmgr, LOG_WARNING | LOG_CONSOLE, "Deferred install script failed with status: %d", WEXITSTATUS(wstatus));
+ }
+ } else {
+ jobmgr_log(root_jobmgr, LOG_WARNING | LOG_CONSOLE, "Weirdness with install script: %d", wstatus);
+ }
+ }
+out:
+ if (result == 0) {
+ /* If the unlink(2) was to fail, it would be most likely fail with
+ * EBUSY. All the other failure cases for unlink(2) don't apply when
+ * we're running under PID 1 and have verified that the file exists.
+ * Outside of someone deliberately messing with us (like if
+ * /etc/rc.deferredinstall is actually a looping sym-link or a mount
+ * point for a filesystem) and I/O errors, we should be good.
+ */
+ if (unlink(argv[1]) == -1) {
+ jobmgr_log(root_jobmgr, LOG_WARNING | LOG_CONSOLE, "Failed to remove deferred install script: %d: %s", errno, strerror(errno));
+ }
+ }
+}
+
+void
+jetsam_property_setup(launch_data_t obj, const char *key, job_t j)
+{
+ job_log(j, LOG_DEBUG, "Setting Jetsam properties for job...");
+ if (strcasecmp(key, LAUNCH_JOBKEY_JETSAMPRIORITY) == 0 && launch_data_get_type(obj) == LAUNCH_DATA_INTEGER) {
+ j->jetsam_priority = (typeof(j->jetsam_priority))launch_data_get_integer(obj);
+ job_log(j, LOG_DEBUG, "Priority: %d", j->jetsam_priority);
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_JETSAMMEMORYLIMIT) == 0 && launch_data_get_type(obj) == LAUNCH_DATA_INTEGER) {
+ j->jetsam_memlimit = (typeof(j->jetsam_memlimit))launch_data_get_integer(obj);
+ job_log(j, LOG_DEBUG, "Memory limit: %d", j->jetsam_memlimit);
+ } else if (strcasecmp(key, LAUNCH_KEY_JETSAMFRONTMOST) == 0) {
+ /* Ignore. We only recognize this key so we don't complain when we get SpringBoard's request.
+ * You can't set this in a plist.
+ */
+ } else if (strcasecmp(key, LAUNCH_KEY_JETSAMACTIVE) == 0) {
+ // Ignore.
+ } else if (strcasecmp(key, LAUNCH_KEY_JETSAMLABEL) == 0) {
+ /* Ignore. This key is present in SpringBoard's request dictionary, so we don't want to
+ * complain about it.
+ */
+ } else {
+ job_log(j, LOG_ERR, "Unknown Jetsam key: %s", key);
+ }
+
+ if (unlikely(!j->jetsam_properties)) {
+ j->jetsam_properties = true;
+ }
+}
+
+#if TARGET_OS_EMBEDDED
+int
+launchd_set_jetsam_priorities(launch_data_t priorities)
+{
+ kern_return_t result = 0;
+
+ if (launch_data_get_type(priorities) != LAUNCH_DATA_ARRAY) {
+ return EINVAL;
+ }
+ if (!launchd_embedded_handofgod) {
+ return EPERM;
+ }
+
+ size_t npris = launch_data_array_get_count(priorities);
+
+ job_t ji = NULL;
+ size_t i = 0;
+ for (i = 0; i < npris; i++) {
+ launch_data_t ldi = launch_data_array_get_index(priorities, i);
+ if (launch_data_get_type(ldi) != LAUNCH_DATA_DICTIONARY) {
+ continue;
+ }
+
+ launch_data_t ldlabel = NULL;
+ if (!(ldlabel = launch_data_dict_lookup(ldi, LAUNCH_KEY_JETSAMLABEL))) {
+ continue;
+ }
+ const char *label = launch_data_get_string(ldlabel);
+
+ ji = job_find(root_jobmgr, label);
+ if (!ji) {
+ continue;
+ }
+
+ launch_data_dict_iterate(ldi, (void (*)(launch_data_t, const char *, void *))jetsam_property_setup, ji);
+
+ launch_data_t frontmost = NULL;
+ if ((frontmost = launch_data_dict_lookup(ldi, LAUNCH_KEY_JETSAMFRONTMOST)) && launch_data_get_type(frontmost) == LAUNCH_DATA_BOOL) {
+ ji->jetsam_frontmost = launch_data_get_bool(frontmost);
+ }
+
+ launch_data_t active = NULL;
+ if ((active = launch_data_dict_lookup(ldi, LAUNCH_KEY_JETSAMACTIVE)) && launch_data_get_type(active) == LAUNCH_DATA_BOOL) {
+ ji->jetsam_active = launch_data_get_bool(active);
+ }
+
+ launchd_update_jetsam_list(ji);
+
+ result = result != 0 ? errno : 0;
+ }
+
+ return result;
+}
+
+int
+launchd_update_jetsam_list(job_t j)
+{
+ memorystatus_priority_entry_t mpe;
+ kern_return_t result;
+
+ mpe.pid = j->p;
+ mpe.priority = j->jetsam_priority;
+ mpe.flags = 0;
+ mpe.flags |= j->jetsam_frontmost ? kMemorystatusFlagsFrontmost : 0;
+ mpe.flags |= j->jetsam_active ? kMemorystatusFlagsActive : 0;
+
+ // ToDo - cache MIB if we keep this interface
+ (void)posix_assumes_zero(result = sysctlbyname("kern.memorystatus_jetsam_change", NULL, NULL, &mpe, sizeof(memorystatus_priority_entry_t)));
+ return result;
+}
+#endif
--- /dev/null
+/*
+ * Copyright (c) 2005 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@
+ */
+
+#ifndef __LAUNCHD_CORE_LOGIC__
+#define __LAUNCHD_CORE_LOGIC__
+
+#include "runtime.h"
+#include "bootstrap.h"
+#include "launch.h"
+
+typedef struct job_s *job_t;
+typedef struct jobmgr_s *jobmgr_t;
+
+extern jobmgr_t root_jobmgr;
+extern mach_port_t launchd_audit_port;
+extern au_asid_t launchd_audit_session;
+extern bool launchd_flat_mach_namespace;
+extern bool launchd_embedded_handofgod;
+
+void jobmgr_init(bool);
+jobmgr_t jobmgr_shutdown(jobmgr_t jm);
+void jobmgr_dispatch_all_semaphores(jobmgr_t jm);
+void jobmgr_dispatch_all_interested(jobmgr_t jm, job_t j);
+jobmgr_t jobmgr_delete_anything_with_port(jobmgr_t jm, mach_port_t port);
+
+launch_data_t job_export_all(void);
+
+job_t job_dispatch(job_t j, bool kickstart); /* returns j on success, NULL on job removal */
+job_t job_find(jobmgr_t jm, const char *label);
+job_t job_find_by_service_port(mach_port_t p);
+bool job_ack_port_destruction(mach_port_t p);
+bool job_is_anonymous(job_t j);
+launch_data_t job_export(job_t j);
+void job_stop(job_t j);
+void job_checkin(job_t j);
+void job_remove(job_t j);
+bool job_is_god(job_t j);
+job_t job_import(launch_data_t pload);
+launch_data_t job_import_bulk(launch_data_t pload);
+job_t job_mig_intran(mach_port_t mp);
+void job_mig_destructor(job_t j);
+void job_ack_no_senders(job_t j);
+void job_log(job_t j, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4)));
+void job_set_pid_crashed(pid_t p);
+
+#if TARGET_OS_EMBEDDED
+int launchd_set_jetsam_priorities(launch_data_t priorities);
+int launchd_update_jetsam_list(job_t j);
+#endif
+
+bool xpc_event_demux(mach_port_t p, xpc_object_t request, xpc_object_t *reply);
+
+#endif /* __LAUNCHD_CORE_LOGIC__ */
--- /dev/null
+#include <mach/std_types.defs>
+#include <mach/mach_types.defs>
+import "vproc.h";
+import "vproc_priv.h";
+import "vproc_internal.h";
+
+subsystem launchd_helper 4241011;
+
+userprefix helper_downcall_;
+serverprefix helper_recv_;
+
+skip;
+
+/* For coreservicesd to harvest exit status, not actually for UserEventAgent. */
+simpleroutine
+wait(
+ p : mach_port_move_send_once_t;
+ status : int
+);
--- /dev/null
+/*
+ * Copyright (c) 2006 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@
+ */
+
+subsystem internal 137000;
+
+#include <mach/std_types.defs>
+#include <mach/mach_types.defs>
+
+serverprefix x_;
+
+routine
+handle_kqueue(
+ p : mach_port_t;
+ fd : integer_t
+);
--- /dev/null
+/*
+ * Copyright (c) 2005 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 "config.h"
+#include "ipc.h"
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/event.h>
+#include <sys/stat.h>
+#include <sys/ucred.h>
+#include <sys/fcntl.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/sysctl.h>
+#include <sys/sockio.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <paths.h>
+#include <string.h>
+#include <assumes.h>
+
+#include "launch.h"
+#include "launch_priv.h"
+#include "launchd.h"
+#include "runtime.h"
+#include "core.h"
+
+extern char **environ;
+
+static LIST_HEAD(, conncb) connections;
+
+static launch_data_t adjust_rlimits(launch_data_t in);
+
+static void ipc_readmsg2(launch_data_t data, const char *cmd, void *context);
+static void ipc_readmsg(launch_data_t msg, void *context);
+
+static void ipc_listen_callback(void *obj __attribute__((unused)), struct kevent *kev);
+
+static kq_callback kqipc_listen_callback = ipc_listen_callback;
+
+static pid_t ipc_self = 0;
+
+char *sockpath = NULL;
+static char *sockdir = NULL;
+
+static bool ipc_inited = false;
+
+static void
+ipc_clean_up(void)
+{
+ if (ipc_self != getpid()) {
+ return;
+ }
+
+ if (-1 == unlink(sockpath)) {
+ launchd_syslog(LOG_WARNING, "unlink(\"%s\"): %s", sockpath, strerror(errno));
+ } else if (-1 == rmdir(sockdir)) {
+ launchd_syslog(LOG_WARNING, "rmdir(\"%s\"): %s", sockdir, strerror(errno));
+ }
+}
+
+void
+ipc_server_init(void)
+{
+ struct sockaddr_un sun;
+ mode_t oldmask;
+ int r, fd = -1;
+ char ourdir[1024];
+
+ if (ipc_inited) {
+ return;
+ }
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+
+ if (pid1_magic) {
+ strcpy(ourdir, LAUNCHD_SOCK_PREFIX);
+ strncpy(sun.sun_path, LAUNCHD_SOCK_PREFIX "/sock", sizeof(sun.sun_path));
+
+ unlink(ourdir);
+ if (mkdir(ourdir, S_IRWXU) == -1) {
+ if (errno == EROFS) {
+ goto out_bad;
+ } else if (errno == EEXIST) {
+ struct stat sb;
+ stat(ourdir, &sb);
+ if (!S_ISDIR(sb.st_mode)) {
+ errno = EEXIST;
+ launchd_syslog(LOG_ERR, "mkdir(\"%s\"): %s", LAUNCHD_SOCK_PREFIX, strerror(errno));
+ goto out_bad;
+ }
+ } else {
+ launchd_syslog(LOG_ERR, "mkdir(\"%s\"): %s", ourdir, strerror(errno));
+ goto out_bad;
+ }
+ }
+ } else {
+ snprintf(ourdir, sizeof(ourdir), _PATH_TMP "launchd-%u.XXXXXX", getpid());
+ if (mkdtemp(ourdir) == NULL) {
+ launchd_syslog(LOG_ERR, "Could not create critical directory \"%s\": %s", ourdir, strerror(errno));
+ goto out_bad;
+ }
+ snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/sock", ourdir);
+ }
+
+ if (unlink(sun.sun_path) == -1 && errno != ENOENT) {
+ if (errno != EROFS) {
+ launchd_syslog(LOG_ERR, "unlink(\"thesocket\"): %s", strerror(errno));
+ }
+ goto out_bad;
+ }
+
+ if (posix_assumes_zero(fd = _fd(socket(AF_UNIX, SOCK_STREAM, 0))) == -1) {
+ goto out_bad;
+ }
+
+ oldmask = umask(S_IRWXG|S_IRWXO);
+ r = bind(fd, (struct sockaddr *)&sun, sizeof(sun));
+ umask(oldmask);
+
+ if (r == -1) {
+ if (errno != EROFS) {
+ launchd_syslog(LOG_ERR, "bind(\"thesocket\"): %s", strerror(errno));
+ }
+ goto out_bad;
+ }
+
+ if (listen(fd, SOMAXCONN) == -1) {
+ launchd_syslog(LOG_ERR, "listen(\"thesocket\"): %s", strerror(errno));
+ goto out_bad;
+ }
+
+ if (kevent_mod(fd, EVFILT_READ, EV_ADD, 0, 0, &kqipc_listen_callback) == -1) {
+ launchd_syslog(LOG_ERR, "kevent_mod(\"thesocket\", EVFILT_READ): %s", strerror(errno));
+ goto out_bad;
+ }
+
+ ipc_inited = true;
+
+ sockdir = strdup(ourdir);
+ sockpath = strdup(sun.sun_path);
+ ipc_self = getpid();
+ atexit(ipc_clean_up);
+
+out_bad:
+ if (!ipc_inited && fd != -1) {
+ (void)runtime_close(fd);
+ }
+}
+
+void
+ipc_open(int fd, job_t j)
+{
+ struct conncb *c = calloc(1, sizeof(struct conncb));
+
+ fcntl(fd, F_SETFL, O_NONBLOCK);
+
+ c->kqconn_callback = ipc_callback;
+ if (j) {
+ c->conn = launchd_fdopen(-1, fd);
+ } else {
+ c->conn = launchd_fdopen(fd, -1);
+ }
+
+ c->j = j;
+ LIST_INSERT_HEAD(&connections, c, sle);
+ kevent_mod(fd, EVFILT_READ, EV_ADD, 0, 0, &c->kqconn_callback);
+}
+
+void
+ipc_listen_callback(void *obj __attribute__((unused)), struct kevent *kev)
+{
+ struct sockaddr_un sun;
+ socklen_t sl = sizeof(sun);
+ int cfd;
+
+ if ((cfd = _fd(accept(kev->ident, (struct sockaddr *)&sun, &sl))) == -1) {
+ return;
+ }
+
+ ipc_open(cfd, NULL);
+}
+
+void
+ipc_callback(void *obj, struct kevent *kev)
+{
+ struct conncb *c = obj;
+ int r;
+
+ if (kev->filter == EVFILT_READ) {
+ if (launchd_msg_recv(c->conn, ipc_readmsg, c) == -1 && errno != EAGAIN) {
+ if (errno != ECONNRESET) {
+ launchd_syslog(LOG_DEBUG, "%s(): recv: %s", __func__, strerror(errno));
+ }
+ ipc_close(c);
+ }
+ } else if (kev->filter == EVFILT_WRITE) {
+ r = launchd_msg_send(c->conn, NULL);
+ if (r == -1) {
+ if (errno != EAGAIN) {
+ launchd_syslog(LOG_DEBUG, "%s(): send: %s", __func__, strerror(errno));
+ ipc_close(c);
+ }
+ } else if (r == 0) {
+ kevent_mod(launchd_getfd(c->conn), EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
+ }
+ } else {
+ launchd_syslog(LOG_DEBUG, "%s(): unknown filter type!", __func__);
+ ipc_close(c);
+ }
+}
+
+static void
+set_user_env(launch_data_t obj, const char *key, void *context __attribute__((unused)))
+{
+ const char *v = launch_data_get_string(obj);
+ if (v) {
+ setenv(key, v, 1);
+ } else {
+ launchd_syslog(LOG_WARNING, "Attempt to set NULL environment variable: %s (type = %d)", key, launch_data_get_type(obj));
+ }
+}
+
+void
+ipc_close_all_with_job(job_t j)
+{
+ struct conncb *ci, *cin;
+
+ LIST_FOREACH_SAFE(ci, &connections, sle, cin) {
+ if (ci->j == j) {
+ ipc_close(ci);
+ }
+ }
+}
+
+void
+ipc_close_fds(launch_data_t o)
+{
+ size_t i;
+
+ switch (launch_data_get_type(o)) {
+ case LAUNCH_DATA_DICTIONARY:
+ launch_data_dict_iterate(o, (void (*)(launch_data_t, const char *, void *))ipc_close_fds, NULL);
+ break;
+ case LAUNCH_DATA_ARRAY:
+ for (i = 0; i < launch_data_array_get_count(o); i++)
+ ipc_close_fds(launch_data_array_get_index(o, i));
+ break;
+ case LAUNCH_DATA_FD:
+ if (launch_data_get_fd(o) != -1) {
+ (void)runtime_close(launch_data_get_fd(o));
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void
+ipc_revoke_fds(launch_data_t o)
+{
+ size_t i;
+
+ switch (launch_data_get_type(o)) {
+ case LAUNCH_DATA_DICTIONARY:
+ launch_data_dict_iterate(o, (void (*)(launch_data_t, const char *, void *))ipc_revoke_fds, NULL);
+ break;
+ case LAUNCH_DATA_ARRAY:
+ for (i = 0; i < launch_data_array_get_count(o); i++)
+ ipc_revoke_fds(launch_data_array_get_index(o, i));
+ break;
+ case LAUNCH_DATA_FD:
+ launch_data_set_fd(o, -1);
+ break;
+ default:
+ break;
+ }
+}
+
+struct readmsg_context {
+ struct conncb *c;
+ launch_data_t resp;
+};
+
+void
+ipc_readmsg(launch_data_t msg, void *context)
+{
+ struct readmsg_context rmc = { context, NULL };
+
+ if (LAUNCH_DATA_DICTIONARY == launch_data_get_type(msg)) {
+ launch_data_dict_iterate(msg, ipc_readmsg2, &rmc);
+ } else if (LAUNCH_DATA_STRING == launch_data_get_type(msg)) {
+ ipc_readmsg2(NULL, launch_data_get_string(msg), &rmc);
+ } else {
+ rmc.resp = launch_data_new_errno(EINVAL);
+ }
+
+ if (NULL == rmc.resp) {
+ rmc.resp = launch_data_new_errno(ENOSYS);
+ }
+
+ ipc_close_fds(msg);
+
+ if (launchd_msg_send(rmc.c->conn, rmc.resp) == -1) {
+ if (errno == EAGAIN) {
+ kevent_mod(launchd_getfd(rmc.c->conn), EVFILT_WRITE, EV_ADD, 0, 0, &rmc.c->kqconn_callback);
+ } else {
+ launchd_syslog(LOG_DEBUG, "launchd_msg_send() == -1: %s", strerror(errno));
+ ipc_close(rmc.c);
+ }
+ }
+ launch_data_free(rmc.resp);
+}
+
+void
+ipc_readmsg2(launch_data_t data, const char *cmd, void *context)
+{
+ struct readmsg_context *rmc = context;
+ launch_data_t resp = NULL;
+ job_t j;
+
+ if (rmc->resp) {
+ return;
+ }
+
+ /* Do not allow commands other than check-in to come over the trusted socket
+ * on the Desktop. On Embedded, allow all commands over the trusted socket
+ * if the job has the God Mode key set.
+ */
+#if TARGET_OS_EMBEDDED
+ bool allow_privileged_ops = (!rmc->c->j || job_is_god(rmc->c->j));
+#else
+ bool allow_privileged_ops = !rmc->c->j;
+#endif
+
+ if (rmc->c->j && strcmp(cmd, LAUNCH_KEY_CHECKIN) == 0) {
+ resp = job_export(rmc->c->j);
+ job_checkin(rmc->c->j);
+ } else if (allow_privileged_ops) {
+#if TARGET_OS_EMBEDDED
+ launchd_embedded_handofgod = rmc->c->j && job_is_god(rmc->c->j);
+#endif
+ if (data == NULL) {
+ if (!strcmp(cmd, LAUNCH_KEY_SHUTDOWN)) {
+ launchd_shutdown();
+ resp = launch_data_new_errno(0);
+ } else if (!strcmp(cmd, LAUNCH_KEY_GETJOBS)) {
+ resp = job_export_all();
+ ipc_revoke_fds(resp);
+ } else if (!strcmp(cmd, LAUNCH_KEY_GETRESOURCELIMITS)) {
+ resp = adjust_rlimits(NULL);
+ } else if (!strcmp(cmd, LAUNCH_KEY_GETRUSAGESELF)) {
+ struct rusage rusage;
+ getrusage(RUSAGE_SELF, &rusage);
+ resp = launch_data_new_opaque(&rusage, sizeof(rusage));
+ } else if (!strcmp(cmd, LAUNCH_KEY_GETRUSAGECHILDREN)) {
+ struct rusage rusage;
+ getrusage(RUSAGE_CHILDREN, &rusage);
+ resp = launch_data_new_opaque(&rusage, sizeof(rusage));
+ }
+ } else {
+ if (!strcmp(cmd, LAUNCH_KEY_STARTJOB)) {
+ if ((j = job_find(NULL, launch_data_get_string(data))) != NULL) {
+ errno = job_dispatch(j, true) ? 0 : errno;
+ }
+ resp = launch_data_new_errno(errno);
+ } else if (!strcmp(cmd, LAUNCH_KEY_STOPJOB)) {
+ if ((j = job_find(NULL, launch_data_get_string(data))) != NULL) {
+ errno = 0;
+ job_stop(j);
+ }
+ resp = launch_data_new_errno(errno);
+ } else if (!strcmp(cmd, LAUNCH_KEY_REMOVEJOB)) {
+ if ((j = job_find(NULL, launch_data_get_string(data))) != NULL) {
+ errno = 0;
+ job_remove(j);
+ }
+ resp = launch_data_new_errno(errno);
+ } else if (!strcmp(cmd, LAUNCH_KEY_SUBMITJOB)) {
+ if (launch_data_get_type(data) == LAUNCH_DATA_ARRAY) {
+ resp = job_import_bulk(data);
+ } else {
+ if (job_import(data)) {
+ errno = 0;
+ }
+ resp = launch_data_new_errno(errno);
+ }
+ } else if (!strcmp(cmd, LAUNCH_KEY_UNSETUSERENVIRONMENT)) {
+ unsetenv(launch_data_get_string(data));
+ resp = launch_data_new_errno(0);
+ } else if (!strcmp(cmd, LAUNCH_KEY_SETUSERENVIRONMENT)) {
+ launch_data_dict_iterate(data, set_user_env, NULL);
+ resp = launch_data_new_errno(0);
+ } else if (!strcmp(cmd, LAUNCH_KEY_SETRESOURCELIMITS)) {
+ resp = adjust_rlimits(data);
+ } else if (!strcmp(cmd, LAUNCH_KEY_GETJOB)) {
+ if ((j = job_find(NULL, launch_data_get_string(data))) == NULL) {
+ resp = launch_data_new_errno(errno);
+ } else {
+ resp = job_export(j);
+ ipc_revoke_fds(resp);
+ }
+ } else if (!strcmp(cmd, LAUNCH_KEY_SETPRIORITYLIST)) {
+#if TARGET_OS_EMBEDDED
+ resp = launch_data_new_errno(launchd_set_jetsam_priorities(data));
+#else
+ resp = launch_data_new_errno(ENOTSUP);
+#endif
+ }
+ }
+#if TARGET_OS_EMBEDDED
+ launchd_embedded_handofgod = false;
+#endif
+ } else {
+ resp = launch_data_new_errno(EACCES);
+ }
+
+ rmc->resp = resp;
+}
+
+static int
+close_abi_fixup(int fd)
+{
+ return runtime_close(fd);
+}
+
+void
+ipc_close(struct conncb *c)
+{
+ LIST_REMOVE(c, sle);
+ launchd_close(c->conn, close_abi_fixup);
+ free(c);
+}
+
+launch_data_t
+adjust_rlimits(launch_data_t in)
+{
+ /* If I never have to deal with this rlimit nonsense again, I'll be a very
+ * happy man.
+ */
+ struct rlimit l[RLIM_NLIMITS];
+ struct rlimit *ltmp;
+ size_t i,ltmpsz;
+
+ for (i = 0; i < RLIM_NLIMITS; i++) {
+ (void)posix_assumes_zero(getrlimit(i, l + i));
+ }
+
+ if (in) {
+ ltmp = launch_data_get_opaque(in);
+ ltmpsz = launch_data_get_opaque_size(in);
+
+ if (ltmpsz > sizeof(l)) {
+ launchd_syslog(LOG_WARNING, "Too much rlimit data sent!");
+ ltmpsz = sizeof(l);
+ }
+
+ for (i = 0; i < (ltmpsz / sizeof(struct rlimit)); i++) {
+ if (ltmp[i].rlim_cur == l[i].rlim_cur && ltmp[i].rlim_max == l[i].rlim_max) {
+ continue;
+ }
+
+ if (/* XXX readcfg_pid && */ pid1_magic && (i == RLIMIT_NOFILE || i == RLIMIT_NPROC)) {
+ int gmib[] = { CTL_KERN, KERN_MAXPROC };
+ int pmib[] = { CTL_KERN, KERN_MAXPROCPERUID };
+ const char *gstr = "kern.maxproc";
+ const char *pstr = "kern.maxprocperuid";
+ int gval = ltmp[i].rlim_max;
+ int pval = ltmp[i].rlim_cur;
+ switch (i) {
+ case RLIMIT_NOFILE:
+ gmib[1] = KERN_MAXFILES;
+ pmib[1] = KERN_MAXFILESPERPROC;
+ gstr = "kern.maxfiles";
+ pstr = "kern.maxfilesperproc";
+ break;
+ default:
+ break;
+ }
+
+ if (gval > 0) {
+ (void)posix_assumes_zero(sysctl(gmib, 2, NULL, NULL, &gval, sizeof(gval)));
+ } else {
+ launchd_syslog(LOG_WARNING, "sysctl(\"%s\"): can't be zero", gstr);
+ }
+ if (pval > 0) {
+ (void)posix_assumes_zero(sysctl(pmib, 2, NULL, NULL, &pval, sizeof(pval)));
+ } else {
+ launchd_syslog(LOG_WARNING, "sysctl(\"%s\"): can't be zero", pstr);
+ }
+ }
+ (void)posix_assumes_zero(setrlimit(i, ltmp + i));
+ /* the kernel may have clamped the values we gave it */
+ (void)posix_assumes_zero(getrlimit(i, l + i));
+ }
+ }
+
+ return launch_data_new_opaque(l, sizeof(struct rlimit) * RLIM_NLIMITS);
+}
--- /dev/null
+/*
+ * Copyright (c) 2005 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@
+ */
+
+#ifndef __LAUNCHD_IPC_H__
+#define __LAUNCHD_IPC_H__
+
+#include <sys/queue.h>
+#include "runtime.h"
+#include "core.h"
+#include "launch_priv.h"
+#include "launch_internal.h"
+
+struct conncb {
+ kq_callback kqconn_callback;
+ LIST_ENTRY(conncb) sle;
+ launch_t conn;
+ job_t j;
+};
+
+extern char *sockpath;
+
+void ipc_open(int fd, job_t j);
+void ipc_close_all_with_job(job_t j);
+void ipc_close(struct conncb *c);
+void ipc_callback(void *, struct kevent *);
+void ipc_revoke_fds(launch_data_t o);
+void ipc_close_fds(launch_data_t o);
+void ipc_server_init(void);
+
+#endif /* __LAUNCHD_IPC_H__ */
--- /dev/null
+/*
+ * Copyright (c) 1999-2004 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@
+ */
+/*
+ * bootstrap -- fundamental service initiator and port server
+ * Mike DeMoney, NeXT, Inc.
+ * Copyright, 1990. All rights reserved.
+ */
+
+subsystem job 400;
+
+#include <mach/std_types.defs>
+#include <mach/mach_types.defs>
+#include "job_types.defs"
+import "vproc.h";
+import "vproc_priv.h";
+import "vproc_internal.h";
+
+userprefix vproc_mig_;
+serverprefix job_mig_;
+
+routine
+create_server(
+ j : job_t;
+ servercmd : cmd_t;
+ serveruid : uid_t;
+ ondemand : boolean_t;
+out serverport : mach_port_make_send_t
+);
+
+routine
+reboot2(
+ j : job_t;
+ flags : uint64_t
+);
+
+routine
+check_in2(
+ j : job_t;
+ servicename : name_t;
+out serviceport : mach_port_move_receive_t;
+out instanceid : uuid_t;
+ flags : uint64_t
+);
+
+routine
+register2(
+ j : job_t;
+ servicename : name_t;
+ serviceport : mach_port_t;
+ flags : uint64_t
+);
+
+routine
+look_up2(
+ j : job_t;
+sreplyport rp : mach_port_make_send_once_t;
+ servicename : name_t;
+out serviceport : mach_port_t;
+UserAuditToken servercreds : audit_token_t;
+ targetpid : pid_t;
+ instanceid : uuid_t;
+ flags : uint64_t
+);
+
+routine
+send_signal(
+ j : job_t;
+sreplyport rp : mach_port_make_send_once_t;
+ label : name_t;
+ sig : integer_t
+);
+
+routine
+parent(
+ j : job_t;
+sreplyport rp : mach_port_make_send_once_t;
+out parentport : mach_port_make_send_t
+);
+
+routine
+post_fork_ping(
+ j : job_t;
+ taskport : task_t;
+out asport : mach_port_t
+);
+
+routine
+info(
+ j : job_t;
+out names : name_array_t, dealloc;
+out jobs : name_array_t, dealloc;
+out actives : bootstrap_status_array_t, dealloc;
+ flags : uint64_t
+);
+
+routine
+subset(
+ j : job_t;
+ reqport : mach_port_t;
+out subsetport : mach_port_make_send_t
+);
+
+skip; /* Formerly setup_shmem. */
+
+routine
+take_subset(
+ j : job_t;
+out reqport : mach_port_move_send_t;
+out recvport : mach_port_move_receive_t;
+out jobs : pointer_t, dealloc;
+out ports : mach_port_move_send_array_t, dealloc
+);
+
+routine
+getsocket(
+ j : job_t;
+out sockpath : name_t
+);
+
+skip; /* Formerly spawn. */
+
+skip; /* Formerly wait. */
+
+skip; /* Formerly uncork_fork. */
+
+routine
+swap_integer(
+ j : job_t;
+ inkey : vproc_gsk_t;
+ outkey : vproc_gsk_t;
+ inval : int64_t;
+out outval : int64_t
+);
+
+routine
+log(
+ j : job_t;
+ pri : integer_t;
+ err : integer_t;
+ message : logmsg_t
+);
+
+routine
+lookup_per_user_context(
+ j : job_t;
+ uid : uid_t;
+out userbport : mach_port_t
+);
+
+routine
+move_subset(
+ j : job_t;
+ targetport : mach_port_t;
+ session : name_t;
+ asport : mach_port_t;
+ flags : uint64_t
+);
+
+routine
+swap_complex(
+ j : job_t;
+ inkey : vproc_gsk_t;
+ outkey : vproc_gsk_t;
+ inval : pointer_t;
+out outval : pointer_t, dealloc
+);
+
+routine
+log_drain(
+ j : job_t;
+sreplyport rp : mach_port_make_send_once_t;
+out outval : pointer_t, dealloc
+);
+
+routine
+log_forward(
+ j : job_t;
+ inval : pointer_t
+);
+
+routine
+kickstart(
+ j : job_t;
+ label : name_t;
+out pid : pid_t;
+ flags : natural_t
+);
+
+skip; /* Formerly embedded_wait. */
+
+routine
+lookup_children(
+ j : job_t;
+out childports : mach_port_move_send_array_t, dealloc;
+out childnames : name_array_t, dealloc;
+out childprops : bootstrap_property_array_t, dealloc
+);
+
+routine
+switch_to_session(
+ j : job_t;
+ reqport : mach_port_t;
+ session : name_t;
+ asport : mach_port_t;
+out newbsport : mach_port_make_send_t
+);
+
+skip; /* Formerly transaction_count_for_pid. */
+
+routine
+pid_is_managed(
+ j : job_t;
+ pid : pid_t;
+out managed : boolean_t
+);
+
+routine
+port_for_label(
+ j : job_t;
+ label : name_t;
+out jport : mach_port_make_send_t
+);
+
+routine
+init_session(
+ j : job_t;
+ session : name_t;
+ asport : mach_port_t
+);
+
+routine
+set_security_session(
+ j : job_t;
+ uuid : uuid_t;
+ asport : mach_port_t
+);
+
+skip; /* Formerly wait2. */
+
+skip; /* Formerly event_source_check_in. */
+
+skip; /* Formerly event_set_state. */
+
+routine
+spawn2(
+ j : job_t;
+sreplyport rp : mach_port_make_send_once_t;
+ job : pointer_t;
+ asport : mach_port_t;
+out outpid : pid_t;
+out obsrvport : mach_port_move_receive_t
+);
+
+routine
+get_root_bootstrap(
+ j : job_t;
+out rootbs : mach_port_move_send_t
+);
+
+routine
+legacy_ipc_request(
+ j : job_t;
+ request : pointer_t;
+ request_fds : mach_port_move_send_array_t;
+out reply : pointer_t, dealloc;
+out reply_fds : mach_port_move_send_array_t, dealloc;
+ asport : mach_port_t
+);
--- /dev/null
+/*
+ * Copyright (c) 2007 Apple 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@
+ */
+
+subsystem job_forward 400;
+
+#include <mach/std_types.defs>
+#include <mach/mach_types.defs>
+#include "job_types.defs"
+import "vproc.h";
+import "vproc_priv.h";
+import "vproc_internal.h";
+
+userprefix vproc_mig_;
+serverprefix job_mig_;
+
+skip; /* create_server */
+
+skip; /* reboot2 */
+
+skip; /* check_in */
+
+skip; /* register2 */
+
+simpleroutine
+look_up2_forward(
+ j : job_t;
+replyport rp : mach_port_move_send_once_t;
+ servicename : name_t;
+ targetpid : pid_t;
+ instanceid : uuid_t;
+ flags : uint64_t
+);
+
+skip; /* send_signal */
+
+simpleroutine
+parent_forward(
+ j : job_t;
+replyport rp : mach_port_move_send_once_t
+);
+
--- /dev/null
+/*
+ * Copyright (c) 2006 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@
+ */
+
+subsystem job_reply 500;
+
+#include <mach/std_types.defs>
+#include <mach/mach_types.defs>
+#include "job_types.defs"
+import "bootstrap.h";
+import "vproc.h";
+import "vproc_internal.h";
+
+skip; /* create_server */
+
+skip; /* reboot2 */
+
+skip; /* check_in2 */
+
+skip; /* register2 */
+
+skip; /* look_up2 */
+
+simpleroutine
+job_mig_send_signal_reply(
+ rp : mach_port_move_send_once_t;
+ kr : kern_return_t, RetCode
+);
+
+skip; /* parent */
+
+skip; /* post_fork_ping */
+
+skip; /* info */
+
+skip; /* subset */
+
+skip; /* setup_shmem */
+
+skip; /* take_subset */
+
+skip; /* getsocket */
+
+skip; /* spawn */
+
+skip; /* wait */
+
+skip; /* uncork_fork */
+
+skip; /* swap_integer */
+
+skip; /* log */
+
+skip; /* lookup_per_user_context */
+
+skip; /* move_subset */
+
+skip; /* swap_complex */
+
+simpleroutine
+job_mig_log_drain_reply(
+ rp : mach_port_move_send_once_t;
+ kr : kern_return_t, RetCode;
+ outval : pointer_t
+);
+
+skip; /* log_forward */
+
+skip; /* kickstart */
+
+skip; /* embedded_wait */
+
+skip; /* lookup_children */
+
+skip; /* switch_to_session */
+
+skip; /* transaction_count_for_pid */
+
+skip; /* pid_is_managed */
+
+skip; /* port_for_label */
+
+skip; /* init_session */
+
+skip; /* set_security_session */
+
+skip; /* wait2 */
+
+skip; /* event_source_check_in */
+
+skip; /* event_set_state */
+
+simpleroutine
+job_mig_spawn2_reply(
+ rp : mach_port_move_send_once_t;
+ kr : kern_return_t, RetCode;
+ pid : pid_t;
+ obsrvp : mach_port_move_receive_t
+);
+
+skip; /* get_root_bootstrap */
+
+skip; /* legacy_ipc_request */
--- /dev/null
+/*
+ * Copyright (c) 1999-2006 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@
+ */
+/*
+ * bootstrap -- fundamental service initiator and port server
+ * Mike DeMoney, NeXT, Inc.
+ * Copyright, 1990. All rights reserved.
+ */
+
+/* This really should be a part of the standard types... */
+type mach_port_move_send_array_t = array[] of mach_port_move_send_t
+ ctype: mach_port_array_t;
+
+type pid_t = integer_t;
+type pid_array_t = ^array [] of pid_t;
+type uid_t = natural_t;
+type gid_t = natural_t;
+type vproc_gsk_t = integer_t;
+type logmsg_t = c_string[*:2048];
+type cmd_t = c_string[512];
+type name_t = c_string[128];
+type name_array_t = ^array [] of name_t;
+type bootstrap_property_t = natural_t;
+type bootstrap_property_array_t = ^array [] of bootstrap_property_t;
+type bootstrap_status_t = integer_t;
+type bootstrap_status_array_t = ^array [] of bootstrap_status_t;
+type uuid_t = array [16] of MACH_MSG_TYPE_BYTE;
+
+type job_t = mach_port_t
+ intran : job_t job_mig_intran(mach_port_t)
+ outtran : mach_port_t job_mig_outtran(job_t)
+ destructor : job_mig_destructor(job_t)
+ cusertype : vproc_mig_t;
--- /dev/null
+/*
+ * Copyright (c) 2007 Apple 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 <sys/syscall.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include "kill2.h"
+
+int
+kill2(pid_t pid, int sig)
+{
+ /*
+ * POSIX defines consistency over correctness, and consequently
+ * kill/killpg now returns EPERM instead of ESRCH.
+ *
+ * I've filed 5487498 to get a non-portable kill().
+ * We'll regretfully take advantage of implementation details for now.
+ */
+ return syscall(SYS_kill, pid, sig, 0);
+}
+
+int
+killpg2(pid_t pgrp, int sig)
+{
+ return kill2(-pgrp, sig);
+}
--- /dev/null
+/*
+ * Copyright (c) 2007 Apple 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@
+ */
+#ifndef __LAUNCHD_KILL2_H__
+#define __LAUNCHD_KILL2_H__
+
+#include <sys/types.h>
+
+int kill2(pid_t pid, int sig);
+int killpg2(pid_t pgrp, int sig);
+
+#endif /* __LAUNCHD_KILL2_H__ */
--- /dev/null
+#include "ktrace.h"
+
+void
+runtime_ktrace1(runtime_ktrace_code_t code)
+{
+ void *ra = __builtin_extract_return_addr(__builtin_return_address(1));
+
+ /* This syscall returns EINVAL when the trace isn't enabled. */
+ if (launchd_apple_internal) {
+ syscall(180, code, 0, 0, 0, (long)ra);
+ }
+}
+
+void
+runtime_ktrace0(runtime_ktrace_code_t code)
+{
+ void *ra = __builtin_extract_return_addr(__builtin_return_address(0));
+
+ /* This syscall returns EINVAL when the trace isn't enabled. */
+ if (launchd_apple_internal) {
+ syscall(180, code, 0, 0, 0, (long)ra);
+ }
+}
+
+void
+runtime_ktrace(runtime_ktrace_code_t code, long a, long b, long c)
+{
+ void *ra = __builtin_extract_return_addr(__builtin_return_address(0));
+
+ /* This syscall returns EINVAL when the trace isn't enabled. */
+ if (launchd_apple_internal) {
+ syscall(180, code, a, b, c, (long)ra);
+ }
+}
--- /dev/null
+#ifndef __LAUNCHD_KTRACE_H__
+#define __LAUNCHD_KTRACE_H__
+
+#include <unistd.h>
+#include <stdbool.h>
+
+extern bool launchd_apple_internal;
+
+#ifndef DBG_LAUNCHD
+#define DBG_LAUNCHD 34
+#endif /* DBG_LAUNCHD */
+
+/* Class(8) | SubClass(8) | Code(14) | Qual(2) */
+#define RTKT_CODE(c) ((DBG_LAUNCHD << 24) | (((c) & 0x3fffff) << 2))
+
+typedef enum {
+ RTKT_LAUNCHD_STARTING = RTKT_CODE(1),
+ RTKT_LAUNCHD_EXITING = RTKT_CODE(2),
+ RTKT_LAUNCHD_FINDING_STRAY_PG = RTKT_CODE(3),
+ RTKT_LAUNCHD_FINDING_ALL_STRAYS = RTKT_CODE(4),
+ RTKT_LAUNCHD_FINDING_EXECLESS = RTKT_CODE(5),
+ RTKT_LAUNCHD_FINDING_WEIRD_UIDS = RTKT_CODE(6),
+ RTKT_LAUNCHD_DATA_PACK = RTKT_CODE(7),
+ RTKT_LAUNCHD_DATA_UNPACK = RTKT_CODE(8),
+ RTKT_LAUNCHD_BUG = RTKT_CODE(9),
+ RTKT_LAUNCHD_MACH_IPC = RTKT_CODE(10),
+ RTKT_LAUNCHD_BSD_KEVENT = RTKT_CODE(11),
+ RTKT_VPROC_TRANSACTION_INCREMENT = RTKT_CODE(12),
+ RTKT_VPROC_TRANSACTION_DECREMENT = RTKT_CODE(13),
+} runtime_ktrace_code_t;
+
+/* All of these log the return address as "arg4" */
+void runtime_ktrace1(runtime_ktrace_code_t code);
+void runtime_ktrace0(runtime_ktrace_code_t code);
+void runtime_ktrace(runtime_ktrace_code_t code, long a, long b, long c);
+
+#endif /* __LAUNCHD_KTRACE_H__ */
--- /dev/null
+/*
+ * Copyright (c) 2005 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 "config.h"
+#include "launchd.h"
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/event.h>
+#include <sys/stat.h>
+#include <sys/ucred.h>
+#include <sys/fcntl.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/sysctl.h>
+#include <sys/sockio.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <sys/kern_event.h>
+#include <sys/reboot.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet6/nd6.h>
+#include <ifaddrs.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <paths.h>
+#include <pwd.h>
+#include <grp.h>
+#include <ttyent.h>
+#include <dlfcn.h>
+#include <dirent.h>
+#include <string.h>
+#include <setjmp.h>
+#include <spawn.h>
+#include <sched.h>
+#include <pthread.h>
+#include <util.h>
+#include <assumes.h>
+
+#if HAVE_LIBAUDITD
+#include <bsm/auditd_lib.h>
+#include <bsm/audit_session.h>
+#endif
+
+#include "bootstrap.h"
+#include "vproc.h"
+#include "vproc_priv.h"
+#include "vproc_internal.h"
+#include "launch.h"
+#include "launch_internal.h"
+
+#include "runtime.h"
+#include "core.h"
+#include "ipc.h"
+
+#define LAUNCHD_CONF ".launchd.conf"
+
+extern char **environ;
+
+static void pfsystem_callback(void *, struct kevent *);
+
+static kq_callback kqpfsystem_callback = pfsystem_callback;
+
+static void pid1_magic_init(void);
+
+static void testfd_or_openfd(int fd, const char *path, int flags);
+static bool get_network_state(void);
+static void monitor_networking_state(void);
+static void fatal_signal_handler(int sig, siginfo_t *si, void *uap);
+static void handle_pid1_crashes_separately(void);
+static void do_pid1_crash_diagnosis_mode(const char *msg);
+static int basic_fork(void);
+static bool do_pid1_crash_diagnosis_mode2(const char *msg);
+
+static void *update_thread(void *nothing);
+
+static void *crash_addr;
+static pid_t crash_pid;
+
+char *_launchd_database_dir;
+char *_launchd_log_dir;
+
+bool launchd_shutting_down;
+bool network_up;
+uid_t launchd_uid;
+FILE *launchd_console = NULL;
+int32_t launchd_sync_frequency = 30;
+
+int
+main(int argc, char *const *argv)
+{
+ bool sflag = false;
+ int ch;
+
+ /* This needs to be cleaned up. Currently, we risk tripping assumes() macros
+ * before we've properly set things like launchd's log database paths, the
+ * global launchd label for syslog messages and the like. Luckily, these are
+ * operations that will probably never fail, like test_of_openfd(), the
+ * stuff in launchd_runtime_init() and the stuff in
+ * handle_pid1_crashes_separately().
+ */
+ testfd_or_openfd(STDIN_FILENO, _PATH_DEVNULL, O_RDONLY);
+ testfd_or_openfd(STDOUT_FILENO, _PATH_DEVNULL, O_WRONLY);
+ testfd_or_openfd(STDERR_FILENO, _PATH_DEVNULL, O_WRONLY);
+
+ if (launchd_use_gmalloc) {
+ if (!getenv("DYLD_INSERT_LIBRARIES")) {
+ setenv("DYLD_INSERT_LIBRARIES", "/usr/lib/libgmalloc.dylib", 1);
+ setenv("MALLOC_STRICT_SIZE", "1", 1);
+ execv(argv[0], argv);
+ } else {
+ unsetenv("DYLD_INSERT_LIBRARIES");
+ unsetenv("MALLOC_STRICT_SIZE");
+ }
+ } else if (launchd_malloc_log_stacks) {
+ if (!getenv("MallocStackLogging")) {
+ setenv("MallocStackLogging", "1", 1);
+ execv(argv[0], argv);
+ } else {
+ unsetenv("MallocStackLogging");
+ }
+ }
+
+ while ((ch = getopt(argc, argv, "s")) != -1) {
+ switch (ch) {
+ case 's': sflag = true; break; /* single user */
+ case '?': /* we should do something with the global optopt variable here */
+ default:
+ fprintf(stderr, "%s: ignoring unknown arguments\n", getprogname());
+ break;
+ }
+ }
+
+ if (getpid() != 1 && getppid() != 1) {
+ fprintf(stderr, "%s: This program is not meant to be run directly.\n", getprogname());
+ exit(EXIT_FAILURE);
+ }
+
+ launchd_runtime_init();
+
+ if (NULL == getenv("PATH")) {
+ setenv("PATH", _PATH_STDPATH, 1);
+ }
+
+ if (pid1_magic) {
+ pid1_magic_init();
+
+ int cfd = -1;
+ if ((cfd = open(_PATH_CONSOLE, O_WRONLY | O_NOCTTY)) != -1) {
+ _fd(cfd);
+ if (!(launchd_console = fdopen(cfd, "w"))) {
+ (void)close(cfd);
+ }
+ }
+
+ char *extra = "";
+ if (launchd_osinstaller) {
+ extra = " in the OS Installer";
+ } else if (sflag) {
+ extra = " in single-user mode";
+ }
+
+ launchd_syslog(LOG_NOTICE | LOG_CONSOLE, "*** launchd[1] has started up%s. ***", extra);
+ if (launchd_use_gmalloc) {
+ launchd_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Using libgmalloc. ***");
+ }
+
+ if (launchd_verbose_boot) {
+ launchd_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Verbose boot, will log to /dev/console. ***");
+ }
+
+ if (launchd_shutdown_debugging) {
+ launchd_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Shutdown debugging is enabled. ***");
+ }
+
+ if (launchd_log_shutdown) {
+ launchd_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Shutdown logging is enabled. ***");
+ }
+
+ if (launchd_log_perf) {
+ launchd_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Performance logging is enabled. ***");
+ }
+
+ if (launchd_log_debug) {
+ launchd_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Debug logging is enabled. ***");
+ }
+
+ handle_pid1_crashes_separately();
+
+ /* Start the update thread.
+ *
+ * <rdar://problem/5039559&6153301>
+ */
+ pthread_t t = NULL;
+ (void)osx_assumes_zero(pthread_create(&t, NULL, update_thread, NULL));
+ (void)osx_assumes_zero(pthread_detach(t));
+
+ /* PID 1 doesn't have a flat namespace. */
+ launchd_flat_mach_namespace = false;
+ fflush(launchd_console);
+ } else {
+ launchd_uid = getuid();
+ launchd_var_available = true;
+ if (asprintf(&launchd_label, "com.apple.launchd.peruser.%u", launchd_uid) == 0) {
+ launchd_label = "com.apple.launchd.peruser.unknown";
+ }
+
+ struct passwd *pwent = getpwuid(launchd_uid);
+ if (pwent) {
+ launchd_username = strdup(pwent->pw_name);
+ } else {
+ launchd_username = "(unknown)";
+ }
+
+ if (asprintf(&_launchd_database_dir, LAUNCHD_DB_PREFIX "/com.apple.launchd.peruser.%u", launchd_uid) == 0) {
+ _launchd_database_dir = "";
+ }
+
+ if (asprintf(&_launchd_log_dir, LAUNCHD_LOG_PREFIX "/com.apple.launchd.peruser.%u", launchd_uid) == 0) {
+ _launchd_log_dir = "";
+ }
+
+ if (launchd_allow_global_dyld_envvars) {
+ launchd_syslog(LOG_WARNING, "Per-user launchd will allow DYLD_* environment variables in the global environment.");
+ }
+
+ ipc_server_init();
+ launchd_log_push();
+
+ auditinfo_addr_t auinfo;
+ if (posix_assumes_zero(getaudit_addr(&auinfo, sizeof(auinfo))) != -1) {
+ launchd_audit_session = auinfo.ai_asid;
+ launchd_syslog(LOG_DEBUG, "Our audit session ID is %i", launchd_audit_session);
+ }
+
+ launchd_audit_port = _audit_session_self();
+
+ vproc_transaction_begin(NULL);
+ vproc_transaction_end(NULL, NULL);
+
+ launchd_syslog(LOG_DEBUG, "Per-user launchd started (UID/username): %u/%s.", launchd_uid, launchd_username);
+ }
+
+ monitor_networking_state();
+ jobmgr_init(sflag);
+
+ launchd_runtime_init2();
+ launchd_runtime();
+}
+
+void
+handle_pid1_crashes_separately(void)
+{
+ struct sigaction fsa;
+
+ fsa.sa_sigaction = fatal_signal_handler;
+ fsa.sa_flags = SA_SIGINFO;
+ sigemptyset(&fsa.sa_mask);
+
+ (void)posix_assumes_zero(sigaction(SIGILL, &fsa, NULL));
+ (void)posix_assumes_zero(sigaction(SIGFPE, &fsa, NULL));
+ (void)posix_assumes_zero(sigaction(SIGBUS, &fsa, NULL));
+ (void)posix_assumes_zero(sigaction(SIGTRAP, &fsa, NULL));
+ (void)posix_assumes_zero(sigaction(SIGABRT, &fsa, NULL));
+ (void)posix_assumes_zero(sigaction(SIGSEGV, &fsa, NULL));
+}
+
+void *
+update_thread(void *nothing __attribute__((unused)))
+{
+ (void)posix_assumes_zero(setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, IOPOL_THROTTLE));
+
+ while (launchd_sync_frequency) {
+ sync();
+ sleep(launchd_sync_frequency);
+ }
+
+ launchd_syslog(LOG_DEBUG, "Update thread exiting.");
+ return NULL;
+}
+
+#define PID1_CRASH_LOGFILE "/var/log/launchd-pid1.crash"
+
+/* This hack forces the dynamic linker to resolve these symbols ASAP */
+static __attribute__((unused)) typeof(sync) *__junk_dyld_trick1 = sync;
+static __attribute__((unused)) typeof(sleep) *__junk_dyld_trick2 = sleep;
+static __attribute__((unused)) typeof(reboot) *__junk_dyld_trick3 = reboot;
+
+void
+do_pid1_crash_diagnosis_mode(const char *msg)
+{
+ if (launchd_wsp) {
+ kill(launchd_wsp, SIGKILL);
+ sleep(3);
+ launchd_wsp = 0;
+ }
+
+ while (launchd_shutdown_debugging && !do_pid1_crash_diagnosis_mode2(msg)) {
+ sleep(1);
+ }
+}
+
+int
+basic_fork(void)
+{
+ int wstatus = 0;
+ pid_t p;
+
+ switch ((p = fork())) {
+ case -1:
+ launchd_syslog(LOG_ERR | LOG_CONSOLE, "Can't fork PID 1 copy for crash debugging: %m");
+ return p;
+ case 0:
+ return p;
+ default:
+ do {
+ (void)waitpid(p, &wstatus, 0);
+ } while(!WIFEXITED(wstatus));
+
+ fprintf(stdout, "PID 1 copy: exit status: %d\n", WEXITSTATUS(wstatus));
+
+ return 1;
+ }
+
+ return -1;
+}
+
+bool
+do_pid1_crash_diagnosis_mode2(const char *msg)
+{
+ if (basic_fork() == 0) {
+ /* Neuter our bootstrap port so that the shell doesn't try talking to us
+ * while we're blocked waiting on it.
+ */
+ if (launchd_console) {
+ fflush(launchd_console);
+ }
+
+ task_set_bootstrap_port(mach_task_self(), MACH_PORT_NULL);
+ if (basic_fork() != 0) {
+ if (launchd_console) {
+ fflush(launchd_console);
+ }
+
+ return true;
+ }
+ } else {
+ return true;
+ }
+
+ int fd;
+ revoke(_PATH_CONSOLE);
+ if ((fd = open(_PATH_CONSOLE, O_RDWR)) == -1) {
+ _exit(2);
+ }
+ if (login_tty(fd) == -1) {
+ _exit(3);
+ }
+
+ setenv("TERM", "vt100", 1);
+ fprintf(stdout, "\n");
+ fprintf(stdout, "Entering launchd PID 1 debugging mode...\n");
+ fprintf(stdout, "The PID 1 launchd has crashed %s.\n", msg);
+ fprintf(stdout, "It has fork(2)ed itself for debugging.\n");
+ fprintf(stdout, "To debug the crashing thread of PID 1:\n");
+ fprintf(stdout, " gdb attach %d\n", getppid());
+ fprintf(stdout, "To exit this shell and shut down:\n");
+ fprintf(stdout, " kill -9 1\n");
+ fprintf(stdout, "A sample of PID 1 has been written to %s\n", PID1_CRASH_LOGFILE);
+ fprintf(stdout, "\n");
+ fflush(stdout);
+
+ execl(_PATH_BSHELL, "-sh", NULL);
+ syslog(LOG_ERR, "can't exec %s for PID 1 crash debugging: %m", _PATH_BSHELL);
+ _exit(EXIT_FAILURE);
+}
+
+void
+fatal_signal_handler(int sig, siginfo_t *si, void *uap __attribute__((unused)))
+{
+ const char *doom_why = "at instruction";
+ char msg[128];
+ char *sample_args[] = { "/usr/bin/sample", "1", "1", "-file", PID1_CRASH_LOGFILE, NULL };
+ pid_t sample_p;
+ int wstatus;
+
+ crash_addr = si->si_addr;
+ crash_pid = si->si_pid;
+
+ setenv("XPC_SERVICES_UNAVAILABLE", "1", 0);
+ unlink(PID1_CRASH_LOGFILE);
+
+ switch ((sample_p = vfork())) {
+ case 0:
+ execve(sample_args[0], sample_args, environ);
+ _exit(EXIT_FAILURE);
+ break;
+ default:
+ waitpid(sample_p, &wstatus, 0);
+ break;
+ case -1:
+ break;
+ }
+
+ switch (sig) {
+ default:
+ case 0:
+ break;
+ case SIGBUS:
+ case SIGSEGV:
+ doom_why = "trying to read/write";
+ case SIGILL:
+ case SIGFPE:
+ snprintf(msg, sizeof(msg), "%s: %p (%s sent by PID %u)", doom_why, crash_addr, strsignal(sig), crash_pid);
+ sync();
+ do_pid1_crash_diagnosis_mode(msg);
+ sleep(3);
+ reboot(0);
+ break;
+ }
+}
+
+void
+pid1_magic_init(void)
+{
+ launchd_label = "com.apple.launchd";
+ launchd_username = "system";
+
+ _launchd_database_dir = LAUNCHD_DB_PREFIX "/com.apple.launchd";
+ _launchd_log_dir = LAUNCHD_LOG_PREFIX "/com.apple.launchd";
+
+ (void)posix_assumes_zero(setsid());
+ (void)posix_assumes_zero(chdir("/"));
+ (void)posix_assumes_zero(setlogin("root"));
+
+#if !TARGET_OS_EMBEDDED
+ auditinfo_addr_t auinfo = {
+ .ai_termid = {
+ .at_type = AU_IPv4
+ },
+ .ai_asid = AU_ASSIGN_ASID,
+ .ai_auid = AU_DEFAUDITID,
+ .ai_flags = AU_SESSION_FLAG_IS_INITIAL,
+ };
+
+ if (setaudit_addr(&auinfo, sizeof(auinfo)) == -1) {
+ launchd_syslog(LOG_WARNING | LOG_CONSOLE, "Could not set audit session: %d: %s.", errno, strerror(errno));
+ _exit(EXIT_FAILURE);
+ }
+
+ launchd_audit_session = auinfo.ai_asid;
+ launchd_syslog(LOG_DEBUG, "Audit Session ID: %i", launchd_audit_session);
+
+ launchd_audit_port = _audit_session_self();
+#endif // !TARGET_OS_EMBEDDED
+}
+
+char *
+launchd_copy_persistent_store(int type, const char *file)
+{
+ char *result = NULL;
+ if (!file) {
+ file = "";
+ }
+
+ switch (type) {
+ case LAUNCHD_PERSISTENT_STORE_DB:
+ (void)asprintf(&result, "%s/%s", _launchd_database_dir, file);
+ break;
+ case LAUNCHD_PERSISTENT_STORE_LOGS:
+ (void)asprintf(&result, "%s/%s", _launchd_log_dir, file);
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+int
+_fd(int fd)
+{
+ if (fd >= 0) {
+ (void)posix_assumes_zero(fcntl(fd, F_SETFD, 1));
+ }
+ return fd;
+}
+
+void
+launchd_shutdown(void)
+{
+ int64_t now;
+
+ if (launchd_shutting_down) {
+ return;
+ }
+
+ runtime_ktrace0(RTKT_LAUNCHD_EXITING);
+
+ launchd_shutting_down = true;
+ launchd_log_push();
+
+ now = runtime_get_wall_time();
+
+ char *term_who = pid1_magic ? "System shutdown" : "Per-user launchd termination for ";
+ launchd_syslog(LOG_INFO, "%s%s began", term_who, pid1_magic ? "" : launchd_username);
+
+ osx_assert(jobmgr_shutdown(root_jobmgr) != NULL);
+
+#if HAVE_LIBAUDITD
+ if (pid1_magic) {
+ (void)osx_assumes_zero(audit_quick_stop());
+ }
+#endif
+}
+
+void
+launchd_SessionCreate(void)
+{
+#if !TARGET_OS_EMBEDDED
+ auditinfo_addr_t auinfo = {
+ .ai_termid = { .at_type = AU_IPv4 },
+ .ai_asid = AU_ASSIGN_ASID,
+ .ai_auid = getuid(),
+ .ai_flags = 0,
+ };
+ if (setaudit_addr(&auinfo, sizeof(auinfo)) == 0) {
+ char session[16];
+ snprintf(session, sizeof(session), "%x", auinfo.ai_asid);
+ setenv("SECURITYSESSIONID", session, 1);
+ } else {
+ launchd_syslog(LOG_WARNING, "Could not set audit session: %d: %s.", errno, strerror(errno));
+ }
+#endif // !TARGET_OS_EMBEDDED
+}
+
+void
+testfd_or_openfd(int fd, const char *path, int flags)
+{
+ int tmpfd;
+
+ if (-1 != (tmpfd = dup(fd))) {
+ (void)posix_assumes_zero(runtime_close(tmpfd));
+ } else {
+ if (-1 == (tmpfd = open(path, flags | O_NOCTTY, DEFFILEMODE))) {
+ launchd_syslog(LOG_ERR, "open(\"%s\", ...): %m", path);
+ } else if (tmpfd != fd) {
+ (void)posix_assumes_zero(dup2(tmpfd, fd));
+ (void)posix_assumes_zero(runtime_close(tmpfd));
+ }
+ }
+}
+
+bool
+get_network_state(void)
+{
+ struct ifaddrs *ifa, *ifai;
+ bool up = false;
+ int r;
+
+ /* Workaround 4978696: getifaddrs() reports false ENOMEM */
+ while ((r = getifaddrs(&ifa)) == -1 && errno == ENOMEM) {
+ launchd_syslog(LOG_DEBUG, "Worked around bug: 4978696");
+ (void)posix_assumes_zero(sched_yield());
+ }
+
+ if (posix_assumes_zero(r) == -1) {
+ return network_up;
+ }
+
+ for (ifai = ifa; ifai; ifai = ifai->ifa_next) {
+ if (!(ifai->ifa_flags & IFF_UP)) {
+ continue;
+ }
+ if (ifai->ifa_flags & IFF_LOOPBACK) {
+ continue;
+ }
+ if (ifai->ifa_addr->sa_family != AF_INET && ifai->ifa_addr->sa_family != AF_INET6) {
+ continue;
+ }
+ up = true;
+ break;
+ }
+
+ freeifaddrs(ifa);
+
+ return up;
+}
+
+void
+monitor_networking_state(void)
+{
+ int pfs = _fd(socket(PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT));
+ struct kev_request kev_req;
+
+ network_up = get_network_state();
+
+ if (pfs == -1) {
+ (void)osx_assumes_zero(errno);
+ return;
+ }
+
+ memset(&kev_req, 0, sizeof(kev_req));
+ kev_req.vendor_code = KEV_VENDOR_APPLE;
+ kev_req.kev_class = KEV_NETWORK_CLASS;
+
+ if (posix_assumes_zero(ioctl(pfs, SIOCSKEVFILT, &kev_req)) == -1) {
+ runtime_close(pfs);
+ return;
+ }
+
+ (void)posix_assumes_zero(kevent_mod(pfs, EVFILT_READ, EV_ADD, 0, 0, &kqpfsystem_callback));
+}
+
+void
+pfsystem_callback(void *obj __attribute__((unused)), struct kevent *kev)
+{
+ bool new_networking_state;
+ char buf[1024];
+
+ (void)posix_assumes_zero(read((int)kev->ident, &buf, sizeof(buf)));
+
+ new_networking_state = get_network_state();
+
+ if (new_networking_state != network_up) {
+ network_up = new_networking_state;
+ jobmgr_dispatch_all_semaphores(root_jobmgr);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2005 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@
+ */
+
+#ifndef __LAUNCHD_H__
+#define __LAUNCHD_H__
+
+#include <mach/mach.h>
+#include <mach/port.h>
+#include "launch.h"
+#include "bootstrap.h"
+#include "runtime.h"
+
+struct kevent;
+struct conncb;
+
+extern bool pid1_magic;
+extern bool launchd_shutting_down;
+extern bool fake_launchd_shutting_down;
+extern bool network_up;
+extern FILE *launchd_console;
+extern uid_t launchd_uid;
+
+void launchd_SessionCreate(void);
+void launchd_shutdown(void);
+
+enum {
+ LAUNCHD_PERSISTENT_STORE_DB,
+ LAUNCHD_PERSISTENT_STORE_LOGS,
+};
+char *launchd_copy_persistent_store(int type, const char *file);
+
+int _fd(int fd);
+
+#endif /* __LAUNCHD_H__ */
--- /dev/null
+#include <dispatch/dispatch.h>
+#include <assumes.h>
+#include "job_reply.h"
+
+#include "launchd.h"
+#include "launch_internal.h"
+#include "vproc_internal.h"
+#include "log.h"
+
+#define ROUND_TO_64BIT_WORD_SIZE(x) ((x + 7) & ~7)
+#define LAUNCHD_DEBUG_LOG "launchd-debug.%s.log"
+#define LAUNCHD_PERF_LOG "launchd-perf.%s.log"
+#define LAUNCHD_SHUTDOWN_LOG "launchd-shutdown.%s.log"
+#define LAUNCHD_LOWLEVEL_LOG "launchd-lowlevel.%s.log"
+
+char *launchd_username = "unknown";
+char *launchd_label = "com.apple.launchd.unknown";
+mach_port_t launchd_drain_reply_port;
+bool launchd_var_available = false;
+int64_t launchd_system_start;
+
+static FILE *_launchd_shutdown_log;
+static FILE *_launchd_debug_log;
+static FILE *_launchd_perf_log;
+static STAILQ_HEAD(, logmsg_s) _launchd_logq = STAILQ_HEAD_INITIALIZER(_launchd_logq);
+static size_t _launchd_logq_sz;
+static size_t _launchd_logq_cnt;
+static int _launchd_log_up2 = LOG_UPTO(LOG_NOTICE);
+
+static int64_t _launchd_shutdown_start;
+
+struct _launchd_open_log_ctx_s {
+ const char *path;
+ FILE **filep;
+};
+
+static void
+_launchd_open_log_once(void *ctx)
+{
+ struct _launchd_open_log_ctx_s *ctx2 = ctx;
+ const char *path = ctx2->path;
+ FILE **filep = ctx2->filep;
+
+ char saved[PATH_MAX];
+ snprintf(saved, sizeof(saved), "%s.1", path);
+ (void)rename(path, saved);
+
+ FILE *file = fopen(path, "w");
+ if (file) {
+ (void)_fd(fileno(file));
+ *filep = file;
+ } else if (launchd_console) {
+ fprintf(launchd_console, "Could not open %s: %d: %s\n", path, errno, strerror(errno));
+ }
+}
+
+static void
+_launchd_shutdown_start_once(void *ctx __attribute__((unused)))
+{
+ _launchd_shutdown_start = runtime_get_wall_time();
+}
+
+int
+runtime_setlogmask(int maskpri)
+{
+ _launchd_log_up2 = maskpri;
+ return _launchd_log_up2;
+}
+
+static bool
+_logmsg_add(struct launchd_syslog_attr *attr, int err_num, const char *msg)
+{
+ size_t lm_sz = sizeof(struct logmsg_s) + strlen(msg) + strlen(attr->from_name) + strlen(attr->about_name) + strlen(attr->session_name) + 4;
+ char *data_off;
+ struct logmsg_s *lm;
+
+ /* Force the unpacking for the log_drain cause unalignment faults. */
+ lm_sz = ROUND_TO_64BIT_WORD_SIZE(lm_sz);
+
+ if (unlikely((lm = calloc(1, lm_sz)) == NULL)) {
+ return false;
+ }
+
+ data_off = lm->data;
+
+ lm->when = runtime_get_wall_time();
+ lm->from_pid = attr->from_pid;
+ lm->about_pid = attr->about_pid;
+ lm->err_num = err_num;
+ lm->pri = attr->priority;
+ lm->obj_sz = lm_sz;
+ lm->msg = data_off;
+ data_off += sprintf(data_off, "%s", msg) + 1;
+ lm->from_name = data_off;
+ data_off += sprintf(data_off, "%s", attr->from_name) + 1;
+ lm->about_name = data_off;
+ data_off += sprintf(data_off, "%s", attr->about_name) + 1;
+ lm->session_name = data_off;
+ data_off += sprintf(data_off, "%s", attr->session_name) + 1;
+
+ STAILQ_INSERT_TAIL(&_launchd_logq, lm, sqe);
+ _launchd_logq_sz += lm_sz;
+ _launchd_logq_cnt++;
+
+ return true;
+}
+
+static void
+_logmsg_remove(struct logmsg_s *lm)
+{
+ STAILQ_REMOVE(&_launchd_logq, lm, logmsg_s, sqe);
+ _launchd_logq_sz -= lm->obj_sz;
+ _launchd_logq_cnt--;
+
+ free(lm);
+}
+
+bool
+_launchd_osx_redirect(const char *message)
+{
+ launchd_syslog(LOG_ERR, "%s", message);
+ return true;
+}
+
+void
+launchd_syslog(int pri, const char *message, ...)
+{
+ struct launchd_syslog_attr attr = {
+ .from_name = launchd_label,
+ .about_name = launchd_label,
+ .session_name = pid1_magic ? "System" : "Background",
+ .priority = pri,
+ .from_uid = launchd_uid,
+ .from_pid = getpid(),
+ .about_pid = getpid(),
+ };
+
+ va_list ap;
+
+ va_start(ap, message);
+ launchd_vsyslog(&attr, message, ap);
+ va_end(ap);
+}
+
+void
+launchd_vsyslog(struct launchd_syslog_attr *attr, const char *fmt, va_list args)
+{
+ int saved_errno = errno;
+ char message[2048];
+
+ static dispatch_once_t perf_once = 0;
+ static dispatch_once_t shutdown_once = 0;
+ static dispatch_once_t shutdown_start_once = 0;
+ static dispatch_once_t debug_once = 0;
+
+ bool echo2console = (attr->priority & LOG_CONSOLE);
+ attr->priority &= ~LOG_CONSOLE;
+ if (attr->priority == LOG_APPLEONLY && launchd_apple_internal) {
+ attr->priority = LOG_NOTICE;
+ }
+
+ FILE *log2here = NULL;
+
+ /* launchctl is responsible for mounting /var as read-write. So we need to
+ * wait until /var/log is available before trying to log anything to our log
+ * anything to our auxilliary log files. This is kind of a hack because
+ * we'll lose the first few relevant messages at boot for debug and
+ * performance logging, but the loss isn't too bad.
+ */
+ if (launchd_var_available) {
+ /* This file is for logging low-level errors where we can't necessarily be
+ * assured that we can write to the console or use syslog.
+ */
+ char *store = launchd_copy_persistent_store(LAUNCHD_PERSISTENT_STORE_LOGS, NULL);
+ char path[PATH_MAX];
+
+ if (attr->priority == LOG_PERF) {
+ if (launchd_log_perf) {
+ (void)snprintf(path, sizeof(path), "%s" LAUNCHD_PERF_LOG, store, launchd_username);
+
+ struct _launchd_open_log_ctx_s ctx2 = {
+ .path = path,
+ .filep = &_launchd_perf_log,
+ };
+ dispatch_once_f(&perf_once, &ctx2, _launchd_open_log_once);
+ log2here = _launchd_perf_log;
+ }
+
+ // Don't log performance messages to the normal syslog store.
+ attr->priority = LOG_DEBUG + 1;
+ } else {
+ if (launchd_shutting_down && launchd_log_shutdown) {
+ dispatch_once_f(&shutdown_start_once, NULL, _launchd_shutdown_start_once);
+
+ (void)snprintf(path, sizeof(path), "%s" LAUNCHD_SHUTDOWN_LOG, store, launchd_username);
+
+ struct _launchd_open_log_ctx_s ctx2 = {
+ .path = path,
+ .filep = &_launchd_shutdown_log,
+ };
+ dispatch_once_f(&shutdown_once, &ctx2, _launchd_open_log_once);
+ log2here = _launchd_shutdown_log;
+ } else if (launchd_log_debug) {
+ (void)snprintf(path, sizeof(path), "%s" LAUNCHD_DEBUG_LOG, store, launchd_username);
+
+ struct _launchd_open_log_ctx_s ctx2 = {
+ .path = path,
+ .filep = &_launchd_debug_log,
+ };
+ dispatch_once_f(&debug_once, &ctx2, _launchd_open_log_once);
+ log2here = _launchd_debug_log;
+ }
+ }
+
+ free(store);
+
+ }
+
+ vsnprintf(message, sizeof(message), fmt, args);
+ if (echo2console && launchd_console) {
+ fprintf(launchd_console, "%-32s %-8u %-64s %-8u %s\n", attr->from_name, attr->from_pid, attr->about_name, attr->about_pid, message);
+ }
+
+ if (log2here) {
+ int64_t delta = 0;
+ if (log2here == _launchd_shutdown_log) {
+ delta = runtime_get_wall_time() - _launchd_shutdown_start;
+ } else {
+ delta = runtime_get_wall_time() - launchd_system_start;
+ }
+
+ fprintf(log2here, "%-8lld %-32s %-8u %-24s %-8u %s\n", delta, attr->from_name, attr->from_pid, attr->about_name, attr->about_pid, message);
+ }
+
+ if ((LOG_MASK(attr->priority) & _launchd_log_up2)) {
+ _logmsg_add(attr, saved_errno, message);
+ }
+}
+
+static kern_return_t
+_launchd_log_pack(vm_offset_t *outval, mach_msg_type_number_t *outvalCnt)
+{
+ struct logmsg_s *lm;
+ void *offset;
+
+ *outvalCnt = _launchd_logq_sz;
+
+ mig_allocate(outval, *outvalCnt);
+
+ if (unlikely(*outval == 0)) {
+ return 1;
+ }
+
+ offset = (void *)*outval;
+ while ((lm = STAILQ_FIRST(&_launchd_logq))) {
+ lm->from_name_offset = lm->from_name - (char *)lm;
+ lm->about_name_offset = lm->about_name - (char *)lm;
+ lm->msg_offset = lm->msg - (char *)lm;
+ lm->session_name_offset = lm->session_name - (char *)lm;
+
+ memcpy(offset, lm, lm->obj_sz);
+
+ offset += lm->obj_sz;
+
+ _logmsg_remove(lm);
+ }
+
+ return 0;
+}
+
+static void
+_launchd_log_uncork_pending_drain(void)
+{
+ mach_msg_type_number_t outvalCnt;
+ mach_port_t tmp_port;
+ vm_offset_t outval;
+
+ if (!launchd_drain_reply_port) {
+ return;
+ }
+
+ if (_launchd_logq_cnt == 0) {
+ return;
+ }
+
+ if (_launchd_log_pack(&outval, &outvalCnt) != 0) {
+ return;
+ }
+
+ tmp_port = launchd_drain_reply_port;
+ launchd_drain_reply_port = MACH_PORT_NULL;
+
+ if (unlikely(errno = job_mig_log_drain_reply(tmp_port, 0, outval, outvalCnt))) {
+ if (errno != MACH_SEND_INVALID_DEST) {
+ (void)osx_assumes_zero(errno);
+ }
+ (void)osx_assumes_zero(launchd_mport_deallocate(tmp_port));
+ }
+
+ mig_deallocate(outval, outvalCnt);
+}
+
+void
+launchd_log_push(void)
+{
+ vm_offset_t outval = 0;
+ mach_msg_type_number_t outvalCnt = 0;
+
+ if (!pid1_magic) {
+ if (_launchd_perf_log) {
+ (void)fflush(_launchd_perf_log);
+ }
+ if (_launchd_shutdown_log) {
+ (void)fflush(_launchd_shutdown_log);
+ }
+ if (_launchd_debug_log) {
+ (void)fflush(_launchd_debug_log);
+ }
+
+ if (_launchd_logq_cnt && _launchd_log_pack(&outval, &outvalCnt) == 0) {
+ (void)_vprocmgr_log_forward(inherited_bootstrap_port, (void *)outval, outvalCnt);
+ mig_deallocate(outval, outvalCnt);
+ }
+ } else {
+ _launchd_log_uncork_pending_drain();
+ }
+}
+
+kern_return_t
+launchd_log_forward(uid_t forward_uid, gid_t forward_gid, vm_offset_t inval, mach_msg_type_number_t invalCnt)
+{
+ struct logmsg_s *lm, *lm_walk;
+ mach_msg_type_number_t data_left = invalCnt;
+
+ if (inval == 0) {
+ return 0;
+ }
+
+ for (lm_walk = (struct logmsg_s *)inval; (data_left > 0) && (lm_walk->obj_sz <= data_left); lm_walk = ((void *)lm_walk + lm_walk->obj_sz)) {
+ /* malloc(3) returns non-NULL when you ask for zero bytes. If our object
+ * is zero bytes, something is wrong.
+ */
+ if (lm_walk->obj_sz == 0) {
+ launchd_syslog(LOG_WARNING, "Encountered a log message of size 0 with %u bytes left in forwarded data. Ignoring remaining messages.", data_left);
+ break;
+ }
+
+ if (!(lm = malloc(lm_walk->obj_sz))) {
+ launchd_syslog(LOG_WARNING, "Failed to allocate %llu bytes for log message with %u bytes left in forwarded data. Ignoring remaining messages.", lm_walk->obj_sz, data_left);
+ break;
+ }
+
+ memcpy(lm, lm_walk, lm_walk->obj_sz);
+ lm->sender_uid = forward_uid;
+ lm->sender_gid = forward_gid;
+
+ lm->from_name += (size_t)lm;
+ lm->about_name += (size_t)lm;
+ lm->msg += (size_t)lm;
+ lm->session_name += (size_t)lm;
+
+ STAILQ_INSERT_TAIL(&_launchd_logq, lm, sqe);
+ _launchd_logq_sz += lm->obj_sz;
+ _launchd_logq_cnt++;
+
+ data_left -= lm->obj_sz;
+ }
+
+ mig_deallocate(inval, invalCnt);
+
+ return 0;
+}
+
+kern_return_t
+launchd_log_drain(mach_port_t srp, vm_offset_t *outval, mach_msg_type_number_t *outvalCnt)
+{
+ (void)osx_assumes_zero(launchd_drain_reply_port);
+
+ if ((_launchd_logq_cnt == 0) || launchd_shutting_down) {
+ launchd_drain_reply_port = srp;
+ (void)osx_assumes_zero(launchd_mport_notify_req(launchd_drain_reply_port, MACH_NOTIFY_DEAD_NAME));
+
+ return MIG_NO_REPLY;
+ }
+
+ return _launchd_log_pack(outval, outvalCnt);
+}
+
+void
+launchd_closelog(void)
+{
+ launchd_log_push();
+
+ if (_launchd_shutdown_log) {
+ (void)fflush(_launchd_shutdown_log);
+ (void)runtime_fsync(fileno(_launchd_shutdown_log));
+ }
+}
--- /dev/null
+#ifndef __LAUNCHD_LOG_H__
+#define __LAUNCHD_LOG_H__
+
+#include <mach/mach.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/queue.h>
+#include <sys/fcntl.h>
+#include <malloc/malloc.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <syslog.h>
+
+extern char *launchd_username;
+extern char *launchd_label;
+extern mach_port_t launchd_drain_reply_port;
+extern bool launchd_var_available;
+extern int64_t launchd_system_start;
+
+struct launchd_syslog_attr {
+ const char *from_name;
+ const char *about_name;
+ const char *session_name;
+ int priority;
+ uid_t from_uid;
+ pid_t from_pid;
+ pid_t about_pid;
+};
+
+/* These constants must not have the high bit set so we can safely mask them
+ * mask them with LOG_CONSOLE.
+ */
+#define LOG_PERF 0x5252615d
+#define LOG_APPLEONLY 0x4141504c
+#define LOG_CONSOLE (1 << 31)
+
+__attribute__((visibility("default")))
+__attribute__((used))
+extern bool
+_launchd_osx_redirect(const char *message);
+
+int
+runtime_setlogmask(int maskpri);
+
+void
+launchd_closelog(void);
+
+__attribute__((format(printf, 2, 3)))
+void
+launchd_syslog(int pri, const char *message, ...);
+
+__attribute__((format(printf, 2, 0)))
+void
+launchd_vsyslog(struct launchd_syslog_attr *attr, const char *message, va_list args);
+
+void
+launchd_log_push(void);
+
+kern_return_t
+launchd_log_forward(uid_t forward_uid, gid_t forward_gid, vm_offset_t inval, mach_msg_type_number_t invalCnt);
+
+kern_return_t
+launchd_log_drain(mach_port_t srp, vm_offset_t *outval, mach_msg_type_number_t *outvalCnt);
+
+#endif /* __LAUNCHD_LOG_H__ */
--- /dev/null
+/*
+ * Copyright (c) 1999-2004 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@
+ */
+/*
+ * bootstrap -- fundamental service initiator and port server
+ * Mike DeMoney, NeXT, Inc.
+ * Copyright, 1990. All rights reserved.
+ */
+
+subsystem bootstrap 400;
+
+#include <mach/std_types.defs>
+#include <mach/mach_types.defs>
+#include "launchd_mig_types.defs"
+import "bootstrap_public.h";
+import "bootstrap_private.h";
+
+userprefix vproc_mig_;
+serverprefix job_mig_;
+
+routine create_server(
+ __bs_port : job_t;
+ __server_cmd : cmd_t;
+ __server_uid : natural_t;
+ __on_demand : boolean_t;
+ ServerAuditToken __token : audit_token_t;
+ out __server_port : mach_port_make_send_t);
+
+skip; /* Last used in 10.4. Was bootstrap_unprivileged() */
+
+routine check_in(
+ __bs_port : job_t;
+ __service_name : name_t;
+ ServerAuditToken __token : audit_token_t;
+ out __service_port : mach_port_move_receive_t);
+
+routine register(
+ __bs_port : job_t;
+ ServerAuditToken __token : audit_token_t;
+ __service_name : name_t;
+ __service_port : mach_port_t);
+
+routine look_up(
+ __bs_port : job_t;
+ ServerAuditToken __token : audit_token_t;
+ __service_name : name_t;
+ out __service_port : mach_port_send_t);
+
+skip; /* last used in 10.4 */
+
+routine parent(
+ __bs_port : job_t;
+ out __parent_port : mach_port_send_t);
+
+skip; /* last used in 10.4 */
+
+routine info(
+ __bs_port : job_t;
+ out __service_names : name_array_t, dealloc;
+ out __service_active : bootstrap_status_array_t, dealloc);
+
+routine subset(
+ __bs_port : job_t;
+ __requestor_port: mach_port_t;
+ out __subset_port : mach_port_make_send_t);
+
+routine create_service(
+ __bs_port : job_t;
+ __service_name : name_t;
+ out __service_port : mach_port_t);
+
+routine transfer_subset(
+ __bs_port : job_t;
+ out __bs_reqport : mach_port_t;
+ out __bs_rcvright : mach_port_move_receive_t;
+ out __service_names : name_array_t, dealloc;
+ out __service_pids : pointer_t, dealloc;
+ out __service_ports : mach_port_array_t, dealloc);
+
+routine getsocket(
+ __bs_port : job_t;
+ out __sockpath : name_t);
+
+routine spawn(
+ __bs_port : job_t;
+ ServerAuditToken __token : audit_token_t;
+ __chars : _internal_string_t;
+ __argc : uint32_t;
+ __envc : uint32_t;
+ __flags : uint64_t;
+ __umask : uint16_t;
+ out __pid : pid_t;
+ out __obsvr_port : mach_port_make_send_t);
+
+routine wait(
+ __bs_port : job_t;
+ sreplyport __rport : mach_port_make_send_once_t;
+ ServerAuditToken __token : audit_token_t;
+ out __waitval : integer_t);
+
+routine uncork_fork(
+ __bs_port : job_t;
+ ServerAuditToken __token : audit_token_t);
+
+/* Essentially the inverse of bootstrap_unprivileged() */
+routine get_self(
+ __bs_port : job_t;
+ ServerAuditToken __token : audit_token_t;
+ out __job_port : mach_port_make_send_t);
--- /dev/null
+/*
+ * Copyright (c) 1999-2008 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 "config.h"
+#include "runtime.h"
+
+#include <mach/mach.h>
+#include <mach/mach_error.h>
+#include <mach/boolean.h>
+#include <mach/message.h>
+#include <mach/notify.h>
+#include <mach/mig_errors.h>
+#include <mach/mach_traps.h>
+#include <mach/mach_interface.h>
+#include <mach/host_info.h>
+#include <mach/mach_host.h>
+#include <mach/mach_time.h>
+#include <mach/exception.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/proc.h>
+#include <sys/event.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/mount.h>
+#include <sys/reboot.h>
+#include <sys/fcntl.h>
+#include <sys/kdebug.h>
+#include <bsm/libbsm.h>
+#include <malloc/malloc.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <syslog.h>
+#include <signal.h>
+#include <dlfcn.h>
+#include <assumes.h>
+
+#include "internalServer.h"
+#include "internal.h"
+#include "notifyServer.h"
+#include "mach_excServer.h"
+
+/* We shouldn't be including these */
+#include "launch.h"
+#include "launchd.h"
+#include "core.h"
+#include "vproc.h"
+#include "vproc_priv.h"
+#include "vproc_internal.h"
+#include "jobServer.h"
+#include "job_reply.h"
+
+#include <xpc/launchd.h>
+
+static mach_port_t ipc_port_set;
+static mach_port_t demand_port_set;
+static mach_port_t launchd_internal_port;
+static int mainkq;
+
+#define BULK_KEV_MAX 100
+static struct kevent *bulk_kev;
+static int bulk_kev_i;
+static int bulk_kev_cnt;
+
+static pthread_t kqueue_demand_thread;
+
+static void mportset_callback(void);
+static kq_callback kqmportset_callback = (kq_callback)mportset_callback;
+static void *kqueue_demand_loop(void *arg);
+
+boolean_t launchd_internal_demux(mach_msg_header_t *Request, mach_msg_header_t *Reply);
+static void launchd_runtime2(mach_msg_size_t msg_size);
+static mach_msg_size_t max_msg_size;
+static mig_callback *mig_cb_table;
+static size_t mig_cb_table_sz;
+static timeout_callback runtime_idle_callback;
+static mach_msg_timeout_t runtime_idle_timeout;
+static struct ldcred ldc;
+static size_t runtime_standby_cnt;
+
+static void do_file_init(void) __attribute__((constructor));
+static mach_timebase_info_data_t tbi;
+static uint64_t tbi_safe_math_max;
+static uint64_t time_of_mach_msg_return;
+static double tbi_float_val;
+
+static const int sigigns[] = { SIGHUP, SIGINT, SIGPIPE, SIGALRM, SIGTERM,
+ SIGURG, SIGTSTP, SIGTSTP, SIGCONT, SIGTTIN, SIGTTOU, SIGIO, SIGXCPU,
+ SIGXFSZ, SIGVTALRM, SIGPROF, SIGWINCH, SIGINFO, SIGUSR1, SIGUSR2
+};
+static sigset_t sigign_set;
+bool pid1_magic;
+bool launchd_apple_internal;
+bool launchd_flat_mach_namespace = true;
+bool launchd_malloc_log_stacks = false;
+bool launchd_use_gmalloc = false;
+bool launchd_log_per_user_shutdown = false;
+#if !TARGET_OS_EMBEDDED
+bool launchd_log_shutdown = true;
+#else
+bool launchd_log_shutdown = false;
+#endif
+bool launchd_log_perf = false;
+bool launchd_log_debug = false;
+bool launchd_trap_sigkill_bugs = false;
+bool launchd_osinstaller = false;
+bool launchd_allow_global_dyld_envvars = false;
+pid_t launchd_wsp = 0;
+size_t runtime_busy_cnt;
+
+#if TARGET_OS_EMBEDDED
+#define LAUNCHD_CONFIG_PREFIX "/"
+#else
+#define LAUNCHD_CONFIG_PREFIX "/private/var/db/"
+#endif
+
+#define config_check(s, sb) (stat(LAUNCHD_CONFIG_PREFIX s, &sb) == 0)
+
+mach_port_t
+runtime_get_kernel_port(void)
+{
+ return launchd_internal_port;
+}
+
+union vproc_mig_max_sz {
+ union __RequestUnion__job_mig_job_subsystem req;
+ union __ReplyUnion__job_mig_job_subsystem rep;
+};
+
+union internal_max_sz {
+ union __RequestUnion__x_internal_subsystem req;
+ union __ReplyUnion__internal_subsystem rep;
+};
+
+union xpc_domain_max_sz {
+ union __RequestUnion__xpc_domain_xpc_domain_subsystem req;
+ union __ReplyUnion__xpc_domain_xpc_domain_subsystem rep;
+};
+
+union mach_exc_max_sz {
+ union __RequestUnion__catch_mach_exc_subsystem req;
+ union __ReplyUnion__catch_mach_exc_subsystem rep;
+};
+
+union do_notify_max_sz {
+ union __RequestUnion__do_notify_subsystem req;
+ union __ReplyUnion__do_notify_subsystem rep;
+};
+
+void
+launchd_runtime_init(void)
+{
+ pid_t p = getpid();
+
+ (void)posix_assert_zero((mainkq = kqueue()));
+
+ osx_assert_zero(mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_PORT_SET, &demand_port_set));
+ osx_assert_zero(mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_PORT_SET, &ipc_port_set));
+ posix_assert_zero(kevent_mod(demand_port_set, EVFILT_MACHPORT, EV_ADD, 0, 0, &kqmportset_callback));
+
+ osx_assert_zero(launchd_mport_create_recv(&launchd_internal_port));
+ osx_assert_zero(launchd_mport_make_send(launchd_internal_port));
+
+ max_msg_size = sizeof(union vproc_mig_max_sz);
+ if (sizeof(union xpc_domain_max_sz) > max_msg_size) {
+ max_msg_size = sizeof(union xpc_domain_max_sz);
+ }
+
+ osx_assert_zero(runtime_add_mport(launchd_internal_port, launchd_internal_demux));
+ osx_assert_zero(pthread_create(&kqueue_demand_thread, NULL, kqueue_demand_loop, NULL));
+ osx_assert_zero(pthread_detach(kqueue_demand_thread));
+
+ (void)posix_assumes_zero(sysctlbyname("vfs.generic.noremotehang", NULL, NULL, &p, sizeof(p)));
+}
+
+void
+launchd_runtime_init2(void)
+{
+ size_t i;
+
+ __OSX_COMPILETIME_ASSERT__(SIG_ERR == (typeof(SIG_ERR))-1);
+ for (i = 0; i < (sizeof(sigigns) / sizeof(int)); i++) {
+ sigaddset(&sigign_set, sigigns[i]);
+ (void)posix_assumes_zero(signal(sigigns[i], SIG_IGN));
+ }
+}
+
+#define FLAGIF(f) if (flags & f) { flags_off += sprintf(flags_off, #f); flags &= ~f; }
+const char *
+reboot_flags_to_C_names(unsigned int flags)
+{
+#define MAX_RB_STR "RB_ASKNAME|RB_SINGLE|RB_NOSYNC|RB_HALT|RB_INITNAME|RB_DFLTROOT|RB_ALTBOOT|RB_UNIPROC|RB_SAFEBOOT|RB_UPSDELAY|0xdeadbeeffeedface"
+ static char flags_buf[sizeof(MAX_RB_STR)];
+ char *flags_off = NULL;
+
+ if (flags == 0) {
+ return "RB_AUTOBOOT";
+ }
+
+ while (flags) {
+ if (flags_off) {
+ *flags_off = '|';
+ flags_off++;
+ *flags_off = '\0';
+ } else {
+ flags_off = flags_buf;
+ }
+
+ FLAGIF(RB_ASKNAME)
+ else FLAGIF(RB_SINGLE)
+ else FLAGIF(RB_NOSYNC)
+ else FLAGIF(RB_HALT)
+ else FLAGIF(RB_INITNAME)
+ else FLAGIF(RB_DFLTROOT)
+ else FLAGIF(RB_ALTBOOT)
+ else FLAGIF(RB_UNIPROC)
+ else FLAGIF(RB_SAFEBOOT)
+ else FLAGIF(RB_UPSDELAY)
+ else {
+ flags_off += sprintf(flags_off, "0x%x", flags);
+ flags = 0;
+ }
+ }
+
+ return flags_buf;
+}
+
+const char *
+signal_to_C_name(unsigned int sig)
+{
+ static char unknown[25];
+
+#define SIG2CASE(sg) case sg: return #sg
+
+ switch (sig) {
+ SIG2CASE(SIGHUP);
+ SIG2CASE(SIGINT);
+ SIG2CASE(SIGQUIT);
+ SIG2CASE(SIGILL);
+ SIG2CASE(SIGTRAP);
+ SIG2CASE(SIGABRT);
+ SIG2CASE(SIGFPE);
+ SIG2CASE(SIGKILL);
+ SIG2CASE(SIGBUS);
+ SIG2CASE(SIGSEGV);
+ SIG2CASE(SIGSYS);
+ SIG2CASE(SIGPIPE);
+ SIG2CASE(SIGALRM);
+ SIG2CASE(SIGTERM);
+ SIG2CASE(SIGURG);
+ SIG2CASE(SIGSTOP);
+ SIG2CASE(SIGTSTP);
+ SIG2CASE(SIGCONT);
+ SIG2CASE(SIGCHLD);
+ SIG2CASE(SIGTTIN);
+ SIG2CASE(SIGTTOU);
+ SIG2CASE(SIGIO);
+ SIG2CASE(SIGXCPU);
+ SIG2CASE(SIGXFSZ);
+ SIG2CASE(SIGVTALRM);
+ SIG2CASE(SIGPROF);
+ SIG2CASE(SIGWINCH);
+ SIG2CASE(SIGINFO);
+ SIG2CASE(SIGUSR1);
+ SIG2CASE(SIGUSR2);
+ default:
+ snprintf(unknown, sizeof(unknown), "%u", sig);
+ return unknown;
+ }
+}
+
+void
+log_kevent_struct(int level, struct kevent *kev_base, int indx)
+{
+ struct kevent *kev = &kev_base[indx];
+ const char *filter_str;
+ char ident_buf[100];
+ char filter_buf[100];
+ char fflags_buf[1000];
+ char flags_buf[1000] = "0x0";
+ char *flags_off = NULL;
+ char *fflags_off = NULL;
+ unsigned short flags = kev->flags;
+ unsigned int fflags = kev->fflags;
+
+ if (likely(!(LOG_MASK(level & ~LOG_CONSOLE) & LOG_DEBUG))) {
+ return;
+ }
+
+ if (flags) while (flags) {
+ if (flags_off) {
+ *flags_off = '|';
+ flags_off++;
+ *flags_off = '\0';
+ } else {
+ flags_off = flags_buf;
+ }
+
+ FLAGIF(EV_ADD)
+ else FLAGIF(EV_RECEIPT)
+ else FLAGIF(EV_DELETE)
+ else FLAGIF(EV_ENABLE)
+ else FLAGIF(EV_DISABLE)
+ else FLAGIF(EV_CLEAR)
+ else FLAGIF(EV_EOF)
+ else FLAGIF(EV_ONESHOT)
+ else FLAGIF(EV_ERROR)
+ else {
+ flags_off += sprintf(flags_off, "0x%hx", flags);
+ flags = 0;
+ }
+ }
+
+ snprintf(ident_buf, sizeof(ident_buf), "%ld", kev->ident);
+ snprintf(fflags_buf, sizeof(fflags_buf), "0x%x", fflags);
+
+ switch (kev->filter) {
+ case EVFILT_READ:
+ filter_str = "EVFILT_READ";
+ break;
+ case EVFILT_WRITE:
+ filter_str = "EVFILT_WRITE";
+ break;
+ case EVFILT_AIO:
+ filter_str = "EVFILT_AIO";
+ break;
+ case EVFILT_VNODE:
+ filter_str = "EVFILT_VNODE";
+ if (fflags) while (fflags) {
+ if (fflags_off) {
+ *fflags_off = '|';
+ fflags_off++;
+ *fflags_off = '\0';
+ } else {
+ fflags_off = fflags_buf;
+ }
+
+#define FFLAGIF(ff) if (fflags & ff) { fflags_off += sprintf(fflags_off, #ff); fflags &= ~ff; }
+
+ FFLAGIF(NOTE_DELETE)
+ else FFLAGIF(NOTE_WRITE)
+ else FFLAGIF(NOTE_EXTEND)
+ else FFLAGIF(NOTE_ATTRIB)
+ else FFLAGIF(NOTE_LINK)
+ else FFLAGIF(NOTE_RENAME)
+ else FFLAGIF(NOTE_REVOKE)
+ else {
+ fflags_off += sprintf(fflags_off, "0x%x", fflags);
+ fflags = 0;
+ }
+ }
+ break;
+ case EVFILT_PROC:
+ filter_str = "EVFILT_PROC";
+ if (fflags) while (fflags) {
+ if (fflags_off) {
+ *fflags_off = '|';
+ fflags_off++;
+ *fflags_off = '\0';
+ } else {
+ fflags_off = fflags_buf;
+ }
+
+ FFLAGIF(NOTE_EXIT)
+ else FFLAGIF(NOTE_REAP)
+ else FFLAGIF(NOTE_FORK)
+ else FFLAGIF(NOTE_EXEC)
+ else FFLAGIF(NOTE_SIGNAL)
+ else FFLAGIF(NOTE_TRACK)
+ else FFLAGIF(NOTE_TRACKERR)
+ else FFLAGIF(NOTE_CHILD)
+ else {
+ fflags_off += sprintf(fflags_off, "0x%x", fflags);
+ fflags = 0;
+ }
+ }
+ break;
+ case EVFILT_SIGNAL:
+ filter_str = "EVFILT_SIGNAL";
+ strcpy(ident_buf, signal_to_C_name(kev->ident));
+ break;
+ case EVFILT_TIMER:
+ filter_str = "EVFILT_TIMER";
+ snprintf(ident_buf, sizeof(ident_buf), "0x%lx", kev->ident);
+ if (fflags) while (fflags) {
+ if (fflags_off) {
+ *fflags_off = '|';
+ fflags_off++;
+ *fflags_off = '\0';
+ } else {
+ fflags_off = fflags_buf;
+ }
+
+ FFLAGIF(NOTE_SECONDS)
+ else FFLAGIF(NOTE_USECONDS)
+ else FFLAGIF(NOTE_NSECONDS)
+ else FFLAGIF(NOTE_ABSOLUTE)
+ else {
+ fflags_off += sprintf(fflags_off, "0x%x", fflags);
+ fflags = 0;
+ }
+ }
+ break;
+ case EVFILT_MACHPORT:
+ filter_str = "EVFILT_MACHPORT";
+ snprintf(ident_buf, sizeof(ident_buf), "0x%lx", kev->ident);
+ break;
+ case EVFILT_FS:
+ filter_str = "EVFILT_FS";
+ snprintf(ident_buf, sizeof(ident_buf), "0x%lx", kev->ident);
+ if (fflags) while (fflags) {
+ if (fflags_off) {
+ *fflags_off = '|';
+ fflags_off++;
+ *fflags_off = '\0';
+ } else {
+ fflags_off = fflags_buf;
+ }
+
+ FFLAGIF(VQ_NOTRESP)
+ else FFLAGIF(VQ_NEEDAUTH)
+ else FFLAGIF(VQ_LOWDISK)
+ else FFLAGIF(VQ_MOUNT)
+ else FFLAGIF(VQ_UNMOUNT)
+ else FFLAGIF(VQ_DEAD)
+ else FFLAGIF(VQ_ASSIST)
+ else FFLAGIF(VQ_NOTRESPLOCK)
+ else FFLAGIF(VQ_UPDATE)
+ else {
+ fflags_off += sprintf(fflags_off, "0x%x", fflags);
+ fflags = 0;
+ }
+ }
+ break;
+ default:
+ snprintf(filter_buf, sizeof(filter_buf), "%hd", kev->filter);
+ filter_str = filter_buf;
+ break;
+ }
+
+ launchd_syslog(level, "KEVENT[%d]: udata = %p data = 0x%lx ident = %s filter = %s flags = %s fflags = %s",
+ indx, kev->udata, kev->data, ident_buf, filter_str, flags_buf, fflags_buf);
+}
+
+void
+mportset_callback(void)
+{
+ mach_port_name_array_t members;
+ mach_msg_type_number_t membersCnt;
+ mach_port_status_t status;
+ mach_msg_type_number_t statusCnt;
+ struct kevent kev;
+ unsigned int i;
+
+ if (osx_assumes_zero(mach_port_get_set_status(mach_task_self(), demand_port_set, &members, &membersCnt)) != 0) {
+ return;
+ }
+
+ for (i = 0; i < membersCnt; i++) {
+ statusCnt = MACH_PORT_RECEIVE_STATUS_COUNT;
+ if (mach_port_get_attributes(mach_task_self(), members[i], MACH_PORT_RECEIVE_STATUS, (mach_port_info_t)&status,
+ &statusCnt) != KERN_SUCCESS) {
+ continue;
+ }
+ if (status.mps_msgcount) {
+ EV_SET(&kev, members[i], EVFILT_MACHPORT, 0, 0, 0, job_find_by_service_port(members[i]));
+#if 0
+ if (kev.udata != NULL) {
+#endif
+ log_kevent_struct(LOG_DEBUG, &kev, 0);
+ (*((kq_callback *)kev.udata))(kev.udata, &kev);
+#if 0
+ } else {
+ log_kevent_struct(LOG_ERR, &kev, 0);
+ }
+#endif
+ /* the callback may have tainted our ability to continue this for loop */
+ break;
+ }
+ }
+
+ (void)osx_assumes_zero(vm_deallocate(mach_task_self(), (vm_address_t)members, (vm_size_t) membersCnt * sizeof(mach_port_name_t)));
+}
+
+void *
+kqueue_demand_loop(void *arg __attribute__((unused)))
+{
+ fd_set rfds;
+
+ /*
+ * Yes, at first glance, calling select() on a kqueue seems silly.
+ *
+ * This avoids a race condition between the main thread and this helper
+ * thread by ensuring that we drain kqueue events on the same thread
+ * that manipulates the kqueue.
+ */
+
+ for (;;) {
+ FD_ZERO(&rfds);
+ FD_SET(mainkq, &rfds);
+ int r = select(mainkq + 1, &rfds, NULL, NULL, NULL);
+ if (r == 1) {
+ (void)osx_assumes_zero(handle_kqueue(launchd_internal_port, mainkq));
+ } else if (posix_assumes_zero(r) != -1) {
+ (void)osx_assumes_zero(r);
+ }
+ }
+
+ return NULL;
+}
+
+kern_return_t
+x_handle_kqueue(mach_port_t junk __attribute__((unused)), integer_t fd)
+{
+ struct timespec ts = { 0, 0 };
+ struct kevent *kevi, kev[BULK_KEV_MAX];
+ int i;
+
+ bulk_kev = kev;
+
+ if ((bulk_kev_cnt = kevent(fd, NULL, 0, kev, BULK_KEV_MAX, &ts)) != -1) {
+#if 0
+ for (i = 0; i < bulk_kev_cnt; i++) {
+ log_kevent_struct(LOG_DEBUG, &kev[0], i);
+ }
+#endif
+ for (i = 0; i < bulk_kev_cnt; i++) {
+ bulk_kev_i = i;
+ kevi = &kev[i];
+
+ if (kevi->filter) {
+ launchd_syslog(LOG_DEBUG, "Dispatching kevent (ident/filter): %lu/%hd", kevi->ident, kevi->filter);
+ log_kevent_struct(LOG_DEBUG, kev, i);
+
+ struct job_check_s {
+ kq_callback kqc;
+ };
+
+ struct job_check_s *check = kevi->udata;
+ if (check && check->kqc) {
+ runtime_ktrace(RTKT_LAUNCHD_BSD_KEVENT|DBG_FUNC_START, kevi->ident, kevi->filter, kevi->fflags);
+ (*((kq_callback *)kevi->udata))(kevi->udata, kevi);
+ runtime_ktrace0(RTKT_LAUNCHD_BSD_KEVENT|DBG_FUNC_END);
+ } else {
+ launchd_syslog(LOG_ERR, "The following kevent had invalid context data. Please file a bug with the following information:");
+ log_kevent_struct(LOG_EMERG, &kev[0], i);
+ }
+ launchd_syslog(LOG_DEBUG, "Handled kevent.");
+ }
+ }
+ } else {
+ (void)osx_assumes_zero(errno);
+ }
+
+ bulk_kev = NULL;
+
+ return 0;
+}
+
+void
+launchd_runtime(void)
+{
+ launchd_runtime2(max_msg_size);
+ dispatch_main();
+}
+
+kern_return_t
+launchd_set_bport(mach_port_t name)
+{
+ return errno = task_set_bootstrap_port(mach_task_self(), name);
+}
+
+kern_return_t
+launchd_get_bport(mach_port_t *name)
+{
+ return errno = task_get_bootstrap_port(mach_task_self(), name);
+}
+
+kern_return_t
+launchd_mport_notify_req(mach_port_t name, mach_msg_id_t which)
+{
+ mach_port_mscount_t msgc = (which == MACH_NOTIFY_PORT_DESTROYED) ? 0 : 1;
+ mach_port_t previous, where = (which == MACH_NOTIFY_NO_SENDERS) ? name : launchd_internal_port;
+
+ if (which == MACH_NOTIFY_NO_SENDERS) {
+ /* Always make sure the send count is zero, in case a receive right is
+ * reused
+ */
+ errno = mach_port_set_mscount(mach_task_self(), name, 0);
+ if (unlikely(errno != KERN_SUCCESS)) {
+ return errno;
+ }
+ }
+
+ errno = mach_port_request_notification(mach_task_self(), name, which, msgc, where, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous);
+
+ if (likely(errno == 0) && previous != MACH_PORT_NULL) {
+ (void)osx_assumes_zero(launchd_mport_deallocate(previous));
+ }
+
+ return errno;
+}
+
+pid_t
+runtime_fork(mach_port_t bsport)
+{
+ sigset_t emptyset, oset;
+ pid_t r = -1;
+ int saved_errno;
+ size_t i;
+
+ sigemptyset(&emptyset);
+
+ (void)osx_assumes_zero(launchd_mport_make_send(bsport));
+ (void)osx_assumes_zero(launchd_set_bport(bsport));
+ (void)osx_assumes_zero(launchd_mport_deallocate(bsport));
+
+ __OSX_COMPILETIME_ASSERT__(SIG_ERR == (typeof(SIG_ERR))-1);
+ (void)posix_assumes_zero(sigprocmask(SIG_BLOCK, &sigign_set, &oset));
+ for (i = 0; i < (sizeof(sigigns) / sizeof(int)); i++) {
+ (void)posix_assumes_zero(signal(sigigns[i], SIG_DFL));
+ }
+
+ r = fork();
+ saved_errno = errno;
+
+ if (r != 0) {
+ for (i = 0; i < (sizeof(sigigns) / sizeof(int)); i++) {
+ (void)posix_assumes_zero(signal(sigigns[i], SIG_IGN));
+ }
+ (void)posix_assumes_zero(sigprocmask(SIG_SETMASK, &oset, NULL));
+ (void)osx_assumes_zero(launchd_set_bport(MACH_PORT_NULL));
+ } else {
+ pid_t p = -getpid();
+ (void)posix_assumes_zero(sysctlbyname("vfs.generic.noremotehang", NULL, NULL, &p, sizeof(p)));
+ (void)posix_assumes_zero(sigprocmask(SIG_SETMASK, &emptyset, NULL));
+ }
+
+ errno = saved_errno;
+
+ return r;
+}
+
+
+void
+runtime_set_timeout(timeout_callback to_cb, unsigned int sec)
+{
+ if (sec == 0 || to_cb == NULL) {
+ runtime_idle_callback = NULL;
+ runtime_idle_timeout = 0;
+ }
+
+ runtime_idle_callback = to_cb;
+ runtime_idle_timeout = sec * 1000;
+}
+
+kern_return_t
+runtime_add_mport(mach_port_t name, mig_callback demux)
+{
+ size_t needed_table_sz = (MACH_PORT_INDEX(name) + 1) * sizeof(mig_callback);
+ mach_port_t target_set = demux ? ipc_port_set : demand_port_set;
+
+ if (unlikely(needed_table_sz > mig_cb_table_sz)) {
+ needed_table_sz *= 2; /* Let's try and avoid realloc'ing for a while */
+ mig_callback *new_table = malloc(needed_table_sz);
+
+ if (!new_table) {
+ return KERN_RESOURCE_SHORTAGE;
+ }
+
+ if (likely(mig_cb_table)) {
+ memcpy(new_table, mig_cb_table, mig_cb_table_sz);
+ free(mig_cb_table);
+ }
+
+ mig_cb_table_sz = needed_table_sz;
+ mig_cb_table = new_table;
+ }
+
+ mig_cb_table[MACH_PORT_INDEX(name)] = demux;
+
+ return errno = mach_port_move_member(mach_task_self(), name, target_set);
+}
+
+kern_return_t
+runtime_remove_mport(mach_port_t name)
+{
+ mig_cb_table[MACH_PORT_INDEX(name)] = NULL;
+
+ return errno = mach_port_move_member(mach_task_self(), name, MACH_PORT_NULL);
+}
+
+kern_return_t
+launchd_mport_make_send(mach_port_t name)
+{
+ return errno = mach_port_insert_right(mach_task_self(), name, name, MACH_MSG_TYPE_MAKE_SEND);
+}
+
+kern_return_t
+launchd_mport_copy_send(mach_port_t name)
+{
+ return errno = mach_port_insert_right(mach_task_self(), name, name, MACH_MSG_TYPE_COPY_SEND);
+}
+
+kern_return_t
+launchd_mport_make_send_once(mach_port_t name, mach_port_t *so)
+{
+ mach_msg_type_name_t right = 0;
+ return errno = mach_port_extract_right(mach_task_self(), name, MACH_MSG_TYPE_MAKE_SEND_ONCE, so, &right);
+}
+
+kern_return_t
+launchd_mport_close_recv(mach_port_t name)
+{
+ return errno = mach_port_mod_refs(mach_task_self(), name, MACH_PORT_RIGHT_RECEIVE, -1);
+}
+
+kern_return_t
+launchd_mport_create_recv(mach_port_t *name)
+{
+ return errno = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, name);
+}
+
+kern_return_t
+launchd_mport_deallocate(mach_port_t name)
+{
+ return errno = mach_port_deallocate(mach_task_self(), name);
+}
+
+int
+kevent_bulk_mod(struct kevent *kev, size_t kev_cnt)
+{
+ size_t i;
+
+ for (i = 0; i < kev_cnt; i++) {
+ kev[i].flags |= EV_CLEAR|EV_RECEIPT;
+ }
+
+ return kevent(mainkq, kev, kev_cnt, kev, kev_cnt, NULL);
+}
+
+int
+kevent_mod(uintptr_t ident, short filter, u_short flags, u_int fflags, intptr_t data, void *udata)
+{
+ struct kevent kev;
+ int r;
+
+ switch (filter) {
+ case EVFILT_READ:
+ case EVFILT_WRITE:
+ break;
+ case EVFILT_TIMER:
+ /* Workaround 5225889 */
+ if (flags & EV_ADD) {
+ (void)kevent_mod(ident, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
+ }
+ /* fall through */
+ default:
+ flags |= EV_CLEAR;
+ break;
+ }
+
+ flags |= EV_RECEIPT;
+
+ if (flags & EV_ADD && !udata) {
+ errno = EINVAL;
+ return -1;
+ } else if ((flags & EV_DELETE) && bulk_kev) {
+ int i = 0;
+ for (i = bulk_kev_i + 1; i < bulk_kev_cnt; i++) {
+ if (bulk_kev[i].filter == filter && bulk_kev[i].ident == ident) {
+ launchd_syslog(LOG_DEBUG, "Pruning the following kevent:");
+ log_kevent_struct(LOG_DEBUG, &bulk_kev[0], i);
+ bulk_kev[i].filter = (short)0;
+ }
+ }
+ }
+
+ EV_SET(&kev, ident, filter, flags, fflags, data, udata);
+
+ r = kevent(mainkq, &kev, 1, &kev, 1, NULL);
+
+ if (r != 1) {
+ return -1;
+ }
+
+ if (kev.flags & EV_ERROR) {
+ if ((flags & EV_ADD) && kev.data) {
+ launchd_syslog(LOG_DEBUG, "%s(): See the next line...", __func__);
+ log_kevent_struct(LOG_DEBUG, &kev, 0);
+ errno = kev.data;
+ return -1;
+ }
+ } else {
+ (void)osx_assert_zero(kev.flags);
+ }
+
+ return r;
+}
+
+boolean_t
+launchd_internal_demux(mach_msg_header_t *Request, mach_msg_header_t *Reply)
+{
+ if (internal_server_routine(Request)) {
+ return internal_server(Request, Reply);
+ } else if (notify_server_routine(Request)) {
+ return notify_server(Request, Reply);
+ } else {
+ return mach_exc_server(Request, Reply);
+ }
+}
+
+kern_return_t
+do_mach_notify_port_destroyed(mach_port_t notify __attribute__((unused)), mach_port_t rights)
+{
+ /* This message is sent to us when a receive right is returned to us. */
+ if (!job_ack_port_destruction(rights)) {
+ (void)osx_assumes_zero(launchd_mport_close_recv(rights));
+ }
+
+ return KERN_SUCCESS;
+}
+
+kern_return_t
+do_mach_notify_port_deleted(mach_port_t notify __attribute__((unused)), mach_port_name_t name __attribute__((unused)))
+{
+ /* If we deallocate/destroy/mod_ref away a port with a pending
+ * notification, the original notification message is replaced with
+ * this message. To quote a Mach kernel expert, "the kernel has a
+ * send-once right that has to be used somehow."
+ */
+ return KERN_SUCCESS;
+}
+
+kern_return_t
+do_mach_notify_no_senders(mach_port_t notify, mach_port_mscount_t mscount __attribute__((unused)))
+{
+ job_t j = job_mig_intran(notify);
+
+ /* This message is sent to us when the last customer of one of our objects
+ * goes away.
+ */
+
+ if (!j) {
+ return KERN_FAILURE;
+ }
+
+ job_ack_no_senders(j);
+
+ return KERN_SUCCESS;
+}
+
+kern_return_t
+do_mach_notify_send_once(mach_port_t notify __attribute__((unused)))
+{
+ /*
+ * This message is sent for each send-once right that is deallocated without
+ * being used.
+ */
+
+ return KERN_SUCCESS;
+}
+
+kern_return_t
+do_mach_notify_dead_name(mach_port_t notify __attribute__((unused)), mach_port_name_t name)
+{
+ /* This message is sent to us when one of our send rights no longer has a
+ * receiver somewhere else on the system.
+ */
+ if (name == launchd_drain_reply_port) {
+ (void)osx_assumes_zero(launchd_mport_deallocate(name));
+ launchd_drain_reply_port = MACH_PORT_NULL;
+ }
+
+ if (root_jobmgr) {
+ root_jobmgr = jobmgr_delete_anything_with_port(root_jobmgr, name);
+ }
+
+ /* A dead-name notification about a port appears to increment the rights on
+ * said port. Let's deallocate it so that we don't leak dead-name ports.
+ */
+ (void)osx_assumes_zero(launchd_mport_deallocate(name));
+
+ return KERN_SUCCESS;
+}
+
+mach_msg_return_t
+launchd_exc_runtime_once(mach_port_t port, mach_msg_size_t rcv_msg_size, mach_msg_size_t send_msg_size, mig_reply_error_t *bufRequest, mig_reply_error_t *bufReply, mach_msg_timeout_t to)
+{
+ mach_msg_return_t mr = ~MACH_MSG_SUCCESS;
+ mach_msg_option_t rcv_options = MACH_RCV_MSG
+ | MACH_RCV_TIMEOUT
+ | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT)
+ | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0);
+
+ do {
+ mr = mach_msg(&bufRequest->Head, rcv_options, 0, rcv_msg_size, port, to, MACH_PORT_NULL);
+ switch (mr) {
+ case MACH_RCV_TIMED_OUT:
+ launchd_syslog(LOG_DEBUG, "Message queue is empty.");
+ break;
+ case MACH_RCV_TOO_LARGE:
+ launchd_syslog(LOG_INFO, "Message is larger than %u bytes.", rcv_msg_size);
+ break;
+ default:
+ (void)osx_assumes_zero(mr);
+ }
+
+ if (mr == MACH_MSG_SUCCESS) {
+ if (!mach_exc_server(&bufRequest->Head, &bufReply->Head)) {
+ launchd_syslog(LOG_WARNING, "Exception server routine failed.");
+ break;
+ }
+
+ mach_msg_return_t smr = ~MACH_MSG_SUCCESS;
+ mach_msg_option_t send_options = MACH_SEND_MSG | MACH_SEND_TIMEOUT;
+
+ (void)osx_assumes(bufReply->Head.msgh_size <= send_msg_size);
+ smr = mach_msg(&bufReply->Head, send_options, bufReply->Head.msgh_size, 0, MACH_PORT_NULL, to + 100, MACH_PORT_NULL);
+ switch (smr) {
+ case MACH_SEND_TIMED_OUT:
+ launchd_syslog(LOG_WARNING, "Timed out while trying to send reply to exception message.");
+ break;
+ case MACH_SEND_INVALID_DEST:
+ launchd_syslog(LOG_WARNING, "Tried sending a message to a port that we don't possess a send right to.");
+ break;
+ default:
+ if (smr) {
+ launchd_syslog(LOG_WARNING, "Couldn't deliver exception reply: 0x%x", smr);
+ }
+ break;
+ }
+ }
+ } while (0);
+
+ return mr;
+}
+
+void
+runtime_record_caller_creds(audit_token_t *token)
+{
+ audit_token_to_au32(*token, NULL, &ldc.euid,&ldc.egid, &ldc.uid, &ldc.gid,
+ &ldc.pid, &ldc.asid, NULL);
+}
+
+struct ldcred *
+runtime_get_caller_creds(void)
+{
+ return &ldc;
+}
+
+static boolean_t
+launchd_mig_demux(mach_msg_header_t *request, mach_msg_header_t *reply)
+{
+ boolean_t result = false;
+
+ time_of_mach_msg_return = runtime_get_opaque_time();
+ launchd_syslog(LOG_DEBUG, "MIG callout: %u", request->msgh_id);
+ mig_callback the_demux = mig_cb_table[MACH_PORT_INDEX(request->msgh_local_port)];
+ mach_msg_audit_trailer_t *tp = (mach_msg_audit_trailer_t *)((vm_offset_t)request + round_msg(request->msgh_size));
+ runtime_record_caller_creds(&tp->msgh_audit);
+
+ result = the_demux(request, reply);
+ if (!result) {
+ launchd_syslog(LOG_DEBUG, "Demux failed. Trying other subsystems...");
+ if (request->msgh_id == MACH_NOTIFY_NO_SENDERS) {
+ launchd_syslog(LOG_DEBUG, "MACH_NOTIFY_NO_SENDERS");
+ result = notify_server(request, reply);
+ } else if (the_demux == job_server) {
+ launchd_syslog(LOG_DEBUG, "Trying domain subsystem...");
+ result = xpc_domain_server(request, reply);
+ } else {
+ launchd_syslog(LOG_ERR, "Cannot handle MIG request with ID: 0x%x", request->msgh_id);
+ }
+ } else {
+ launchd_syslog(LOG_DEBUG, "MIG demux succeeded.");
+ }
+
+ return result;
+}
+
+void
+launchd_runtime2(mach_msg_size_t msg_size)
+{
+ for (;;) {
+ launchd_log_push();
+
+ mach_port_t recvp = MACH_PORT_NULL;
+ xpc_object_t request = NULL;
+ int result = xpc_pipe_try_receive(ipc_port_set, &request, &recvp, launchd_mig_demux, msg_size, 0);
+ if (result == 0 && request) {
+ time_of_mach_msg_return = runtime_get_opaque_time();
+ launchd_syslog(LOG_DEBUG, "XPC request.");
+
+ xpc_object_t reply = NULL;
+ if (!xpc_event_demux(recvp, request, &reply)) {
+ launchd_syslog(LOG_DEBUG, "XPC routine could not be handled.");
+ xpc_release(request);
+ continue;
+ }
+
+ launchd_syslog(LOG_DEBUG, "XPC routine was handled.");
+ if (reply) {
+ launchd_syslog(LOG_DEBUG, "Sending reply.");
+ result = xpc_pipe_routine_reply(reply);
+ if (result == 0) {
+ launchd_syslog(LOG_DEBUG, "Reply sent successfully.");
+ } else if (result != EPIPE) {
+ launchd_syslog(LOG_ERR, "Failed to send reply message: 0x%x", result);
+ }
+
+ xpc_release(reply);
+ }
+
+ xpc_release(request);
+ } else if (result == 0) {
+ launchd_syslog(LOG_DEBUG, "MIG request.");
+ } else if (result == EINVAL) {
+ launchd_syslog(LOG_ERR, "Rejected invalid request message.");
+ }
+ }
+}
+
+int
+runtime_close(int fd)
+{
+ int i;
+
+ if (bulk_kev) for (i = bulk_kev_i + 1; i < bulk_kev_cnt; i++) {
+ switch (bulk_kev[i].filter) {
+ case EVFILT_VNODE:
+ case EVFILT_WRITE:
+ case EVFILT_READ:
+ if (unlikely((int)bulk_kev[i].ident == fd)) {
+ launchd_syslog(LOG_DEBUG, "Skipping kevent index: %d", i);
+ bulk_kev[i].filter = 0;
+ }
+ default:
+ break;
+ }
+ }
+
+ return close(fd);
+}
+
+int
+runtime_fsync(int fd)
+{
+#if 0
+ if (launchd_apple_internal) {
+ return fcntl(fd, F_FULLFSYNC, NULL);
+ } else {
+ return fsync(fd);
+ }
+#else
+ return fsync(fd);
+#endif
+}
+
+/*
+ * We should break this into two reference counts.
+ *
+ * One for hard references that would prevent exiting.
+ * One for soft references that would only prevent idle exiting.
+ *
+ * In the long run, reference counting should completely automate when a
+ * process can and should exit.
+ */
+void
+runtime_add_ref(void)
+{
+ if (!pid1_magic) {
+#if !TARGET_OS_EMBEDDED
+ vproc_transaction_begin(NULL);
+#endif
+ }
+
+ runtime_busy_cnt++;
+ launchd_syslog(LOG_PERF, "Incremented busy count. Now: %lu", runtime_busy_cnt);
+ runtime_remove_timer();
+}
+
+void
+runtime_del_ref(void)
+{
+ if (!pid1_magic) {
+#if !TARGET_OS_EMBEDDED
+ if (_vproc_transaction_count() == 0) {
+ launchd_syslog(LOG_PERF, "Exiting cleanly.");
+ }
+
+ vproc_transaction_end(NULL, NULL);
+#endif
+ }
+
+ runtime_busy_cnt--;
+ launchd_syslog(LOG_PERF, "Decremented busy count. Now: %lu", runtime_busy_cnt);
+ runtime_install_timer();
+}
+
+void
+runtime_add_weak_ref(void)
+{
+ if (!pid1_magic) {
+#if !TARGET_OS_EMBEDDED
+ _vproc_standby_begin();
+#endif
+ }
+ runtime_standby_cnt++;
+}
+
+void
+runtime_del_weak_ref(void)
+{
+ if (!pid1_magic) {
+#if !TARGET_OS_EMBEDDED
+ _vproc_standby_end();
+#endif
+ }
+ runtime_standby_cnt--;
+}
+
+void
+runtime_install_timer(void)
+{
+ if (!pid1_magic && runtime_busy_cnt == 0) {
+ launchd_syslog(LOG_PERF, "Gone idle. Installing idle-exit timer.");
+ (void)posix_assumes_zero(kevent_mod((uintptr_t)&launchd_runtime_busy_time, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, 10, root_jobmgr));
+ }
+}
+
+void
+runtime_remove_timer(void)
+{
+ if (!pid1_magic && runtime_busy_cnt > 0) {
+ if (runtime_busy_cnt == 1) {
+ launchd_syslog(LOG_PERF, "No longer idle. Removing idle-exit timer.");
+ }
+ (void)posix_assumes_zero(kevent_mod((uintptr_t)&launchd_runtime_busy_time, EVFILT_TIMER, EV_DELETE, 0, 0, NULL));
+ }
+}
+
+kern_return_t
+catch_mach_exception_raise(mach_port_t exception_port __attribute__((unused)), mach_port_t thread, mach_port_t task,
+ exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt)
+{
+ pid_t p4t = -1;
+
+ (void)osx_assumes_zero(pid_for_task(task, &p4t));
+
+ launchd_syslog(LOG_NOTICE, "%s(): PID: %u thread: 0x%x type: 0x%x code: %p codeCnt: 0x%x",
+ __func__, p4t, thread, exception, code, codeCnt);
+
+ (void)osx_assumes_zero(launchd_mport_deallocate(thread));
+ (void)osx_assumes_zero(launchd_mport_deallocate(task));
+
+ return KERN_SUCCESS;
+}
+
+kern_return_t
+catch_mach_exception_raise_state(mach_port_t exception_port __attribute__((unused)),
+ exception_type_t exception, const mach_exception_data_t code, mach_msg_type_number_t codeCnt,
+ int *flavor, const thread_state_t old_state, mach_msg_type_number_t old_stateCnt,
+ thread_state_t new_state, mach_msg_type_number_t *new_stateCnt)
+{
+ launchd_syslog(LOG_NOTICE, "%s(): type: 0x%x code: %p codeCnt: 0x%x flavor: %p old_state: %p old_stateCnt: 0x%x new_state: %p new_stateCnt: %p",
+ __func__, exception, code, codeCnt, flavor, old_state, old_stateCnt, new_state, new_stateCnt);
+
+ memcpy(new_state, old_state, old_stateCnt * sizeof(old_state[0]));
+ *new_stateCnt = old_stateCnt;
+
+ return KERN_SUCCESS;
+}
+
+kern_return_t
+catch_mach_exception_raise_state_identity(mach_port_t exception_port __attribute__((unused)), mach_port_t thread, mach_port_t task,
+ exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt,
+ int *flavor, thread_state_t old_state, mach_msg_type_number_t old_stateCnt,
+ thread_state_t new_state, mach_msg_type_number_t *new_stateCnt)
+{
+ pid_t p4t = -1;
+
+ (void)osx_assumes_zero(pid_for_task(task, &p4t));
+
+ launchd_syslog(LOG_NOTICE, "%s(): PID: %u thread: 0x%x type: 0x%x code: %p codeCnt: 0x%x flavor: %p old_state: %p old_stateCnt: 0x%x new_state: %p new_stateCnt: %p",
+ __func__, p4t, thread, exception, code, codeCnt, flavor, old_state, old_stateCnt, new_state, new_stateCnt);
+
+ memcpy(new_state, old_state, old_stateCnt * sizeof(old_state[0]));
+ *new_stateCnt = old_stateCnt;
+
+ (void)osx_assumes_zero(launchd_mport_deallocate(thread));
+ (void)osx_assumes_zero(launchd_mport_deallocate(task));
+
+ return KERN_SUCCESS;
+}
+
+void
+launchd_log_vm_stats(void)
+{
+ static struct vm_statistics orig_stats;
+ static bool did_first_pass;
+ unsigned int count = HOST_VM_INFO_COUNT;
+ struct vm_statistics stats, *statsp;
+ mach_port_t mhs = mach_host_self();
+
+ statsp = did_first_pass ? &stats : &orig_stats;
+
+ if (osx_assumes_zero(host_statistics(mhs, HOST_VM_INFO, (host_info_t)statsp, &count)) != KERN_SUCCESS) {
+ return;
+ }
+
+ if (count != HOST_VM_INFO_COUNT) {
+ (void)osx_assumes_zero(count);
+ }
+
+ if (did_first_pass) {
+ launchd_syslog(LOG_DEBUG, "VM statistics (now - orig): Free: %d Active: %d Inactive: %d Reactivations: %d PageIns: %d PageOuts: %d Faults: %d COW-Faults: %d Purgeable: %d Purges: %d",
+ stats.free_count - orig_stats.free_count,
+ stats.active_count - orig_stats.active_count,
+ stats.inactive_count - orig_stats.inactive_count,
+ stats.reactivations - orig_stats.reactivations,
+ stats.pageins - orig_stats.pageins,
+ stats.pageouts - orig_stats.pageouts,
+ stats.faults - orig_stats.faults,
+ stats.cow_faults - orig_stats.cow_faults,
+ stats.purgeable_count - orig_stats.purgeable_count,
+ stats.purges - orig_stats.purges);
+ } else {
+ launchd_syslog(LOG_DEBUG, "VM statistics (now): Free: %d Active: %d Inactive: %d Reactivations: %d PageIns: %d PageOuts: %d Faults: %d COW-Faults: %d Purgeable: %d Purges: %d",
+ orig_stats.free_count,
+ orig_stats.active_count,
+ orig_stats.inactive_count,
+ orig_stats.reactivations,
+ orig_stats.pageins,
+ orig_stats.pageouts,
+ orig_stats.faults,
+ orig_stats.cow_faults,
+ orig_stats.purgeable_count,
+ orig_stats.purges);
+
+ did_first_pass = true;
+ }
+
+ launchd_mport_deallocate(mhs);
+}
+
+int64_t
+runtime_get_wall_time(void)
+{
+ struct timeval tv;
+ int64_t r;
+
+ (void)posix_assumes_zero(gettimeofday(&tv, NULL));
+
+ r = tv.tv_sec;
+ r *= USEC_PER_SEC;
+ r += tv.tv_usec;
+
+ return r;
+}
+
+uint64_t
+runtime_get_opaque_time(void)
+{
+ return mach_absolute_time();
+}
+
+uint64_t
+runtime_get_opaque_time_of_event(void)
+{
+ return time_of_mach_msg_return;
+}
+
+uint64_t
+runtime_get_nanoseconds_since(uint64_t o)
+{
+ return runtime_opaque_time_to_nano(runtime_get_opaque_time_of_event() - o);
+}
+
+uint64_t
+runtime_opaque_time_to_nano(uint64_t o)
+{
+#if defined(__i386__) || defined(__x86_64__)
+ if (unlikely(tbi.numer != tbi.denom)) {
+#elif defined(__ppc__) || defined(__ppc64__)
+ if (likely(tbi.numer != tbi.denom)) {
+#else
+ if (tbi.numer != tbi.denom) {
+#endif
+#ifdef __LP64__
+ __uint128_t tmp = o;
+ tmp *= tbi.numer;
+ tmp /= tbi.denom;
+ o = tmp;
+#else
+ if (o <= tbi_safe_math_max) {
+ o *= tbi.numer;
+ o /= tbi.denom;
+ } else {
+ double d = o;
+ d *= tbi_float_val;
+ o = d;
+ }
+#endif
+ }
+
+ return o;
+}
+
+void
+do_file_init(void)
+{
+ struct stat sb;
+
+ osx_assert_zero(mach_timebase_info(&tbi));
+ tbi_float_val = tbi.numer;
+ tbi_float_val /= tbi.denom;
+ tbi_safe_math_max = UINT64_MAX / tbi.numer;
+
+ launchd_system_start = runtime_get_wall_time();
+
+ if (getpid() == 1) {
+ pid1_magic = true;
+ }
+
+ if (stat("/AppleInternal", &sb) == 0 && stat("/var/db/disableAppleInternal", &sb) == -1) {
+ launchd_apple_internal = true;
+ }
+
+ if (config_check(".launchd_use_gmalloc", sb)) {
+ launchd_use_gmalloc = true;
+ }
+
+ if (config_check(".launchd_log_shutdown", sb)) {
+ launchd_log_shutdown = true;
+ }
+
+ if (config_check(".launchd_log_debug", sb)) {
+ launchd_log_debug = true;
+ }
+
+ if (config_check(".launchd_log_perf", sb)) {
+ launchd_log_perf = true;
+ }
+
+ if (config_check("/etc/rc.cdrom", sb)) {
+ launchd_osinstaller = true;
+ }
+
+ if (!pid1_magic && config_check(".launchd_allow_global_dyld_envvars", sb)) {
+ launchd_allow_global_dyld_envvars = true;
+ }
+
+ char bootargs[1024];
+ size_t len = sizeof(bootargs) - 1;
+ int r = pid1_magic ? sysctlbyname("kern.bootargs", bootargs, &len, NULL, 0) : -1;
+ if (r == 0) {
+ if (strnstr(bootargs, "-v", len)) {
+ launchd_verbose_boot = true;
+ }
+ if (strnstr(bootargs, "launchd_trap_sigkill_bugs", len)) {
+ launchd_trap_sigkill_bugs = true;
+ }
+ }
+
+ if (pid1_magic && launchd_verbose_boot && config_check(".launchd_shutdown_debugging", sb)) {
+ launchd_shutdown_debugging = true;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2005 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@
+ */
+
+#ifndef __LAUNCHD_RUNTIME_H__
+#define __LAUNCHD_RUNTIME_H__
+
+#include <xpc/xpc.h>
+#include <mach/mach.h>
+#include <sys/types.h>
+#include <bsm/libbsm.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <float.h>
+#include <syslog.h>
+
+#include "kill2.h"
+#include "ktrace.h"
+#include "log.h"
+
+#define likely(x) __builtin_expect((bool)(x), true)
+#define unlikely(x) __builtin_expect((bool)(x), false)
+
+struct ldcred {
+ uid_t euid;
+ uid_t uid;
+ gid_t egid;
+ gid_t gid;
+ pid_t pid;
+ au_asid_t asid;
+ mach_port_t asport;
+};
+
+typedef void (*kq_callback)(void *, struct kevent *);
+typedef boolean_t (*mig_callback)(mach_msg_header_t *, mach_msg_header_t *);
+typedef void (*timeout_callback)(void);
+
+extern bool launchd_verbose_boot;
+/* Configuration knobs set in do_file_init(). */
+extern bool launchd_shutdown_debugging;
+extern bool launchd_use_gmalloc;
+extern bool launchd_malloc_log_stacks;
+extern bool launchd_log_shutdown;
+extern bool launchd_log_debug;
+extern bool launchd_log_perf;
+extern bool launchd_trap_sigkill_bugs;
+extern bool launchd_osinstaller;
+extern bool launchd_allow_global_dyld_envvars;
+
+extern bool launchd_runtime_busy_time;
+extern mach_port_t inherited_bootstrap_port;
+extern size_t runtime_busy_cnt;
+extern int32_t launchd_sync_frequency;
+extern pid_t launchd_wsp;
+
+mach_port_t runtime_get_kernel_port(void);
+extern boolean_t launchd_internal_demux(mach_msg_header_t *Request, mach_msg_header_t *Reply);
+
+void runtime_add_ref(void);
+void runtime_del_ref(void);
+void runtime_add_weak_ref(void);
+void runtime_del_weak_ref(void);
+void runtime_install_timer(void);
+void runtime_remove_timer(void);
+
+void launchd_runtime_init(void);
+void launchd_runtime_init2(void);
+void launchd_runtime(void) __attribute__((noreturn));
+
+void launchd_log_vm_stats(void);
+
+int runtime_close(int fd);
+int runtime_fsync(int fd);
+
+#define RUNTIME_ADVISABLE_IDLE_TIMEOUT 30
+
+void runtime_set_timeout(timeout_callback to_cb, unsigned int sec);
+kern_return_t runtime_add_mport(mach_port_t name, mig_callback demux);
+kern_return_t runtime_remove_mport(mach_port_t name);
+void runtime_record_caller_creds(audit_token_t *token);
+struct ldcred *runtime_get_caller_creds(void);
+
+const char *signal_to_C_name(unsigned int sig);
+const char *reboot_flags_to_C_names(unsigned int flags);
+
+int kevent_bulk_mod(struct kevent *kev, size_t kev_cnt);
+int kevent_mod(uintptr_t ident, short filter, u_short flags, u_int fflags, intptr_t data, void *udata);
+void log_kevent_struct(int level, struct kevent *kev_base, int indx);
+
+pid_t runtime_fork(mach_port_t bsport);
+
+mach_msg_return_t launchd_exc_runtime_once(mach_port_t port, mach_msg_size_t rcv_msg_size, mach_msg_size_t send_msg_size, mig_reply_error_t *bufRequest, mig_reply_error_t *bufReply, mach_msg_timeout_t to);
+
+int64_t runtime_get_wall_time(void) __attribute__((warn_unused_result));
+uint64_t runtime_get_opaque_time(void) __attribute__((warn_unused_result));
+uint64_t runtime_get_opaque_time_of_event(void) __attribute__((pure, warn_unused_result));
+uint64_t runtime_opaque_time_to_nano(uint64_t o) __attribute__((const, warn_unused_result));
+uint64_t runtime_get_nanoseconds_since(uint64_t o) __attribute__((pure, warn_unused_result));
+
+kern_return_t launchd_set_bport(mach_port_t name);
+kern_return_t launchd_get_bport(mach_port_t *name);
+kern_return_t launchd_mport_notify_req(mach_port_t name, mach_msg_id_t which);
+kern_return_t launchd_mport_notify_cancel(mach_port_t name, mach_msg_id_t which);
+kern_return_t launchd_mport_create_recv(mach_port_t *name);
+kern_return_t launchd_mport_deallocate(mach_port_t name);
+kern_return_t launchd_mport_make_send(mach_port_t name);
+kern_return_t launchd_mport_copy_send(mach_port_t name);
+kern_return_t launchd_mport_make_send_once(mach_port_t name, mach_port_t *so);
+kern_return_t launchd_mport_close_recv(mach_port_t name);
+
+#endif /* __LAUNCHD_RUNTIME_H__ */
--- /dev/null
+/*
+ * Copyright (c) 2005-2011 Apple 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 "config.h"
+#include "launch.h"
+#include "launch_priv.h"
+#include "bootstrap.h"
+#include "vproc.h"
+#include "vproc_priv.h"
+#include "vproc_internal.h"
+#include "bootstrap_priv.h"
+#include "launch_internal.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreFoundation/CFPriv.h>
+#include <CoreFoundation/CFLogUtilities.h>
+#include <TargetConditionals.h>
+#include <IOKit/IOKitLib.h>
+#include <NSSystemDirectories.h>
+#include <mach/mach.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/sysctl.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/fcntl.h>
+#include <sys/event.h>
+#include <sys/resource.h>
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/reboot.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet6/nd6.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <libinfo.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <grp.h>
+#include <netdb.h>
+#include <syslog.h>
+#include <glob.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+#include <dns_sd.h>
+#include <paths.h>
+#include <utmpx.h>
+#include <bootfiles.h>
+#include <sysexits.h>
+#include <util.h>
+#include <spawn.h>
+#include <sys/syslimits.h>
+#include <fnmatch.h>
+#include <assumes.h>
+#include <dlfcn.h>
+
+#if HAVE_LIBAUDITD
+#include <bsm/auditd_lib.h>
+#ifndef AUDITD_PLIST_FILE
+#define AUDITD_PLIST_FILE "/System/Library/LaunchDaemons/com.apple.auditd.plist"
+#endif
+#endif
+
+extern char **environ;
+
+#define LAUNCH_SECDIR _PATH_TMP "launch-XXXXXX"
+#define LAUNCH_ENV_KEEPCONTEXT "LaunchKeepContext"
+#define LAUNCH_ENV_BOOTSTRAPPINGSYSTEM "LaunchBootstrappingSystem"
+
+#define CFTypeCheck(cf, type) (CFGetTypeID(cf) == type ## GetTypeID())
+#define CFReleaseIfNotNULL(cf) if (cf) CFRelease(cf);
+
+struct load_unload_state {
+ launch_data_t pass1;
+ char *session_type;
+ bool editondisk:1, load:1, forceload:1;
+};
+
+static void launchctl_log(int level, const char *fmt, ...);
+static void launchctl_log_CFString(int level, CFStringRef string);
+static void myCFDictionaryApplyFunction(const void *key, const void *value, void *context);
+static void job_override(CFTypeRef key, CFTypeRef val, CFMutableDictionaryRef job);
+static CFTypeRef CFTypeCreateFromLaunchData(launch_data_t obj);
+static CFArrayRef CFArrayCreateFromLaunchArray(launch_data_t arr);
+static CFDictionaryRef CFDictionaryCreateFromLaunchDictionary(launch_data_t dict);
+static bool launch_data_array_append(launch_data_t a, launch_data_t o);
+static void insert_event(launch_data_t, const char *, const char *, launch_data_t);
+static void distill_jobs(launch_data_t);
+static void distill_config_file(launch_data_t);
+static void distill_fsevents(launch_data_t);
+static void sock_dict_cb(launch_data_t what, const char *key, void *context);
+static void sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data_t fdarray, launch_data_t thejob);
+static launch_data_t CF2launch_data(CFTypeRef);
+static launch_data_t read_plist_file(const char *file, bool editondisk, bool load);
+static CFPropertyListRef CreateMyPropertyListFromFile(const char *);
+static CFPropertyListRef CFPropertyListCreateFromFile(CFURLRef plistURL);
+static void WriteMyPropertyListToFile(CFPropertyListRef, const char *);
+static bool path_goodness_check(const char *path, bool forceload);
+static void readpath(const char *, struct load_unload_state *);
+static void readfile(const char *, struct load_unload_state *);
+static int _fd(int);
+static int demux_cmd(int argc, char *const argv[]);
+static void submit_job_pass(launch_data_t jobs);
+static void do_mgroup_join(int fd, int family, int socktype, int protocol, const char *mgroup);
+static mach_port_t str2bsport(const char *s);
+static void print_jobs(launch_data_t j, const char *key, void *context);
+static void print_obj(launch_data_t obj, const char *key, void *context);
+static bool str2lim(const char *buf, rlim_t *res);
+static const char *lim2str(rlim_t val, char *buf);
+static const char *num2name(int n);
+static ssize_t name2num(const char *n);
+static void unloadjob(launch_data_t job);
+static void print_key_value(launch_data_t obj, const char *key, void *context);
+static void print_launchd_env(launch_data_t obj, const char *key, void *context);
+static void loopback_setup_ipv4(void);
+static void loopback_setup_ipv6(void);
+static pid_t fwexec(const char *const *argv, int *wstatus);
+static void do_potential_fsck(void);
+static bool path_check(const char *path);
+static bool is_safeboot(void);
+static bool is_netboot(void);
+static void apply_sysctls_from_file(const char *thefile);
+static void empty_dir(const char *thedir, struct stat *psb);
+static int touch_file(const char *path, mode_t m);
+static void do_sysversion_sysctl(void);
+static void do_application_firewall_magic(int sfd, launch_data_t thejob);
+static void preheat_page_cache_hack(void);
+static void do_bootroot_magic(void);
+static void do_single_user_mode(bool);
+static bool do_single_user_mode2(void);
+static void do_crash_debug_mode(void);
+static bool do_crash_debug_mode2(void);
+static void read_launchd_conf(void);
+static bool job_disabled_logic(launch_data_t obj);
+static void fix_bogus_file_metadata(void);
+static void do_file_init(void) __attribute__((constructor));
+static void setup_system_context(void);
+static void handle_system_bootstrapper_crashes_separately(void);
+static void fatal_signal_handler(int sig, siginfo_t *si, void *uap);
+
+typedef enum {
+ BOOTCACHE_START = 1,
+ BOOTCACHE_TAG,
+ BOOTCACHE_STOP,
+} BootCache_action_t;
+
+static void do_BootCache_magic(BootCache_action_t what);
+
+static int bootstrap_cmd(int argc, char *const argv[]);
+static int load_and_unload_cmd(int argc, char *const argv[]);
+//static int reload_cmd(int argc, char *const argv[]);
+static int start_stop_remove_cmd(int argc, char *const argv[]);
+static int submit_cmd(int argc, char *const argv[]);
+static int list_cmd(int argc, char *const argv[]);
+
+static int setenv_cmd(int argc, char *const argv[]);
+static int unsetenv_cmd(int argc, char *const argv[]);
+static int getenv_and_export_cmd(int argc, char *const argv[]);
+static int wait4debugger_cmd(int argc, char *const argv[]);
+
+static int limit_cmd(int argc, char *const argv[]);
+static int stdio_cmd(int argc, char *const argv[]);
+static int fyi_cmd(int argc, char *const argv[]);
+static int logupdate_cmd(int argc, char *const argv[]);
+static int umask_cmd(int argc, char *const argv[]);
+static int getrusage_cmd(int argc, char *const argv[]);
+static int bsexec_cmd(int argc, char *const argv[]);
+static int _bslist_cmd(mach_port_t bport, unsigned int depth, bool show_job, bool local_only);
+static int bslist_cmd(int argc, char *const argv[]);
+static int _bstree_cmd(mach_port_t bsport, unsigned int depth, bool show_jobs);
+static int bstree_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused)));
+static int managerpid_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused)));
+static int manageruid_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused)));
+static int managername_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused)));
+static int asuser_cmd(int argc, char * const argv[]);
+static int exit_cmd(int argc, char *const argv[]) __attribute__((noreturn));
+static int help_cmd(int argc, char *const argv[]);
+
+static const struct {
+ const char *name;
+ int (*func)(int argc, char *const argv[]);
+ const char *desc;
+} cmds[] = {
+ { "load", load_and_unload_cmd, "Load configuration files and/or directories" },
+ { "unload", load_and_unload_cmd, "Unload configuration files and/or directories" },
+// { "reload", reload_cmd, "Reload configuration files and/or directories" },
+ { "start", start_stop_remove_cmd, "Start specified job" },
+ { "stop", start_stop_remove_cmd, "Stop specified job" },
+ { "submit", submit_cmd, "Submit a job from the command line" },
+ { "remove", start_stop_remove_cmd, "Remove specified job" },
+ { "bootstrap", bootstrap_cmd, "Bootstrap launchd" },
+ { "list", list_cmd, "List jobs and information about jobs" },
+ { "setenv", setenv_cmd, "Set an environmental variable in launchd" },
+ { "unsetenv", unsetenv_cmd, "Unset an environmental variable in launchd" },
+ { "getenv", getenv_and_export_cmd, "Get an environmental variable from launchd" },
+ { "export", getenv_and_export_cmd, "Export shell settings from launchd" },
+ { "debug", wait4debugger_cmd, "Set the WaitForDebugger flag for the target job to true." },
+ { "limit", limit_cmd, "View and adjust launchd resource limits" },
+ { "stdout", stdio_cmd, "Redirect launchd's standard out to the given path" },
+ { "stderr", stdio_cmd, "Redirect launchd's standard error to the given path" },
+ { "shutdown", fyi_cmd, "Prepare for system shutdown" },
+ { "singleuser", fyi_cmd, "Switch to single-user mode" },
+ { "getrusage", getrusage_cmd, "Get resource usage statistics from launchd" },
+ { "log", logupdate_cmd, "Adjust the logging level or mask of launchd" },
+ { "umask", umask_cmd, "Change launchd's umask" },
+ { "bsexec", bsexec_cmd, "Execute a process within a different Mach bootstrap subset" },
+ { "bslist", bslist_cmd, "List Mach bootstrap services and optional servers" },
+ { "bstree", bstree_cmd, "Show the entire Mach bootstrap tree. Requires root privileges." },
+ { "managerpid", managerpid_cmd, "Print the PID of the launchd managing this Mach bootstrap." },
+ { "manageruid", manageruid_cmd, "Print the UID of the launchd managing this Mach bootstrap." },
+ { "managername", managername_cmd, "Print the name of this Mach bootstrap." },
+ { "asuser", asuser_cmd, "Execute a subcommand in the given user's context." },
+ { "exit", exit_cmd, "Exit the interactive invocation of launchctl" },
+ { "quit", exit_cmd, "Quit the interactive invocation of launchctl" },
+ { "help", help_cmd, "This help output" },
+};
+
+static bool _launchctl_istty;
+static bool _launchctl_verbose;
+static bool _launchctl_is_managed;
+static bool _launchctl_apple_internal;
+static bool _launchctl_system_context;
+static bool _launchctl_uid0_context;
+static bool _launchctl_system_bootstrap;
+static bool _launchctl_peruser_bootstrap;
+static bool _launchctl_verbose_boot = false;
+static bool _launchctl_startup_debugging = false;
+
+static bool _launchctl_overrides_db_changed = false;
+static CFMutableDictionaryRef _launchctl_overrides_db = NULL;
+
+static char *_launchctl_job_overrides_db_path;
+static char *_launchctl_managername = NULL;
+
+int
+main(int argc, char *const argv[])
+{
+ char *l;
+
+ if (getenv(LAUNCH_ENV_BOOTSTRAPPINGSYSTEM)) {
+ /* We're bootstrapping the install environment, so we can't talk to
+ * mDNSResponder or opendirectoryd.
+ *
+ * See <rdar://problem/9877230>.
+ */
+ si_search_module_set_flags("mdns", 1);
+ si_search_module_set_flags("ds", 1);
+ }
+
+ int64_t is_managed = 0;
+ (void)vproc_swap_integer(NULL, VPROC_GSK_IS_MANAGED, NULL, &is_managed);
+ _launchctl_is_managed = is_managed;
+
+ _launchctl_istty = isatty(STDIN_FILENO);
+ argc--, argv++;
+
+ if (argc > 0 && argv[0][0] == '-') {
+ char *flago;
+
+ for (flago = argv[0] + 1; *flago; flago++) {
+ switch (*flago) {
+ case 'v':
+ _launchctl_verbose = true;
+ break;
+ case 'u':
+ if (argc > 1) {
+ if (strncmp(argv[1], "root", sizeof("root")) == 0) {
+ _launchctl_uid0_context = true;
+ } else {
+ launchctl_log(LOG_ERR, "Unknown user: %s", argv[1]);
+ exit(EXIT_FAILURE);
+ }
+ argc--, argv++;
+ } else {
+ launchctl_log(LOG_ERR, "-u option requires an argument.");
+ }
+ break;
+ case '1':
+ _launchctl_system_context = true;
+ break;
+ default:
+ launchctl_log(LOG_ERR, "Unknown argument: '-%c'", *flago);
+ break;
+ }
+ }
+ argc--, argv++;
+ }
+
+ /* Running in the context of the root user's per-user launchd is only from
+ * within that session.
+ */
+ if (_launchctl_uid0_context) {
+ int64_t manager_uid = -1, manager_pid = -1;
+ (void)vproc_swap_integer(NULL, VPROC_GSK_MGR_UID, NULL, &manager_uid);
+ (void)vproc_swap_integer(NULL, VPROC_GSK_MGR_PID, NULL, &manager_pid);
+ if (manager_uid || manager_pid == 1) {
+ launchctl_log(LOG_ERR, "Running in the root user's per-user context is not supported outside of the root user's bootstrap.");
+ exit(EXIT_FAILURE);
+ }
+ } else if (!(_launchctl_system_context || _launchctl_uid0_context)) {
+ /* Running in the system context is implied when we're running as root
+ * and not running as a bootstrapper.
+ */
+ _launchctl_system_context = (!_launchctl_is_managed && getuid() == 0);
+ }
+
+ if (_launchctl_system_context) {
+ if (getuid() == 0) {
+ setup_system_context();
+ } else {
+ launchctl_log(LOG_ERR, "You must be root to run in the system context.");
+ exit(EXIT_FAILURE);
+ }
+ } else if (_launchctl_uid0_context) {
+ if (getuid() != 0) {
+ launchctl_log(LOG_ERR, "You must be root to run in the root user context.");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (!readline) {
+ launchctl_log(LOG_ERR, "missing library: readline");
+ exit(EXIT_FAILURE);
+ }
+
+ if (argc == 0) {
+ while ((l = readline(_launchctl_istty ? "launchd% " : NULL))) {
+ char *inputstring = l, *argv2[100], **ap = argv2;
+ int i = 0;
+
+ while ((*ap = strsep(&inputstring, " \t"))) {
+ if (**ap != '\0') {
+ ap++;
+ i++;
+ }
+ }
+
+ if (i > 0) {
+ demux_cmd(i, argv2);
+ }
+
+ free(l);
+ }
+
+ if (_launchctl_istty) {
+ fputc('\n', stdout);
+ }
+ }
+
+ if (argc > 0) {
+ exit(demux_cmd(argc, argv));
+ }
+
+ exit(EXIT_SUCCESS);
+}
+
+int
+demux_cmd(int argc, char *const argv[])
+{
+ size_t i;
+
+ optind = 1;
+ optreset = 1;
+
+ for (i = 0; i < (sizeof cmds / sizeof cmds[0]); i++) {
+ if (!strcmp(cmds[i].name, argv[0])) {
+ return cmds[i].func(argc, argv);
+ }
+ }
+
+ launchctl_log(LOG_ERR, "%s: unknown subcommand \"%s\"", getprogname(), argv[0]);
+ return 1;
+}
+
+void
+launchctl_log(int level, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+
+ if (_launchctl_is_managed) {
+ vsyslog(level, fmt, ap);
+ } else {
+ char *buff = NULL;
+ (void)vasprintf(&buff, fmt, ap);
+
+ FILE *where = stdout;
+ if (level < LOG_NOTICE) {
+ where = stderr;
+ }
+
+ fprintf(where, "%s\n", buff);
+ free(buff);
+ }
+
+ va_end(ap);
+}
+
+void
+launchctl_log_CFString(int level, CFStringRef string)
+{
+ // Big enough. Don't feel like jumping through CF's hoops.
+ char *buff = malloc(4096);
+ (void)CFStringGetCString(string, buff, 4096, kCFStringEncodingUTF8);
+ launchctl_log(level, "%s", buff);
+ free(buff);
+}
+
+void
+read_launchd_conf(void)
+{
+ char s[1000], *c, *av[100];
+ const char *file;
+ size_t len;
+ int i;
+ FILE *f;
+
+ if (getppid() == 1) {
+ file = "/etc/launchd.conf";
+ } else {
+ file = "/etc/launchd-user.conf";
+ }
+
+ if (!(f = fopen(file, "r"))) {
+ return;
+ }
+
+ while ((c = fgets(s, (int) sizeof s, f))) {
+ len = strlen(c);
+ if (len && c[len - 1] == '\n') {
+ c[len - 1] = '\0';
+ }
+
+ i = 0;
+
+ while ((av[i] = strsep(&c, " \t"))) {
+ if (*(av[i]) != '\0') {
+ i++;
+ }
+ }
+
+ if (i > 0) {
+ demux_cmd(i, av);
+ }
+ }
+
+ fclose(f);
+}
+
+static CFPropertyListRef
+CFPropertyListCreateFromFile(CFURLRef plistURL)
+{
+ CFReadStreamRef plistReadStream = CFReadStreamCreateWithFile(NULL, plistURL);
+
+ CFErrorRef streamErr = NULL;
+ if (!CFReadStreamOpen(plistReadStream)) {
+ streamErr = CFReadStreamCopyError(plistReadStream);
+ CFStringRef errString = CFErrorCopyDescription(streamErr);
+
+ launchctl_log_CFString(LOG_ERR, errString);
+
+ CFRelease(errString);
+ CFRelease(streamErr);
+ }
+
+ CFPropertyListRef plist = NULL;
+ if (plistReadStream) {
+ CFStringRef errString = NULL;
+ CFPropertyListFormat plistFormat = 0;
+ plist = CFPropertyListCreateFromStream(NULL, plistReadStream, 0, kCFPropertyListImmutable, &plistFormat, &errString);
+ if (!plist) {
+ launchctl_log_CFString(LOG_ERR, errString);
+ CFRelease(errString);
+ }
+ }
+
+ CFReadStreamClose(plistReadStream);
+ CFRelease(plistReadStream);
+
+ return plist;
+}
+
+int
+unsetenv_cmd(int argc, char *const argv[])
+{
+ launch_data_t resp, tmp, msg;
+
+ if (argc != 2) {
+ launchctl_log(LOG_ERR, "%s usage: unsetenv <key>", getprogname());
+ return 1;
+ }
+
+ msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+
+ tmp = launch_data_new_string(argv[1]);
+ launch_data_dict_insert(msg, tmp, LAUNCH_KEY_UNSETUSERENVIRONMENT);
+
+ resp = launch_msg(msg);
+
+ launch_data_free(msg);
+
+ if (resp) {
+ launch_data_free(resp);
+ } else {
+ launchctl_log(LOG_ERR, "launch_msg(\"%s\"): %s", LAUNCH_KEY_UNSETUSERENVIRONMENT, strerror(errno));
+ }
+
+ return 0;
+}
+
+int
+setenv_cmd(int argc, char *const argv[])
+{
+ launch_data_t resp, tmp, tmpv, msg;
+
+ if (argc != 3) {
+ launchctl_log(LOG_ERR, "%s usage: setenv <key> <value>", getprogname());
+ return 1;
+ }
+
+ msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ tmp = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+
+ tmpv = launch_data_new_string(argv[2]);
+ launch_data_dict_insert(tmp, tmpv, argv[1]);
+ launch_data_dict_insert(msg, tmp, LAUNCH_KEY_SETUSERENVIRONMENT);
+
+ resp = launch_msg(msg);
+ launch_data_free(msg);
+
+ if (resp) {
+ launch_data_free(resp);
+ } else {
+ launchctl_log(LOG_ERR, "launch_msg(\"%s\"): %s", LAUNCH_KEY_SETUSERENVIRONMENT, strerror(errno));
+ }
+
+ return 0;
+}
+
+void
+print_launchd_env(launch_data_t obj, const char *key, void *context)
+{
+ bool *is_csh = context;
+
+ /* XXX escape the double quotes */
+ if (*is_csh) {
+ launchctl_log(LOG_NOTICE, "setenv %s \"%s\";", key, launch_data_get_string(obj));
+ } else {
+ launchctl_log(LOG_NOTICE, "%s=\"%s\"; export %s;", key, launch_data_get_string(obj), key);
+ }
+}
+
+void
+print_key_value(launch_data_t obj, const char *key, void *context)
+{
+ const char *k = context;
+
+ if (!strcmp(key, k)) {
+ launchctl_log(LOG_NOTICE, "%s", launch_data_get_string(obj));
+ }
+}
+
+int
+getenv_and_export_cmd(int argc, char *const argv[])
+{
+ launch_data_t resp;
+ bool is_csh = false;
+ char *k;
+
+ if (!strcmp(argv[0], "export")) {
+ char *s = getenv("SHELL");
+ if (s) {
+ is_csh = strstr(s, "csh") ? true : false;
+ }
+ } else if (argc != 2) {
+ launchctl_log(LOG_ERR, "%s usage: getenv <key>", getprogname());
+ return 1;
+ }
+
+ k = argv[1];
+
+ if (vproc_swap_complex(NULL, VPROC_GSK_ENVIRONMENT, NULL, &resp) == NULL) {
+ if (!strcmp(argv[0], "export")) {
+ launch_data_dict_iterate(resp, print_launchd_env, &is_csh);
+ } else {
+ launch_data_dict_iterate(resp, print_key_value, k);
+ }
+ launch_data_free(resp);
+ return 0;
+ } else {
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+wait4debugger_cmd(int argc, char * const argv[])
+{
+ if (argc != 3) {
+ launchctl_log(LOG_ERR, "%s usage: debug <label> <value>", argv[0]);
+ return 1;
+ }
+
+ int result = 1;
+ int64_t inval = 0;
+ if (strncmp(argv[2], "true", sizeof("true")) == 0) {
+ inval = 1;
+ } else if (strncmp(argv[2], "false", sizeof("false")) != 0) {
+ inval = atoi(argv[2]);
+ inval &= 1;
+ }
+
+ vproc_t vp = vprocmgr_lookup_vproc(argv[1]);
+ if (vp) {
+ vproc_err_t verr = vproc_swap_integer(vp, VPROC_GSK_WAITFORDEBUGGER, &inval, NULL);
+ if (verr) {
+ launchctl_log(LOG_ERR, "Failed to set WaitForDebugger flag on %s.", argv[1]);
+ } else {
+ result = 0;
+ }
+ vproc_release(vp);
+ }
+
+ return result;
+}
+
+void
+unloadjob(launch_data_t job)
+{
+ launch_data_t tmps;
+
+ tmps = launch_data_dict_lookup(job, LAUNCH_JOBKEY_LABEL);
+
+ if (!tmps) {
+ launchctl_log(LOG_ERR, "%s: Error: Missing Key: %s", getprogname(), LAUNCH_JOBKEY_LABEL);
+ return;
+ }
+
+ if (_vproc_send_signal_by_label(launch_data_get_string(tmps), VPROC_MAGIC_UNLOAD_SIGNAL) != NULL) {
+ launchctl_log(LOG_ERR, "%s: Error unloading: %s", getprogname(), launch_data_get_string(tmps));
+ }
+}
+
+void
+job_override(CFTypeRef key, CFTypeRef val, CFMutableDictionaryRef job)
+{
+ if (!CFTypeCheck(key, CFString)) {
+ return;
+ }
+ if (CFStringCompare(key, CFSTR(LAUNCH_JOBKEY_LABEL), kCFCompareCaseInsensitive) == 0) {
+ return;
+ }
+
+ CFDictionarySetValue(job, key, val);
+}
+
+launch_data_t
+read_plist_file(const char *file, bool editondisk, bool load)
+{
+ CFPropertyListRef plist = CreateMyPropertyListFromFile(file);
+ launch_data_t r = NULL;
+
+ if (NULL == plist) {
+ launchctl_log(LOG_ERR, "%s: no plist was returned for: %s", getprogname(), file);
+ return NULL;
+ }
+
+ CFStringRef label = CFDictionaryGetValue(plist, CFSTR(LAUNCH_JOBKEY_LABEL));
+ if (!(label && CFTypeCheck(label, CFString))) {
+ return NULL;
+ }
+
+ if (_launchctl_overrides_db) {
+ CFDictionaryRef overrides = CFDictionaryGetValue(_launchctl_overrides_db, label);
+ if (overrides && CFTypeCheck(overrides, CFDictionary)) {
+ CFDictionaryApplyFunction(overrides, (CFDictionaryApplierFunction)job_override, (void *)plist);
+ }
+ }
+
+ if (editondisk) {
+ if (_launchctl_overrides_db) {
+ CFMutableDictionaryRef job = (CFMutableDictionaryRef)CFDictionaryGetValue(_launchctl_overrides_db, label);
+ if (!job || !CFTypeCheck(job, CFDictionary)) {
+ job = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFDictionarySetValue(_launchctl_overrides_db, label, job);
+ CFRelease(job);
+ }
+
+ CFDictionarySetValue(job, CFSTR(LAUNCH_JOBKEY_DISABLED), load ? kCFBooleanFalse : kCFBooleanTrue);
+ CFDictionarySetValue((CFMutableDictionaryRef)plist, CFSTR(LAUNCH_JOBKEY_DISABLED), load ? kCFBooleanFalse : kCFBooleanTrue);
+ _launchctl_overrides_db_changed = true;
+ } else {
+ if (load) {
+ CFDictionaryRemoveValue((CFMutableDictionaryRef)plist, CFSTR(LAUNCH_JOBKEY_DISABLED));
+ } else {
+ CFDictionarySetValue((CFMutableDictionaryRef)plist, CFSTR(LAUNCH_JOBKEY_DISABLED), kCFBooleanTrue);
+ }
+ WriteMyPropertyListToFile(plist, file);
+ }
+ }
+
+ r = CF2launch_data(plist);
+
+ CFRelease(plist);
+
+ return r;
+}
+
+static bool
+sysctl_hw_streq(int mib_slot, const char *str)
+{
+ char buf[1000];
+ size_t bufsz = sizeof(buf);
+ int mib[] = { CTL_HW, mib_slot };
+
+ if (sysctl(mib, 2, buf, &bufsz, NULL, 0) != -1) {
+ if (strcmp(buf, str) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void
+limitloadtohardware_iterator(launch_data_t val, const char *key, void *ctx)
+{
+ bool *result = ctx;
+
+ char name[128];
+ (void)snprintf(name, sizeof(name), "hw.%s", key);
+
+ int mib[2];
+ size_t sz = 2;
+ if (*result != true && osx_assumes_zero(sysctlnametomib(name, mib, &sz)) == 0) {
+ if (launch_data_get_type(val) == LAUNCH_DATA_ARRAY) {
+ size_t c = launch_data_array_get_count(val);
+
+ size_t i = 0;
+ for (i = 0; i < c; i++) {
+ launch_data_t oai = launch_data_array_get_index(val, i);
+ if (sysctl_hw_streq(mib[1], launch_data_get_string(oai))) {
+ *result = true;
+ i = c;
+ }
+ }
+ }
+ }
+}
+
+void
+readfile(const char *what, struct load_unload_state *lus)
+{
+ char ourhostname[1024];
+ launch_data_t tmpd, tmps, thejob, tmpa;
+ bool job_disabled = false;
+ size_t i, c;
+
+ gethostname(ourhostname, sizeof(ourhostname));
+
+ if (NULL == (thejob = read_plist_file(what, lus->editondisk, lus->load))) {
+ launchctl_log(LOG_ERR, "%s: no plist was returned for: %s", getprogname(), what);
+ return;
+ }
+
+
+ if (NULL == launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LABEL)) {
+ launchctl_log(LOG_ERR, "%s: missing the Label key: %s", getprogname(), what);
+ goto out_bad;
+ }
+
+ if ((launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_PROGRAM) == NULL) &&
+ (launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_PROGRAMARGUMENTS) == NULL)) {
+ launchctl_log(LOG_ERR, "%s: neither a Program nor a ProgramArguments key was specified: %s", getprogname(), what);
+ goto out_bad;
+ }
+
+ if (NULL != (tmpa = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADFROMHOSTS))) {
+ c = launch_data_array_get_count(tmpa);
+
+ for (i = 0; i < c; i++) {
+ launch_data_t oai = launch_data_array_get_index(tmpa, i);
+ if (!strcasecmp(ourhostname, launch_data_get_string(oai))) {
+ goto out_bad;
+ }
+ }
+ }
+
+ if (NULL != (tmpa = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADTOHOSTS))) {
+ c = launch_data_array_get_count(tmpa);
+
+ for (i = 0; i < c; i++) {
+ launch_data_t oai = launch_data_array_get_index(tmpa, i);
+ if (!strcasecmp(ourhostname, launch_data_get_string(oai))) {
+ break;
+ }
+ }
+
+ if (i == c) {
+ goto out_bad;
+ }
+ }
+
+ if (NULL != (tmpd = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADTOHARDWARE))) {
+ bool result = false;
+ launch_data_dict_iterate(tmpd, limitloadtohardware_iterator, &result);
+ if (!result) {
+ goto out_bad;
+ }
+ }
+
+ if (NULL != (tmpd = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADFROMHARDWARE))) {
+ bool result = false;
+ launch_data_dict_iterate(tmpd, limitloadtohardware_iterator, &result);
+ if (result) {
+ goto out_bad;
+ }
+ }
+
+ /* If the manager is Aqua, the LimitLoadToSessionType should default to
+ * "Aqua".
+ *
+ * <rdar://problem/8297909>
+ */
+ if (!_launchctl_managername) {
+ if (vproc_swap_string(NULL, VPROC_GSK_MGR_NAME, NULL, &_launchctl_managername)) {
+ if (bootstrap_port) {
+ /* This is only an error if we are running with a neutered
+ * bootstrap port, otherwise we wouldn't expect this operating to
+ * succeed.
+ *
+ * <rdar://problem/10514286>
+ */
+ launchctl_log(LOG_ERR, "Could not obtain manager name: ppid/bootstrap: %d/0x%x", getppid(), bootstrap_port);
+ }
+
+ _launchctl_managername = "";
+ }
+ }
+
+ if (!lus->session_type) {
+ if (strcmp(_launchctl_managername, "Aqua") == 0) {
+ lus->session_type = "Aqua";
+ }
+ }
+
+ if (lus->session_type && !(tmpa = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE))) {
+ tmpa = launch_data_new_string("Aqua");
+ launch_data_dict_insert(thejob, tmpa, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE);
+ }
+
+ if ((tmpa = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE))) {
+ const char *allowed_session;
+ bool skipjob = true;
+
+ /* My sincere apologies to anyone who has to deal with this
+ * LimitLoadToSessionType madness. It was like this when I got here, but
+ * I've knowingly made it worse, hopefully to the benefit of the end
+ * user.
+ *
+ * See <rdar://problem/8769211> and <rdar://problem/7114980>.
+ */
+ if (!lus->session_type && launch_data_get_type(tmpa) == LAUNCH_DATA_STRING) {
+ if (strcasecmp("System", _launchctl_managername) == 0 && strcasecmp("System", launch_data_get_string(tmpa)) == 0) {
+ skipjob = false;
+ }
+ }
+
+ if (lus->session_type) switch (launch_data_get_type(tmpa)) {
+ case LAUNCH_DATA_ARRAY:
+ c = launch_data_array_get_count(tmpa);
+ for (i = 0; i < c; i++) {
+ tmps = launch_data_array_get_index(tmpa, i);
+ allowed_session = launch_data_get_string(tmps);
+ if (strcasecmp(lus->session_type, allowed_session) == 0) {
+ skipjob = false;
+ /* we have to do the following so job_reparent_hack() works within launchd */
+ tmpa = launch_data_new_string(lus->session_type);
+ launch_data_dict_insert(thejob, tmpa, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE);
+ break;
+ }
+ }
+ break;
+ case LAUNCH_DATA_STRING:
+ allowed_session = launch_data_get_string(tmpa);
+ if (strcasecmp(lus->session_type, allowed_session) == 0) {
+ skipjob = false;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (skipjob) {
+ goto out_bad;
+ }
+ }
+
+ if ((tmpd = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_DISABLED))) {
+ job_disabled = job_disabled_logic(tmpd);
+ }
+
+ if (lus->forceload) {
+ job_disabled = false;
+ }
+
+ if (job_disabled && lus->load) {
+ goto out_bad;
+ }
+
+ if (_launchctl_system_bootstrap || _launchctl_peruser_bootstrap) {
+ uuid_t uuid;
+ uuid_clear(uuid);
+
+ launch_data_t lduuid = launch_data_new_opaque(uuid, sizeof(uuid_t));
+ launch_data_dict_insert(thejob, lduuid, LAUNCH_JOBKEY_SECURITYSESSIONUUID);
+ }
+
+ launch_data_array_append(lus->pass1, thejob);
+
+ if (_launchctl_verbose) {
+ launchctl_log(LOG_NOTICE, "Will load: %s", what);
+ }
+
+ return;
+out_bad:
+ if (_launchctl_verbose) {
+ launchctl_log(LOG_NOTICE, "Ignored: %s", what);
+ }
+ launch_data_free(thejob);
+}
+
+static void
+job_disabled_dict_logic(launch_data_t obj, const char *key, void *context)
+{
+ bool *r = context;
+
+ if (launch_data_get_type(obj) != LAUNCH_DATA_STRING) {
+ return;
+ }
+
+ if (strcasecmp(key, LAUNCH_JOBKEY_DISABLED_MACHINETYPE) == 0) {
+ if (sysctl_hw_streq(HW_MACHINE, launch_data_get_string(obj))) {
+ *r = true;
+ }
+ } else if (strcasecmp(key, LAUNCH_JOBKEY_DISABLED_MODELNAME) == 0) {
+ if (sysctl_hw_streq(HW_MODEL, launch_data_get_string(obj))) {
+ *r = true;
+ }
+ }
+}
+
+bool
+job_disabled_logic(launch_data_t obj)
+{
+ bool r = false;
+
+ switch (launch_data_get_type(obj)) {
+ case LAUNCH_DATA_DICTIONARY:
+ launch_data_dict_iterate(obj, job_disabled_dict_logic, &r);
+ break;
+ case LAUNCH_DATA_BOOL:
+ r = launch_data_get_bool(obj);
+ break;
+ default:
+ break;
+ }
+
+ return r;
+}
+
+bool
+path_goodness_check(const char *path, bool forceload)
+{
+ struct stat sb;
+
+ if (stat(path, &sb) == -1) {
+ launchctl_log(LOG_ERR, "%s: Couldn't stat(\"%s\"): %s", getprogname(), path, strerror(errno));
+ return false;
+ }
+
+ if (forceload) {
+ return true;
+ }
+
+ if (sb.st_mode & (S_IWOTH|S_IWGRP)) {
+ launchctl_log(LOG_ERR, "%s: Dubious permissions on file (skipping): %s", getprogname(), path);
+ return false;
+ }
+
+ if (sb.st_uid != 0 && sb.st_uid != getuid()) {
+ launchctl_log(LOG_ERR, "%s: Dubious ownership on file (skipping): %s", getprogname(), path);
+ return false;
+ }
+
+ if (!(S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode))) {
+ launchctl_log(LOG_ERR, "%s: Dubious path. Not a regular file or directory (skipping): %s", getprogname(), path);
+ return false;
+ }
+
+ if ((!S_ISDIR(sb.st_mode)) && (fnmatch("*.plist", path, FNM_CASEFOLD) == FNM_NOMATCH)) {
+ launchctl_log(LOG_ERR, "%s: Dubious file. Not of type .plist (skipping): %s", getprogname(), path);
+ return false;
+ }
+
+ return true;
+}
+
+void
+readpath(const char *what, struct load_unload_state *lus)
+{
+ char buf[MAXPATHLEN];
+ struct stat sb;
+ struct dirent *de;
+ DIR *d;
+
+ if (!path_goodness_check(what, lus->forceload)) {
+ return;
+ }
+
+ if (stat(what, &sb) == -1) {
+ return;
+ }
+
+ if (S_ISREG(sb.st_mode)) {
+ readfile(what, lus);
+ } else if (S_ISDIR(sb.st_mode)) {
+ if ((d = opendir(what)) == NULL) {
+ launchctl_log(LOG_ERR, "%s: opendir() failed to open the directory", getprogname());
+ return;
+ }
+
+ while ((de = readdir(d))) {
+ if (de->d_name[0] == '.') {
+ continue;
+ }
+ snprintf(buf, sizeof(buf), "%s/%s", what, de->d_name);
+
+ if (!path_goodness_check(buf, lus->forceload)) {
+ continue;
+ }
+
+ readfile(buf, lus);
+ }
+ closedir(d);
+ }
+}
+
+void
+insert_event(launch_data_t job, const char *stream, const char *key, launch_data_t event)
+{
+ launch_data_t launchevents, streamdict;
+
+ launchevents = launch_data_dict_lookup(job, LAUNCH_JOBKEY_LAUNCHEVENTS);
+ if (launchevents == NULL) {
+ launchevents = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ launch_data_dict_insert(job, launchevents, LAUNCH_JOBKEY_LAUNCHEVENTS);
+ }
+
+ streamdict = launch_data_dict_lookup(launchevents, stream);
+ if (streamdict == NULL) {
+ streamdict = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ launch_data_dict_insert(launchevents, streamdict, stream);
+ }
+
+ launch_data_dict_insert(streamdict, event, key);
+}
+
+struct distill_context {
+ launch_data_t base;
+ launch_data_t newsockdict;
+};
+
+void
+distill_jobs(launch_data_t jobs)
+{
+ size_t i, c = launch_data_array_get_count(jobs);
+ launch_data_t job;
+
+ for (i = 0; i < c; i++) {
+ job = launch_data_array_get_index(jobs, i);
+ distill_config_file(job);
+ distill_fsevents(job);
+ }
+}
+
+void
+distill_config_file(launch_data_t id_plist)
+{
+ struct distill_context dc = { id_plist, NULL };
+ launch_data_t tmp;
+
+ if ((tmp = launch_data_dict_lookup(dc.base, LAUNCH_JOBKEY_SOCKETS))) {
+ dc.newsockdict = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ launch_data_dict_iterate(tmp, sock_dict_cb, &dc);
+ launch_data_dict_insert(dc.base, dc.newsockdict, LAUNCH_JOBKEY_SOCKETS);
+ }
+}
+
+void
+sock_dict_cb(launch_data_t what, const char *key, void *context)
+{
+ struct distill_context *dc = context;
+ launch_data_t fdarray = launch_data_alloc(LAUNCH_DATA_ARRAY);
+
+ launch_data_dict_insert(dc->newsockdict, fdarray, key);
+
+ if (launch_data_get_type(what) == LAUNCH_DATA_DICTIONARY) {
+ sock_dict_edit_entry(what, key, fdarray, dc->base);
+ } else if (launch_data_get_type(what) == LAUNCH_DATA_ARRAY) {
+ launch_data_t tmp;
+ size_t i;
+
+ for (i = 0; i < launch_data_array_get_count(what); i++) {
+ tmp = launch_data_array_get_index(what, i);
+ sock_dict_edit_entry(tmp, key, fdarray, dc->base);
+ }
+ }
+}
+
+void
+sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data_t fdarray, launch_data_t thejob)
+{
+ launch_data_t a, val;
+ int sfd, st = SOCK_STREAM;
+ bool passive = true;
+
+ if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_TYPE))) {
+ if (!strcasecmp(launch_data_get_string(val), "stream")) {
+ st = SOCK_STREAM;
+ } else if (!strcasecmp(launch_data_get_string(val), "dgram")) {
+ st = SOCK_DGRAM;
+ } else if (!strcasecmp(launch_data_get_string(val), "seqpacket")) {
+ st = SOCK_SEQPACKET;
+ }
+ }
+
+ if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_PASSIVE))) {
+ passive = launch_data_get_bool(val);
+ }
+
+ if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_SECUREWITHKEY))) {
+ char secdir[] = LAUNCH_SECDIR, buf[1024];
+ launch_data_t uenv = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES);
+
+ if (NULL == uenv) {
+ uenv = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ launch_data_dict_insert(thejob, uenv, LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES);
+ }
+
+ mkdtemp(secdir);
+
+ sprintf(buf, "%s/%s", secdir, key);
+
+ a = launch_data_new_string(buf);
+ launch_data_dict_insert(tmp, a, LAUNCH_JOBSOCKETKEY_PATHNAME);
+ a = launch_data_new_string(buf);
+ launch_data_dict_insert(uenv, a, launch_data_get_string(val));
+ }
+
+ if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_PATHNAME))) {
+ struct sockaddr_un sun;
+ mode_t sun_mode = 0;
+ mode_t oldmask;
+ bool setm = false;
+
+ memset(&sun, 0, sizeof(sun));
+
+ sun.sun_family = AF_UNIX;
+
+ strncpy(sun.sun_path, launch_data_get_string(val), sizeof(sun.sun_path));
+
+ if (posix_assumes_zero(sfd = _fd(socket(AF_UNIX, st, 0))) == -1) {
+ return;
+ }
+
+ if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_PATHMODE))) {
+ sun_mode = (mode_t)launch_data_get_integer(val);
+ setm = true;
+ }
+
+ if (passive) {
+ if (unlink(sun.sun_path) == -1 && errno != ENOENT) {
+ close(sfd);
+ return;
+ }
+ oldmask = umask(S_IRWXG|S_IRWXO);
+ if (bind(sfd, (struct sockaddr *)&sun, (socklen_t) sizeof sun) == -1) {
+ close(sfd);
+ umask(oldmask);
+ return;
+ }
+ umask(oldmask);
+ if (setm) {
+ chmod(sun.sun_path, sun_mode);
+ }
+ if ((st == SOCK_STREAM || st == SOCK_SEQPACKET) && listen(sfd, -1) == -1) {
+ close(sfd);
+ return;
+ }
+ } else if (connect(sfd, (struct sockaddr *)&sun, (socklen_t) sizeof sun) == -1) {
+ close(sfd);
+ return;
+ }
+
+ val = launch_data_new_fd(sfd);
+ launch_data_array_append(fdarray, val);
+ } else {
+ launch_data_t rnames = NULL;
+ const char *node = NULL, *serv = NULL, *mgroup = NULL;
+ char servnbuf[50];
+ struct addrinfo hints, *res0, *res;
+ int gerr, sock_opt = 1;
+
+ memset(&hints, 0, sizeof(hints));
+
+ hints.ai_socktype = st;
+ if (passive) {
+ hints.ai_flags |= AI_PASSIVE;
+ }
+
+ if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_NODENAME))) {
+ node = launch_data_get_string(val);
+ }
+ if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_MULTICASTGROUP))) {
+ mgroup = launch_data_get_string(val);
+ }
+ if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_SERVICENAME))) {
+ if (LAUNCH_DATA_INTEGER == launch_data_get_type(val)) {
+ sprintf(servnbuf, "%lld", launch_data_get_integer(val));
+ serv = servnbuf;
+ } else {
+ serv = launch_data_get_string(val);
+ }
+ }
+ if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_FAMILY))) {
+ if (!strcasecmp("IPv4", launch_data_get_string(val))) {
+ hints.ai_family = AF_INET;
+ } else if (!strcasecmp("IPv6", launch_data_get_string(val))) {
+ hints.ai_family = AF_INET6;
+ }
+ }
+ if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_PROTOCOL))) {
+ if (!strcasecmp("TCP", launch_data_get_string(val))) {
+ hints.ai_protocol = IPPROTO_TCP;
+ } else if (!strcasecmp("UDP", launch_data_get_string(val))) {
+ hints.ai_protocol = IPPROTO_UDP;
+ }
+ }
+ if ((rnames = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_BONJOUR))) {
+ if (LAUNCH_DATA_BOOL != launch_data_get_type(rnames) || launch_data_get_bool(rnames)) {
+ launch_data_t newevent;
+ char eventkey[100];
+
+ newevent = launch_data_copy(tmp);
+ snprintf(eventkey, sizeof(eventkey), "com.apple.launchd.%s", key);
+ insert_event(thejob, "com.apple.bonjour.registration", eventkey, newevent);
+ }
+ }
+
+ if ((gerr = getaddrinfo(node, serv, &hints, &res0)) != 0) {
+ launchctl_log(LOG_ERR, "getaddrinfo(): %s", gai_strerror(gerr));
+ return;
+ }
+
+ for (res = res0; res; res = res->ai_next) {
+ if ((sfd = _fd(socket(res->ai_family, res->ai_socktype, res->ai_protocol))) == -1) {
+ launchctl_log(LOG_ERR, "socket(): %s", strerror(errno));
+ return;
+ }
+
+ do_application_firewall_magic(sfd, thejob);
+
+ if (hints.ai_flags & AI_PASSIVE) {
+ if (AF_INET6 == res->ai_family && -1 == setsockopt(sfd, IPPROTO_IPV6, IPV6_V6ONLY,
+ (void *)&sock_opt, (socklen_t) sizeof sock_opt)) {
+ launchctl_log(LOG_ERR, "setsockopt(IPV6_V6ONLY): %m");
+ return;
+ }
+ if (mgroup) {
+ if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, (void *)&sock_opt, (socklen_t) sizeof sock_opt) == -1) {
+ launchctl_log(LOG_ERR, "setsockopt(SO_REUSEPORT): %s", strerror(errno));
+ return;
+ }
+ } else {
+ if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&sock_opt, (socklen_t) sizeof sock_opt) == -1) {
+ launchctl_log(LOG_ERR, "setsockopt(SO_REUSEADDR): %s", strerror(errno));
+ return;
+ }
+ }
+ if (bind(sfd, res->ai_addr, res->ai_addrlen) == -1) {
+ launchctl_log(LOG_ERR, "bind(): %s", strerror(errno));
+ return;
+ }
+ /* The kernel may have dynamically assigned some part of the
+ * address. (The port being a common example.)
+ */
+ if (getsockname(sfd, res->ai_addr, &res->ai_addrlen) == -1) {
+ launchctl_log(LOG_ERR, "getsockname(): %s", strerror(errno));
+ return;
+ }
+
+ if (mgroup) {
+ do_mgroup_join(sfd, res->ai_family, res->ai_socktype, res->ai_protocol, mgroup);
+ }
+ if ((res->ai_socktype == SOCK_STREAM || res->ai_socktype == SOCK_SEQPACKET) && listen(sfd, -1) == -1) {
+ launchctl_log(LOG_ERR, "listen(): %s", strerror(errno));
+ return;
+ }
+ } else {
+ if (connect(sfd, res->ai_addr, res->ai_addrlen) == -1) {
+ launchctl_log(LOG_ERR, "connect(): %s", strerror(errno));
+ return;
+ }
+ }
+ val = launch_data_new_fd(sfd);
+ launch_data_array_append(fdarray, val);
+ }
+ }
+}
+
+void
+distill_fsevents(launch_data_t id_plist)
+{
+ launch_data_t copy, newevent;
+ launch_data_t tmp, tmp2;
+
+ if ((tmp = launch_data_dict_lookup(id_plist, LAUNCH_JOBKEY_QUEUEDIRECTORIES))) {
+ copy = launch_data_copy(tmp);
+ (void)launch_data_dict_remove(id_plist, LAUNCH_JOBKEY_QUEUEDIRECTORIES);
+
+ newevent = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ launch_data_dict_insert(newevent, copy, LAUNCH_JOBKEY_QUEUEDIRECTORIES);
+ insert_event(id_plist, "com.apple.fsevents.matching", "com.apple.launchd." LAUNCH_JOBKEY_QUEUEDIRECTORIES, newevent);
+ }
+
+ if ((tmp = launch_data_dict_lookup(id_plist, LAUNCH_JOBKEY_WATCHPATHS))) {
+ copy = launch_data_copy(tmp);
+ (void)launch_data_dict_remove(id_plist, LAUNCH_JOBKEY_WATCHPATHS);
+
+ newevent = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ launch_data_dict_insert(newevent, copy, LAUNCH_JOBKEY_WATCHPATHS);
+ insert_event(id_plist, "com.apple.fsevents.matching", "com.apple.launchd." LAUNCH_JOBKEY_WATCHPATHS, newevent);
+ }
+
+ if ((tmp = launch_data_dict_lookup(id_plist, LAUNCH_JOBKEY_KEEPALIVE))) {
+ if ((tmp2 = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_KEEPALIVE_PATHSTATE))) {
+ copy = launch_data_copy(tmp2);
+ (void)launch_data_dict_remove(tmp, LAUNCH_JOBKEY_KEEPALIVE_PATHSTATE);
+
+ newevent = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ launch_data_dict_insert(newevent, copy, LAUNCH_JOBKEY_KEEPALIVE_PATHSTATE);
+ insert_event(id_plist, "com.apple.fsevents.matching", "com.apple.launchd." LAUNCH_JOBKEY_KEEPALIVE_PATHSTATE, newevent);
+ }
+ }
+}
+
+void
+do_mgroup_join(int fd, int family, int socktype, int protocol, const char *mgroup)
+{
+ struct addrinfo hints, *res0, *res;
+ struct ip_mreq mreq;
+ struct ipv6_mreq m6req;
+ int gerr;
+
+ memset(&hints, 0, sizeof(hints));
+
+ hints.ai_flags |= AI_PASSIVE;
+ hints.ai_family = family;
+ hints.ai_socktype = socktype;
+ hints.ai_protocol = protocol;
+
+ if ((gerr = getaddrinfo(mgroup, NULL, &hints, &res0)) != 0) {
+ launchctl_log(LOG_ERR, "getaddrinfo(): %s", gai_strerror(gerr));
+ return;
+ }
+
+ for (res = res0; res; res = res->ai_next) {
+ if (AF_INET == family) {
+ memset(&mreq, 0, sizeof(mreq));
+ mreq.imr_multiaddr = ((struct sockaddr_in *)res->ai_addr)->sin_addr;
+ if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, (socklen_t) sizeof mreq) == -1) {
+ launchctl_log(LOG_ERR, "setsockopt(IP_ADD_MEMBERSHIP): %s", strerror(errno));
+ continue;
+ }
+ break;
+ } else if (AF_INET6 == family) {
+ memset(&m6req, 0, sizeof(m6req));
+ m6req.ipv6mr_multiaddr = ((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &m6req, (socklen_t) sizeof m6req) == -1) {
+ launchctl_log(LOG_ERR, "setsockopt(IPV6_JOIN_GROUP): %s", strerror(errno));
+ continue;
+ }
+ break;
+ } else {
+ launchctl_log(LOG_ERR, "unknown family during multicast group bind!");
+ break;
+ }
+ }
+
+ freeaddrinfo(res0);
+}
+
+CFPropertyListRef
+CreateMyPropertyListFromFile(const char *posixfile)
+{
+ CFPropertyListRef propertyList;
+ CFStringRef errorString;
+ CFDataRef resourceData;
+ SInt32 errorCode;
+ CFURLRef fileURL;
+
+ fileURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)posixfile, strlen(posixfile), false);
+ if (!fileURL) {
+ launchctl_log(LOG_ERR, "%s: CFURLCreateFromFileSystemRepresentation(%s) failed", getprogname(), posixfile);
+ }
+ if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, fileURL, &resourceData, NULL, NULL, &errorCode)) {
+ launchctl_log(LOG_ERR, "%s: CFURLCreateDataAndPropertiesFromResource(%s) failed: %d", getprogname(), posixfile, (int)errorCode);
+ }
+
+ propertyList = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, resourceData, kCFPropertyListMutableContainersAndLeaves, &errorString);
+ if (fileURL) {
+ CFRelease(fileURL);
+ }
+
+ if (resourceData) {
+ CFRelease(resourceData);
+ }
+
+ return propertyList;
+}
+
+void
+WriteMyPropertyListToFile(CFPropertyListRef plist, const char *posixfile)
+{
+ CFDataRef resourceData;
+ CFURLRef fileURL;
+ SInt32 errorCode;
+
+ fileURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)posixfile, strlen(posixfile), false);
+ if (!fileURL) {
+ launchctl_log(LOG_ERR, "%s: CFURLCreateFromFileSystemRepresentation(%s) failed", getprogname(), posixfile);
+ }
+ resourceData = CFPropertyListCreateXMLData(kCFAllocatorDefault, plist);
+ if (resourceData == NULL) {
+ launchctl_log(LOG_ERR, "%s: CFPropertyListCreateXMLData(%s) failed", getprogname(), posixfile);
+ }
+ if (!CFURLWriteDataAndPropertiesToResource(fileURL, resourceData, NULL, &errorCode)) {
+ launchctl_log(LOG_ERR, "%s: CFURLWriteDataAndPropertiesToResource(%s) failed: %d", getprogname(), posixfile, (int)errorCode);
+ }
+
+ if (resourceData) {
+ CFRelease(resourceData);
+ }
+}
+
+static inline Boolean
+_is_launch_data_t(launch_data_t obj)
+{
+ Boolean result = true;
+
+ switch (launch_data_get_type(obj)) {
+ case LAUNCH_DATA_STRING : break;
+ case LAUNCH_DATA_INTEGER : break;
+ case LAUNCH_DATA_REAL : break;
+ case LAUNCH_DATA_BOOL : break;
+ case LAUNCH_DATA_ARRAY : break;
+ case LAUNCH_DATA_DICTIONARY : break;
+ case LAUNCH_DATA_FD : break;
+ case LAUNCH_DATA_MACHPORT : break;
+ default : result = false;
+ }
+
+ return result;
+}
+
+static void
+_launch_data_iterate(launch_data_t obj, const char *key, CFMutableDictionaryRef dict)
+{
+ if (obj && _is_launch_data_t(obj)) {
+ CFStringRef cfKey = CFStringCreateWithCString(NULL, key, kCFStringEncodingUTF8);
+ CFTypeRef cfVal = CFTypeCreateFromLaunchData(obj);
+
+ if (cfVal) {
+ CFDictionarySetValue(dict, cfKey, cfVal);
+ CFRelease(cfVal);
+ }
+ CFRelease(cfKey);
+ }
+}
+
+static CFTypeRef
+CFTypeCreateFromLaunchData(launch_data_t obj)
+{
+ CFTypeRef cfObj = NULL;
+
+ switch (launch_data_get_type(obj)) {
+ case LAUNCH_DATA_STRING: {
+ const char *str = launch_data_get_string(obj);
+ cfObj = CFStringCreateWithCString(NULL, str, kCFStringEncodingUTF8);
+ break;
+ }
+ case LAUNCH_DATA_INTEGER: {
+ long long integer = launch_data_get_integer(obj);
+ cfObj = CFNumberCreate(NULL, kCFNumberLongLongType, &integer);
+ break;
+ }
+ case LAUNCH_DATA_REAL: {
+ double real = launch_data_get_real(obj);
+ cfObj = CFNumberCreate(NULL, kCFNumberDoubleType, &real);
+ break;
+ }
+ case LAUNCH_DATA_BOOL: {
+ bool yesno = launch_data_get_bool(obj);
+ cfObj = yesno ? kCFBooleanTrue : kCFBooleanFalse;
+ break;
+ }
+ case LAUNCH_DATA_ARRAY: {
+ cfObj = (CFTypeRef)CFArrayCreateFromLaunchArray(obj);
+ break;
+ }
+ case LAUNCH_DATA_DICTIONARY: {
+ cfObj = (CFTypeRef)CFDictionaryCreateFromLaunchDictionary(obj);
+ break;
+ }
+ case LAUNCH_DATA_FD: {
+ int fd = launch_data_get_fd(obj);
+ cfObj = CFNumberCreate(NULL, kCFNumberIntType, &fd);
+ break;
+ }
+ case LAUNCH_DATA_MACHPORT: {
+ mach_port_t port = launch_data_get_machport(obj);
+ cfObj = CFNumberCreate(NULL, kCFNumberIntType, &port);
+ break;
+ }
+ default:
+ break;
+ }
+
+ return cfObj;
+}
+
+#pragma mark CFArray
+static CFArrayRef
+CFArrayCreateFromLaunchArray(launch_data_t arr)
+{
+ CFArrayRef result = NULL;
+ CFMutableArrayRef mutResult = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+
+ if (launch_data_get_type(arr) == LAUNCH_DATA_ARRAY) {
+ unsigned int count = launch_data_array_get_count(arr);
+ unsigned int i = 0;
+
+ for (i = 0; i < count; i++) {
+ launch_data_t launch_obj = launch_data_array_get_index(arr, i);
+ CFTypeRef obj = CFTypeCreateFromLaunchData(launch_obj);
+
+ if (obj) {
+ CFArrayAppendValue(mutResult, obj);
+ CFRelease(obj);
+ }
+ }
+
+ result = CFArrayCreateCopy(NULL, mutResult);
+ }
+
+ if (mutResult) {
+ CFRelease(mutResult);
+ }
+ return result;
+}
+
+#pragma mark CFDictionary / CFPropertyList
+static CFDictionaryRef
+CFDictionaryCreateFromLaunchDictionary(launch_data_t dict)
+{
+ CFDictionaryRef result = NULL;
+
+ if (launch_data_get_type(dict) == LAUNCH_DATA_DICTIONARY) {
+ CFMutableDictionaryRef mutResult = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+
+ launch_data_dict_iterate(dict, (void (*)(launch_data_t, const char *, void *))_launch_data_iterate, mutResult);
+
+ result = CFDictionaryCreateCopy(NULL, mutResult);
+ CFRelease(mutResult);
+ }
+
+ return result;
+}
+
+void
+myCFDictionaryApplyFunction(const void *key, const void *value, void *context)
+{
+ launch_data_t ik, iw, where = context;
+
+ ik = CF2launch_data(key);
+ iw = CF2launch_data(value);
+
+ launch_data_dict_insert(where, iw, launch_data_get_string(ik));
+ launch_data_free(ik);
+}
+
+launch_data_t
+CF2launch_data(CFTypeRef cfr)
+{
+ launch_data_t r;
+ CFTypeID cft = CFGetTypeID(cfr);
+
+ if (cft == CFStringGetTypeID()) {
+ char buf[4096];
+ CFStringGetCString(cfr, buf, sizeof(buf), kCFStringEncodingUTF8);
+ r = launch_data_alloc(LAUNCH_DATA_STRING);
+ launch_data_set_string(r, buf);
+ } else if (cft == CFBooleanGetTypeID()) {
+ r = launch_data_alloc(LAUNCH_DATA_BOOL);
+ launch_data_set_bool(r, CFBooleanGetValue(cfr));
+ } else if (cft == CFArrayGetTypeID()) {
+ CFIndex i, ac = CFArrayGetCount(cfr);
+ r = launch_data_alloc(LAUNCH_DATA_ARRAY);
+ for (i = 0; i < ac; i++) {
+ CFTypeRef v = CFArrayGetValueAtIndex(cfr, i);
+ if (v) {
+ launch_data_t iv = CF2launch_data(v);
+ launch_data_array_set_index(r, iv, i);
+ }
+ }
+ } else if (cft == CFDictionaryGetTypeID()) {
+ r = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ CFDictionaryApplyFunction(cfr, myCFDictionaryApplyFunction, r);
+ } else if (cft == CFDataGetTypeID()) {
+ r = launch_data_alloc(LAUNCH_DATA_OPAQUE);
+ launch_data_set_opaque(r, CFDataGetBytePtr(cfr), CFDataGetLength(cfr));
+ } else if (cft == CFNumberGetTypeID()) {
+ long long n;
+ double d;
+ CFNumberType cfnt = CFNumberGetType(cfr);
+ switch (cfnt) {
+ case kCFNumberSInt8Type:
+ case kCFNumberSInt16Type:
+ case kCFNumberSInt32Type:
+ case kCFNumberSInt64Type:
+ case kCFNumberCharType:
+ case kCFNumberShortType:
+ case kCFNumberIntType:
+ case kCFNumberLongType:
+ case kCFNumberLongLongType:
+ CFNumberGetValue(cfr, kCFNumberLongLongType, &n);
+ r = launch_data_alloc(LAUNCH_DATA_INTEGER);
+ launch_data_set_integer(r, n);
+ break;
+ case kCFNumberFloat32Type:
+ case kCFNumberFloat64Type:
+ case kCFNumberFloatType:
+ case kCFNumberDoubleType:
+ CFNumberGetValue(cfr, kCFNumberDoubleType, &d);
+ r = launch_data_alloc(LAUNCH_DATA_REAL);
+ launch_data_set_real(r, d);
+ break;
+ default:
+ r = NULL;
+ break;
+ }
+ } else {
+ r = NULL;
+ }
+ return r;
+}
+
+int
+help_cmd(int argc, char *const argv[])
+{
+ size_t i, l, cmdwidth = 0;
+
+ int level = LOG_NOTICE;
+ if (argc == 0 || argv == NULL) {
+ level = LOG_ERR;
+ }
+
+ launchctl_log(level, "usage: %s <subcommand>", getprogname());
+
+ for (i = 0; i < (sizeof cmds / sizeof cmds[0]); i++) {
+ l = strlen(cmds[i].name);
+ if (l > cmdwidth) {
+ cmdwidth = l;
+ }
+ }
+
+ for (i = 0; i < (sizeof cmds / sizeof cmds[0]); i++) {
+ launchctl_log(level, "\t%-*s\t%s", (int)cmdwidth, cmds[i].name, cmds[i].desc);
+ }
+
+ return 0;
+}
+
+int
+exit_cmd(int argc __attribute__((unused)), char *const argv[] __attribute__((unused)))
+{
+ exit(0);
+}
+
+int
+_fd(int fd)
+{
+ if (fd >= 0)
+ fcntl(fd, F_SETFD, 1);
+ return fd;
+}
+
+void
+do_single_user_mode(bool sflag)
+{
+ if (sflag) {
+ while (!do_single_user_mode2()) {
+ sleep(1);
+ }
+ }
+}
+
+bool
+do_single_user_mode2(void)
+{
+ bool runcom_fsck = true; /* should_fsck(); */
+ int wstatus;
+ int fd;
+ pid_t p;
+
+ switch ((p = fork())) {
+ case -1:
+ syslog(LOG_ERR, "can't fork single-user shell, trying again: %m");
+ return false;
+ case 0:
+ break;
+ default:
+ (void)osx_assumes_zero(waitpid(p, &wstatus, 0));
+ if (WIFEXITED(wstatus)) {
+ if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
+ return true;
+ } else {
+ launchctl_log(LOG_NOTICE, "single user mode: exit status: %d", WEXITSTATUS(wstatus));
+ }
+ } else {
+ launchctl_log(LOG_NOTICE, "single user mode shell: %s", strsignal(WTERMSIG(wstatus)));
+ }
+ return false;
+ }
+
+ revoke(_PATH_CONSOLE);
+ if (posix_assumes_zero((fd = open(_PATH_CONSOLE, O_RDWR))) == -1) {
+ _exit(EXIT_FAILURE);
+ }
+ if (posix_assumes_zero(login_tty(fd)) == -1) {
+ _exit(EXIT_FAILURE);
+ }
+
+ mach_timespec_t wt = { 5, 0 };
+ IOKitWaitQuiet(kIOMasterPortDefault, &wt); /* This will hopefully return after all the kexts have shut up. */
+
+ setenv("TERM", "vt100", 1);
+ if (runcom_fsck) {
+ fprintf(stdout, "Singleuser boot -- fsck not done\n");
+ fprintf(stdout, "Root device is mounted read-only\n");
+ fprintf(stdout, "If you want to make modifications to files:\n");
+ fprintf(stdout, "\t/sbin/fsck -fy\n\t/sbin/mount -uw /\n");
+ fprintf(stdout, "If you wish to boot the system:\n");
+ fprintf(stdout, "\texit\n");
+ fflush(stdout);
+ }
+
+ execl(_PATH_BSHELL, "-sh", NULL);
+ fprintf(stderr, "can't exec %s for single user: %m\n", _PATH_BSHELL);
+ _exit(EXIT_FAILURE);
+}
+
+void
+do_crash_debug_mode(void)
+{
+ while (!do_crash_debug_mode2()) {
+ sleep(1);
+ }
+}
+
+bool
+do_crash_debug_mode2(void)
+{
+ int wstatus;
+ int fd;
+ pid_t p;
+
+ switch ((p = fork())) {
+ case -1:
+ syslog(LOG_ERR, "can't fork crash debug shell, trying again: %m");
+ return false;
+ case 0:
+ break;
+ default:
+ (void)osx_assumes_zero(waitpid(p, &wstatus, 0));
+ if (WIFEXITED(wstatus)) {
+ if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
+ return true;
+ } else {
+ launchctl_log(LOG_NOTICE, "crash debug mode: exit status: %d", WEXITSTATUS(wstatus));
+ }
+ } else {
+ launchctl_log(LOG_NOTICE, "crash debug mode shell: %s", strsignal(WTERMSIG(wstatus)));
+ }
+ return false;
+ }
+
+ revoke(_PATH_CONSOLE);
+ if (posix_assumes_zero((fd = open(_PATH_CONSOLE, O_RDWR))) == -1) {
+ _exit(EXIT_FAILURE);
+ }
+ if (posix_assumes_zero(login_tty(fd)) == -1) {
+ _exit(EXIT_FAILURE);
+ }
+
+ /* The idea is to wait until all the kexts have quiesced to prevent a bunch
+ * of log messages from being slammed onto the console prompt. It mostly
+ * works.
+ */
+ mach_timespec_t wt = { 5, 0 };
+ IOKitWaitQuiet(kIOMasterPortDefault, &wt);
+
+ setenv("TERM", "vt100", 1);
+ fprintf(stdout, "Entering boot-time debugging mode...\n");
+ fprintf(stdout, "The system bootstrapper process has crashed. To debug:\n");
+ fprintf(stdout, "\tgdb attach %i\n", getppid());
+ fprintf(stdout, "You can try booting the system with:\n");
+ fprintf(stdout, "\tlaunchctl load -S System -D All\n");
+
+ execl(_PATH_BSHELL, "-sh", NULL);
+ fprintf(stderr, "can't exec %s for crash debug: %m\n", _PATH_BSHELL);
+ _exit(EXIT_FAILURE);
+}
+
+static void
+exit_at_sigterm(int sig)
+{
+ if (sig == SIGTERM) {
+ _exit(EXIT_SUCCESS);
+ }
+}
+
+void
+fatal_signal_handler(int sig __attribute__((unused)), siginfo_t *si __attribute__((unused)), void *uap __attribute__((unused)))
+{
+ do_crash_debug_mode();
+}
+
+void
+handle_system_bootstrapper_crashes_separately(void)
+{
+ if (!_launchctl_startup_debugging) {
+ return;
+ }
+
+ fprintf(stdout, "com.apple.launchctl.System\t\t\t*** Handling system bootstrapper crashes separately. ***\n");
+ struct sigaction fsa;
+
+ fsa.sa_sigaction = fatal_signal_handler;
+ fsa.sa_flags = SA_SIGINFO;
+ sigemptyset(&fsa.sa_mask);
+
+ (void)posix_assumes_zero(sigaction(SIGILL, &fsa, NULL));
+ (void)posix_assumes_zero(sigaction(SIGFPE, &fsa, NULL));
+ (void)posix_assumes_zero(sigaction(SIGBUS, &fsa, NULL));
+ (void)posix_assumes_zero(sigaction(SIGTRAP, &fsa, NULL));
+ (void)posix_assumes_zero(sigaction(SIGABRT, &fsa, NULL));
+}
+
+static void
+system_specific_bootstrap(bool sflag)
+{
+ int hnmib[] = { CTL_KERN, KERN_HOSTNAME };
+ struct kevent kev;
+ int kq;
+#if HAVE_LIBAUDITD
+ launch_data_t lda, ldb;
+#endif
+
+ handle_system_bootstrapper_crashes_separately();
+
+ // Disable Libinfo lookups to mdns and ds while bootstrapping (8698260)
+ si_search_module_set_flags("mdns", 1);
+ si_search_module_set_flags("ds", 1);
+
+ /* rc.cdrom's hack to load the system means that we're not the real system
+ * bootstrapper. So we set this environment variable, and if the real
+ * bootstrapper detects it, it will disable lookups to mDNSResponder and
+ * opendirectoryd to prevent deadlocks at boot.
+ *
+ * See <rdar://problem/9877230>.
+ */
+ (void)setenv(LAUNCH_ENV_BOOTSTRAPPINGSYSTEM, "1", 1);
+
+ do_sysversion_sysctl();
+
+ do_single_user_mode(sflag);
+
+ (void)posix_assumes_zero(kq = kqueue());
+ EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, 60, 0);
+ (void)posix_assumes_zero(kevent(kq, &kev, 1, NULL, 0, NULL));
+
+ __OSX_COMPILETIME_ASSERT__(SIG_ERR == (typeof(SIG_ERR))-1);
+ EV_SET(&kev, SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, 0);
+ (void)posix_assumes_zero(kevent(kq, &kev, 1, NULL, 0, NULL));
+ (void)posix_assumes_zero(signal(SIGTERM, SIG_IGN));
+ (void)posix_assumes_zero(sysctl(hnmib, 2, NULL, NULL, "localhost", sizeof("localhost")));
+
+ loopback_setup_ipv4();
+ loopback_setup_ipv6();
+
+ apply_sysctls_from_file("/etc/sysctl.conf");
+
+#if TARGET_OS_EMBEDDED
+ if (path_check("/etc/rc.boot")) {
+ const char *rcboot_tool[] = { "/etc/rc.boot", NULL };
+
+ (void)posix_assumes_zero(signal(SIGTERM, exit_at_sigterm));
+ (void)posix_assumes_zero(fwexec(rcboot_tool, NULL));
+ }
+#endif
+
+ if (path_check("/etc/rc.cdrom")) {
+ const char *rccdrom_tool[] = { _PATH_BSHELL, "/etc/rc.cdrom", "multiuser", NULL };
+
+ /* The bootstrapper should always be killable during install-time. This
+ * is a special case for /etc/rc.cdrom, which runs a process and never
+ * exits.
+ *
+ * <rdar://problem/6103485>
+ */
+ (void)posix_assumes_zero(signal(SIGTERM, exit_at_sigterm));
+ (void)posix_assumes_zero(fwexec(rccdrom_tool, NULL));
+ (void)reboot(RB_HALT);
+ _exit(EXIT_FAILURE);
+ } else if (is_netboot()) {
+ const char *rcnetboot_tool[] = { _PATH_BSHELL, "/etc/rc.netboot", "init", NULL };
+ if (posix_assumes_zero(fwexec(rcnetboot_tool, NULL)) == -1) {
+ (void)reboot(RB_HALT);
+ _exit(EXIT_FAILURE);
+ }
+ } else {
+ do_potential_fsck();
+ }
+
+#if TARGET_OS_EMBEDDED
+ if (path_check("/usr/libexec/FinishRestoreFromBackup")) {
+ const char *finish_restore[] = { "/usr/libexec/FinishRestoreFromBackup", NULL };
+ if (fwexec(finish_restore, NULL) == -1) {
+ launchctl_log(LOG_ERR, "Couldn't finish restore: %d: %s", errno, strerror(errno));
+ (void)reboot(RB_HALT);
+
+ _exit(EXIT_FAILURE);
+ }
+ }
+#endif
+
+ if (path_check("/usr/libexec/cc_fips_test")) {
+ const char *fips_tool[] = { "/usr/libexec/cc_fips_test", "-P", NULL };
+ if (fwexec(fips_tool, NULL) == -1) {
+ launchctl_log(LOG_ERR, "FIPS self check failure: %d: %s", errno, strerror(errno));
+ (void)reboot(RB_HALT);
+
+ _exit(EXIT_FAILURE);
+ }
+ }
+
+ if (path_check("/etc/rc.server")) {
+ const char *rcserver_tool[] = { _PATH_BSHELL, "/etc/rc.server", NULL };
+ (void)posix_assumes_zero(fwexec(rcserver_tool, NULL));
+ }
+
+ read_launchd_conf();
+
+ if (path_check("/var/account/acct")) {
+ (void)posix_assumes_zero(acct("/var/account/acct"));
+ }
+
+#if !TARGET_OS_EMBEDDED
+ if (path_check("/etc/fstab")) {
+ const char *mount_tool[] = { "mount", "-vat", "nonfs", NULL };
+ (void)posix_assumes_zero(fwexec(mount_tool, NULL));
+ }
+#endif
+
+ if (path_check("/etc/rc.installer_cleanup")) {
+ const char *rccleanup_tool[] = { _PATH_BSHELL, "/etc/rc.installer_cleanup", "multiuser", NULL };
+ (void)posix_assumes_zero(fwexec(rccleanup_tool, NULL));
+ }
+
+ if (path_check("/etc/rc.deferred_install")) {
+ int status = 0;
+ const char *deferredinstall_tool[] = { _PATH_BSHELL, "/etc/rc.deferred_install", NULL };
+ if (posix_assumes_zero(fwexec(deferredinstall_tool, &status)) == 0) {
+ if (WEXITSTATUS(status) == EXIT_SUCCESS) {
+ if (_launchctl_apple_internal) {
+ launchctl_log(LOG_NOTICE, "Deferred install script completed successfully. Rebooting in 3 seconds...");
+ sleep(3);
+ }
+
+ (void)remove(deferredinstall_tool[1]);
+ (void)reboot(RB_AUTOBOOT);
+ exit(EXIT_FAILURE);
+ } else {
+ launchctl_log(LOG_NOTICE, "Deferred install script exited with status %i. Continuing boot and hoping it'll work...", WEXITSTATUS(status));
+ (void)remove(deferredinstall_tool[1]);
+ }
+ }
+ }
+
+ empty_dir(_PATH_VARRUN, NULL);
+ empty_dir(_PATH_TMP, NULL);
+ (void)remove(_PATH_NOLOGIN);
+
+ if (path_check("/usr/libexec/dirhelper")) {
+ const char *dirhelper_tool[] = { "/usr/libexec/dirhelper", "-machineBoot", NULL };
+ (void)posix_assumes_zero(fwexec(dirhelper_tool, NULL));
+ }
+
+ (void)posix_assumes_zero(touch_file(_PATH_UTMPX, DEFFILEMODE));
+#if !TARGET_OS_EMBEDDED
+ (void)posix_assumes_zero(touch_file(_PATH_VARRUN "/.systemStarterRunning", DEFFILEMODE));
+#endif
+
+#if HAVE_LIBAUDITD
+ /* Only start auditing if not "Disabled" in auditd plist. */
+ if ((lda = read_plist_file(AUDITD_PLIST_FILE, false, false)) != NULL && ((ldb = launch_data_dict_lookup(lda, LAUNCH_JOBKEY_DISABLED)) == NULL || job_disabled_logic(ldb) == false)) {
+ (void)osx_assumes_zero(audit_quick_start());
+ launch_data_free(lda);
+ }
+#else
+ if (path_check("/etc/security/rc.audit")) {
+ const char *audit_tool[] = { _PATH_BSHELL, "/etc/security/rc.audit", NULL };
+ (void)posix_assumes_zero(fwexec(audit_tool, NULL));
+ }
+#endif
+
+ do_BootCache_magic(BOOTCACHE_START);
+
+ preheat_page_cache_hack();
+
+ _vproc_set_global_on_demand(true);
+
+ char *load_launchd_items[] = { "load", "-D", "all", NULL };
+ int load_launchd_items_cnt = 3;
+
+ if (is_safeboot()) {
+ load_launchd_items[2] = "system";
+ }
+
+ (void)posix_assumes_zero(load_and_unload_cmd(load_launchd_items_cnt, load_launchd_items));
+
+ /* See <rdar://problem/5066316>. */
+ if (!_launchctl_apple_internal) {
+ mach_timespec_t w = { 5, 0 };
+ IOKitWaitQuiet(kIOMasterPortDefault, &w);
+ }
+
+ do_BootCache_magic(BOOTCACHE_TAG);
+
+ do_bootroot_magic();
+
+ _vproc_set_global_on_demand(false);
+
+ (void)posix_assumes_zero(kevent(kq, NULL, 0, &kev, 1, NULL));
+
+ /* warmd now handles cutting off the BootCache. We just kick it off. */
+ (void)close(kq);
+}
+
+void
+do_BootCache_magic(BootCache_action_t what)
+{
+ const char *bcc_tool[] = { "/usr/sbin/BootCacheControl", NULL, NULL };
+
+ if (is_safeboot() || !path_check(bcc_tool[0])) {
+ return;
+ }
+
+ switch (what) {
+ case BOOTCACHE_START:
+ bcc_tool[1] = "start";
+ break;
+ case BOOTCACHE_TAG:
+ bcc_tool[1] = "tag";
+ break;
+ case BOOTCACHE_STOP:
+ bcc_tool[1] = "stop";
+ break;
+ }
+
+ fwexec(bcc_tool, NULL);
+}
+
+int
+bootstrap_cmd(int argc, char *const argv[])
+{
+ char *session = NULL;
+ bool sflag = false;
+ int ch;
+
+ while ((ch = getopt(argc, argv, "sS:")) != -1) {
+ switch (ch) {
+ case 's':
+ sflag = true;
+ break;
+ case 'S':
+ session = optarg;
+ break;
+ case '?':
+ default:
+ break;
+ }
+ }
+
+ optind = 1;
+ optreset = 1;
+
+ if (!session) {
+ launchctl_log(LOG_ERR, "usage: %s bootstrap [-s] -S <session-type>", getprogname());
+ return 1;
+ }
+
+ if (strcasecmp(session, "System") == 0) {
+ _launchctl_system_bootstrap = true;
+ system_specific_bootstrap(sflag);
+ } else {
+ char *load_launchd_items[] = {
+ "load",
+ "-S",
+ session,
+ "-D",
+ "all",
+ NULL,
+ NULL,
+ NULL,
+ };
+ size_t the_argc = 5;
+
+ bool bootstrap_login_items = false;
+ if (strcasecmp(session, VPROCMGR_SESSION_AQUA) == 0) {
+ bootstrap_login_items = true;
+ } else if (strcasecmp(session, VPROCMGR_SESSION_BACKGROUND) == 0
+ || strcasecmp(session, VPROCMGR_SESSION_LOGINWINDOW) == 0) {
+ /* If we're bootstrapping either the LoginWindow or Background
+ * sessions, then we only load items from /System and /Library. We
+ * do not attempt to load anything from a user's home directory, as
+ * it might not be available at this time.
+ */
+ load_launchd_items[4] = "system";
+ if (!is_safeboot()) {
+ load_launchd_items[5] = "-D";
+ load_launchd_items[6] = "local";
+ the_argc += 2;
+ }
+
+ if (strcasecmp(session, VPROCMGR_SESSION_BACKGROUND) == 0) {
+ /* This is to force a bootstrapped job to inherit its security
+ * session from the launchd that it resides in.
+ */
+ _launchctl_peruser_bootstrap = true;
+ read_launchd_conf();
+ }
+ }
+
+ if (is_safeboot()) {
+ load_launchd_items[4] = "system";
+ }
+
+ int result = load_and_unload_cmd(the_argc, load_launchd_items);
+ if (result) {
+ syslog(LOG_ERR, "Could not bootstrap session: %s", session);
+ return 1;
+ }
+
+ /* This will tell launchd to start listening on MachServices again. When
+ * bootstrapping, launchd ignores requests from everyone but the
+ * bootstrapper (us), so this unsets the "weird bootstrap" mode.
+ */
+ int64_t junk = 0;
+ vproc_err_t verr = vproc_swap_integer(NULL, VPROC_GSK_WEIRD_BOOTSTRAP, &junk, NULL);
+ if (!verr) {
+#if !TARGET_OS_EMBEDDED
+ if (bootstrap_login_items) {
+ void *smf = dlopen("/System/Library/Frameworks/ServiceManagement.framework/Versions/A/ServiceManagement", 0);
+ if (smf) {
+ void (*_SMLoginItemBootstrapItemsFunc)(void) = dlsym(smf, "_SMLoginItemBootstrapItems");
+ if (_SMLoginItemBootstrapItemsFunc) {
+ _SMLoginItemBootstrapItemsFunc();
+ } else {
+ launchctl_log(LOG_ERR, "Could not find login item bootstrap function. LoginItems will be unavailable.");
+ }
+ } else {
+ launchctl_log(LOG_ERR, "Failed to open ServiceManagement framework. LoginItems will be unavailable.");
+ }
+ }
+#endif
+ } else if (bootstrap_login_items) {
+ launchctl_log(LOG_ERR, "Failed to unset weird bootstrap. LoginItems will be unavailable.");
+ }
+ }
+
+ return 0;
+}
+
+int
+load_and_unload_cmd(int argc, char *const argv[])
+{
+ NSSearchPathEnumerationState es = 0;
+ char nspath[PATH_MAX * 2]; /* safe side, we need to append */
+ bool badopts = false;
+ struct load_unload_state lus;
+ size_t i;
+ int ch;
+
+ memset(&lus, 0, sizeof(lus));
+
+ if (strcmp(argv[0], "load") == 0) {
+ lus.load = true;
+ }
+
+ while ((ch = getopt(argc, argv, "wFS:D:")) != -1) {
+ switch (ch) {
+ case 'w':
+ lus.editondisk = true;
+ break;
+ case 'F':
+ lus.forceload = true;
+ break;
+ case 'S':
+ lus.session_type = optarg;
+ break;
+ case 'D':
+ if (strcasecmp(optarg, "all") == 0) {
+ es |= NSAllDomainsMask;
+ } else if (strcasecmp(optarg, "user") == 0) {
+ es |= NSUserDomainMask;
+ } else if (strcasecmp(optarg, "local") == 0) {
+ es |= NSLocalDomainMask;
+ } else if (strcasecmp(optarg, "network") == 0) {
+ es |= NSNetworkDomainMask;
+ } else if (strcasecmp(optarg, "system") == 0) {
+ es |= NSSystemDomainMask;
+ } else {
+ badopts = true;
+ }
+ break;
+ case '?':
+ default:
+ badopts = true;
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (lus.session_type == NULL) {
+ es &= ~NSUserDomainMask;
+ }
+
+ if (argc == 0 && es == 0) {
+ badopts = true;
+ }
+
+ if (badopts) {
+ launchctl_log(LOG_ERR, "usage: %s load [-wF] [-D <user|local|network|system|all>] paths...", getprogname());
+ return 1;
+ }
+
+ int dbfd = -1;
+ vproc_err_t verr = vproc_swap_string(NULL, VPROC_GSK_JOB_OVERRIDES_DB, NULL, &_launchctl_job_overrides_db_path);
+ if (verr) {
+ if (bootstrap_port) {
+ launchctl_log(LOG_ERR, "Could not get location of job overrides database: ppid/bootstrap: %d/0x%x", getppid(), bootstrap_port);
+ }
+ } else {
+ dbfd = open(_launchctl_job_overrides_db_path, O_RDONLY | O_EXLOCK | O_CREAT, S_IRUSR | S_IWUSR);
+ if (dbfd != -1) {
+ _launchctl_overrides_db = (CFMutableDictionaryRef)CreateMyPropertyListFromFile(_launchctl_job_overrides_db_path);
+ if (!_launchctl_overrides_db) {
+ _launchctl_overrides_db = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ }
+ } else if (errno != EROFS) {
+ launchctl_log(LOG_ERR, "Could not open job overrides database at: %s: %d: %s", _launchctl_job_overrides_db_path, errno, strerror(errno));
+ }
+ }
+
+ /* Only one pass! */
+ lus.pass1 = launch_data_alloc(LAUNCH_DATA_ARRAY);
+
+ es = NSStartSearchPathEnumeration(NSLibraryDirectory, es);
+
+ while ((es = NSGetNextSearchPathEnumeration(es, nspath))) {
+ glob_t g;
+
+ if (lus.session_type) {
+ strcat(nspath, "/LaunchAgents");
+ } else {
+ strcat(nspath, "/LaunchDaemons");
+ }
+
+ if (glob(nspath, GLOB_TILDE|GLOB_NOSORT, NULL, &g) == 0) {
+ for (i = 0; i < g.gl_pathc; i++) {
+ readpath(g.gl_pathv[i], &lus);
+ }
+ globfree(&g);
+ }
+ }
+
+ for (i = 0; i < (size_t)argc; i++) {
+ readpath(argv[i], &lus);
+ }
+
+ if (launch_data_array_get_count(lus.pass1) == 0) {
+ if (!_launchctl_is_managed) {
+ launchctl_log(LOG_ERR, "nothing found to %s", lus.load ? "load" : "unload");
+ }
+ launch_data_free(lus.pass1);
+ return _launchctl_is_managed ? 0 : 1;
+ }
+
+ if (lus.load) {
+ distill_jobs(lus.pass1);
+ submit_job_pass(lus.pass1);
+ } else {
+ for (i = 0; i < launch_data_array_get_count(lus.pass1); i++) {
+ unloadjob(launch_data_array_get_index(lus.pass1, i));
+ }
+ }
+
+ if (_launchctl_overrides_db_changed) {
+ WriteMyPropertyListToFile(_launchctl_overrides_db, _launchctl_job_overrides_db_path);
+ }
+
+ flock(dbfd, LOCK_UN);
+ close(dbfd);
+ return 0;
+}
+
+void
+submit_job_pass(launch_data_t jobs)
+{
+ launch_data_t msg, resp;
+ size_t i;
+ int e;
+
+ if (launch_data_array_get_count(jobs) == 0)
+ return;
+
+ msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+
+ launch_data_dict_insert(msg, jobs, LAUNCH_KEY_SUBMITJOB);
+
+ resp = launch_msg(msg);
+
+ if (resp) {
+ switch (launch_data_get_type(resp)) {
+ case LAUNCH_DATA_ERRNO:
+ if ((e = launch_data_get_errno(resp)))
+ launchctl_log(LOG_ERR, "%s", strerror(e));
+ break;
+ case LAUNCH_DATA_ARRAY:
+ for (i = 0; i < launch_data_array_get_count(jobs); i++) {
+ launch_data_t obatind = launch_data_array_get_index(resp, i);
+ launch_data_t jatind = launch_data_array_get_index(jobs, i);
+ const char *lab4job = launch_data_get_string(launch_data_dict_lookup(jatind, LAUNCH_JOBKEY_LABEL));
+ if (LAUNCH_DATA_ERRNO == launch_data_get_type(obatind)) {
+ e = launch_data_get_errno(obatind);
+ switch (e) {
+ case EEXIST:
+ launchctl_log(LOG_ERR, "%s: %s", lab4job, "Already loaded");
+ break;
+ case ESRCH:
+ launchctl_log(LOG_ERR, "%s: %s", lab4job, "Not loaded");
+ break;
+ case ENEEDAUTH:
+ launchctl_log(LOG_ERR, "%s: %s", lab4job, "Could not set security session");
+ default:
+ launchctl_log(LOG_ERR, "%s: %s", lab4job, strerror(e));
+ case 0:
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ launchctl_log(LOG_ERR, "unknown respose from launchd!");
+ break;
+ }
+ launch_data_free(resp);
+ } else {
+ launchctl_log(LOG_ERR, "launch_msg(): %s", strerror(errno));
+ }
+
+ launch_data_free(msg);
+}
+
+int
+start_stop_remove_cmd(int argc, char *const argv[])
+{
+ launch_data_t resp, msg;
+ const char *lmsgcmd = LAUNCH_KEY_STOPJOB;
+ int e, r = 0;
+
+ if (0 == strcmp(argv[0], "start"))
+ lmsgcmd = LAUNCH_KEY_STARTJOB;
+
+ if (0 == strcmp(argv[0], "remove"))
+ lmsgcmd = LAUNCH_KEY_REMOVEJOB;
+
+ if (argc != 2) {
+ launchctl_log(LOG_ERR, "usage: %s %s <job label>", getprogname(), argv[0]);
+ return 1;
+ }
+
+ msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ launch_data_dict_insert(msg, launch_data_new_string(argv[1]), lmsgcmd);
+
+ resp = launch_msg(msg);
+ launch_data_free(msg);
+
+ if (resp == NULL) {
+ launchctl_log(LOG_ERR, "launch_msg(): %s", strerror(errno));
+ return 1;
+ } else if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO) {
+ if ((e = launch_data_get_errno(resp))) {
+ launchctl_log(LOG_ERR, "%s %s error: %s", getprogname(), argv[0], strerror(e));
+ r = 1;
+ }
+ } else {
+ launchctl_log(LOG_ERR, "%s %s returned unknown response", getprogname(), argv[0]);
+ r = 1;
+ }
+
+ launch_data_free(resp);
+ return r;
+}
+
+void
+print_jobs(launch_data_t j, const char *key __attribute__((unused)), void *context __attribute__((unused)))
+{
+ static size_t depth = 0;
+ launch_data_t lo = launch_data_dict_lookup(j, LAUNCH_JOBKEY_LABEL);
+ launch_data_t pido = launch_data_dict_lookup(j, LAUNCH_JOBKEY_PID);
+ launch_data_t stato = launch_data_dict_lookup(j, LAUNCH_JOBKEY_LASTEXITSTATUS);
+ const char *label = launch_data_get_string(lo);
+ size_t i;
+
+ if (pido) {
+ fprintf(stdout, "%lld\t-\t%s\n", launch_data_get_integer(pido), label);
+ } else if (stato) {
+ int wstatus = (int)launch_data_get_integer(stato);
+ if (WIFEXITED(wstatus)) {
+ fprintf(stdout, "-\t%d\t%s\n", WEXITSTATUS(wstatus), label);
+ } else if (WIFSIGNALED(wstatus)) {
+ fprintf(stdout, "-\t-%d\t%s\n", WTERMSIG(wstatus), label);
+ } else {
+ fprintf(stdout, "-\t???\t%s\n", label);
+ }
+ } else {
+ fprintf(stdout, "-\t-\t%s\n", label);
+ }
+ for (i = 0; i < depth; i++) {
+ fprintf(stdout, "\t");
+ }
+}
+
+void
+print_obj(launch_data_t obj, const char *key, void *context __attribute__((unused)))
+{
+ static size_t indent = 0;
+ size_t i, c;
+
+ for (i = 0; i < indent; i++) {
+ fprintf(stdout, "\t");
+ }
+
+ if (key) {
+ fprintf(stdout, "\"%s\" = ", key);
+ }
+
+ switch (launch_data_get_type(obj)) {
+ case LAUNCH_DATA_STRING:
+ fprintf(stdout, "\"%s\";\n", launch_data_get_string(obj));
+ break;
+ case LAUNCH_DATA_INTEGER:
+ fprintf(stdout, "%lld;\n", launch_data_get_integer(obj));
+ break;
+ case LAUNCH_DATA_REAL:
+ fprintf(stdout, "%f;\n", launch_data_get_real(obj));
+ break;
+ case LAUNCH_DATA_BOOL:
+ fprintf(stdout, "%s;\n", launch_data_get_bool(obj) ? "true" : "false");
+ break;
+ case LAUNCH_DATA_ARRAY:
+ c = launch_data_array_get_count(obj);
+ fprintf(stdout, "(\n");
+ indent++;
+ for (i = 0; i < c; i++) {
+ print_obj(launch_data_array_get_index(obj, i), NULL, NULL);
+ }
+ indent--;
+ for (i = 0; i < indent; i++) {
+ fprintf(stdout, "\t");
+ }
+ fprintf(stdout, ");\n");
+ break;
+ case LAUNCH_DATA_DICTIONARY:
+ fprintf(stdout, "{\n");
+ indent++;
+ launch_data_dict_iterate(obj, print_obj, NULL);
+ indent--;
+ for (i = 0; i < indent; i++) {
+ fprintf(stdout, "\t");
+ }
+ fprintf(stdout, "};\n");
+ break;
+ case LAUNCH_DATA_FD:
+ fprintf(stdout, "file-descriptor-object;\n");
+ break;
+ case LAUNCH_DATA_MACHPORT:
+ fprintf(stdout, "mach-port-object;\n");
+ break;
+ default:
+ fprintf(stdout, "???;\n");
+ break;
+ }
+}
+
+int
+list_cmd(int argc, char *const argv[])
+{
+ if (_launchctl_is_managed) {
+ /* This output is meant for a command line, so don't print anything if
+ * we're managed by launchd.
+ */
+ return 1;
+ }
+
+ launch_data_t resp, msg = NULL;
+ int r = 0;
+
+ bool plist_output = false;
+ char *label = NULL;
+ if (argc > 3) {
+ launchctl_log(LOG_ERR, "usage: %s list [-x] [label]", getprogname());
+ return 1;
+ } else if (argc >= 2) {
+ plist_output = (strncmp(argv[1], "-x", sizeof("-x")) == 0);
+ label = plist_output ? argv[2] : argv[1];
+ }
+
+ if (label) {
+ msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ launch_data_dict_insert(msg, launch_data_new_string(label), LAUNCH_KEY_GETJOB);
+
+ resp = launch_msg(msg);
+ launch_data_free(msg);
+
+ if (resp == NULL) {
+ launchctl_log(LOG_ERR, "launch_msg(): %s", strerror(errno));
+ r = 1;
+ } else if (launch_data_get_type(resp) == LAUNCH_DATA_DICTIONARY) {
+ if (plist_output) {
+ CFDictionaryRef respDict = CFDictionaryCreateFromLaunchDictionary(resp);
+ CFStringRef plistStr = NULL;
+ if (respDict) {
+ CFDataRef plistData = CFPropertyListCreateXMLData(NULL, (CFPropertyListRef)respDict);
+ CFRelease(respDict);
+ if (plistData) {
+ plistStr = CFStringCreateWithBytes(NULL, CFDataGetBytePtr(plistData), CFDataGetLength(plistData), kCFStringEncodingUTF8, false);
+ CFRelease(plistData);
+ } else {
+ r = 1;
+ }
+ } else {
+ r = 1;
+ }
+
+ if (plistStr) {
+ launchctl_log_CFString(LOG_NOTICE, plistStr);
+ CFRelease(plistStr);
+ r = 0;
+ }
+ } else {
+ print_obj(resp, NULL, NULL);
+ r = 0;
+ }
+ launch_data_free(resp);
+ } else {
+ launchctl_log(LOG_ERR, "%s %s returned unknown response", getprogname(), argv[0]);
+ r = 1;
+ launch_data_free(resp);
+ }
+ } else if (vproc_swap_complex(NULL, VPROC_GSK_ALLJOBS, NULL, &resp) == NULL) {
+ fprintf(stdout, "PID\tStatus\tLabel\n");
+ launch_data_dict_iterate(resp, print_jobs, NULL);
+ launch_data_free(resp);
+
+ r = 0;
+ }
+
+ return r;
+}
+
+int
+stdio_cmd(int argc __attribute__((unused)), char *const argv[])
+{
+ launchctl_log(LOG_ERR, "%s %s: This sub-command no longer does anything", getprogname(), argv[0]);
+ return 1;
+}
+
+int
+fyi_cmd(int argc, char *const argv[])
+{
+ launch_data_t resp, msg;
+ const char *lmsgk = NULL;
+ int e, r = 0;
+
+ if (argc != 1) {
+ launchctl_log(LOG_ERR, "usage: %s %s", getprogname(), argv[0]);
+ return 1;
+ }
+
+ if (!strcmp(argv[0], "shutdown")) {
+ lmsgk = LAUNCH_KEY_SHUTDOWN;
+ } else if (!strcmp(argv[0], "singleuser")) {
+ lmsgk = LAUNCH_KEY_SINGLEUSER;
+ } else {
+ return 1;
+ }
+
+ msg = launch_data_new_string(lmsgk);
+ resp = launch_msg(msg);
+ launch_data_free(msg);
+
+ if (resp == NULL) {
+ launchctl_log(LOG_ERR, "launch_msg(): %s", strerror(errno));
+ return 1;
+ } else if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO) {
+ if ((e = launch_data_get_errno(resp))) {
+ launchctl_log(LOG_ERR, "%s %s error: %s", getprogname(), argv[0], strerror(e));
+ r = 1;
+ }
+ } else {
+ launchctl_log(LOG_ERR, "%s %s returned unknown response", getprogname(), argv[0]);
+ r = 1;
+ }
+
+ launch_data_free(resp);
+
+ return r;
+}
+
+int
+logupdate_cmd(int argc, char *const argv[])
+{
+ int64_t inval, outval;
+ bool badargs = false, maskmode = false, onlymode = false, levelmode = false;
+ static const struct {
+ const char *name;
+ int level;
+ } logtbl[] = {
+ { "debug", LOG_DEBUG },
+ { "info", LOG_INFO },
+ { "notice", LOG_NOTICE },
+ { "warning", LOG_WARNING },
+ { "error", LOG_ERR },
+ { "critical", LOG_CRIT },
+ { "alert", LOG_ALERT },
+ { "emergency", LOG_EMERG },
+ };
+ size_t i, j, logtblsz = sizeof logtbl / sizeof logtbl[0];
+ int m = 0;
+
+ if (argc >= 2) {
+ if (!strcmp(argv[1], "mask"))
+ maskmode = true;
+ else if (!strcmp(argv[1], "only"))
+ onlymode = true;
+ else if (!strcmp(argv[1], "level"))
+ levelmode = true;
+ else
+ badargs = true;
+ }
+
+ if (maskmode)
+ m = LOG_UPTO(LOG_DEBUG);
+
+ if (argc > 2 && (maskmode || onlymode)) {
+ for (i = 2; i < (size_t)argc; i++) {
+ for (j = 0; j < logtblsz; j++) {
+ if (!strcmp(argv[i], logtbl[j].name)) {
+ if (maskmode)
+ m &= ~(LOG_MASK(logtbl[j].level));
+ else
+ m |= LOG_MASK(logtbl[j].level);
+ break;
+ }
+ }
+ if (j == logtblsz) {
+ badargs = true;
+ break;
+ }
+ }
+ } else if (argc > 2 && levelmode) {
+ for (j = 0; j < logtblsz; j++) {
+ if (!strcmp(argv[2], logtbl[j].name)) {
+ m = LOG_UPTO(logtbl[j].level);
+ break;
+ }
+ }
+ if (j == logtblsz)
+ badargs = true;
+ } else if (argc != 1) {
+ badargs = true;
+ }
+
+ if (badargs) {
+ launchctl_log(LOG_ERR, "usage: %s [[mask loglevels...] | [only loglevels...] [level loglevel]]", getprogname());
+ return 1;
+ }
+
+ inval = m;
+
+ if (vproc_swap_integer(NULL, VPROC_GSK_GLOBAL_LOG_MASK, argc != 1 ? &inval : NULL, &outval) == NULL) {
+ if (argc == 1) {
+ for (j = 0; j < logtblsz; j++) {
+ if (outval & LOG_MASK(logtbl[j].level)) {
+ launchctl_log(LOG_NOTICE, "%s ", logtbl[j].name);
+ }
+ }
+ launchctl_log(LOG_NOTICE, "");
+ }
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+static const struct {
+ const char *name;
+ int lim;
+} limlookup[] = {
+ { "cpu", RLIMIT_CPU },
+ { "filesize", RLIMIT_FSIZE },
+ { "data", RLIMIT_DATA },
+ { "stack", RLIMIT_STACK },
+ { "core", RLIMIT_CORE },
+ { "rss", RLIMIT_RSS },
+ { "memlock", RLIMIT_MEMLOCK },
+ { "maxproc", RLIMIT_NPROC },
+ { "maxfiles", RLIMIT_NOFILE }
+};
+
+static const size_t limlookupcnt = sizeof limlookup / sizeof limlookup[0];
+
+ssize_t
+name2num(const char *n)
+{
+ size_t i;
+
+ for (i = 0; i < limlookupcnt; i++) {
+ if (!strcmp(limlookup[i].name, n)) {
+ return limlookup[i].lim;
+ }
+ }
+ return -1;
+}
+
+const char *
+num2name(int n)
+{
+ size_t i;
+
+ for (i = 0; i < limlookupcnt; i++) {
+ if (limlookup[i].lim == n)
+ return limlookup[i].name;
+ }
+ return NULL;
+}
+
+const char *
+lim2str(rlim_t val, char *buf)
+{
+ if (val == RLIM_INFINITY)
+ strcpy(buf, "unlimited");
+ else
+ sprintf(buf, "%lld", val);
+ return buf;
+}
+
+bool
+str2lim(const char *buf, rlim_t *res)
+{
+ char *endptr;
+ *res = strtoll(buf, &endptr, 10);
+ if (!strcmp(buf, "unlimited")) {
+ *res = RLIM_INFINITY;
+ return false;
+ } else if (*endptr == '\0') {
+ return false;
+ }
+ return true;
+}
+
+int
+limit_cmd(int argc, char *const argv[])
+{
+ char slimstr[100];
+ char hlimstr[100];
+ struct rlimit *lmts = NULL;
+ launch_data_t resp, resp1 = NULL, msg, tmp;
+ int r = 0;
+ size_t i, lsz = -1;
+ ssize_t which = 0;
+ rlim_t slim = -1, hlim = -1;
+ bool badargs = false;
+
+ if (argc > 4)
+ badargs = true;
+
+ if (argc >= 3 && str2lim(argv[2], &slim))
+ badargs = true;
+ else
+ hlim = slim;
+
+ if (argc == 4 && str2lim(argv[3], &hlim))
+ badargs = true;
+
+ if (argc >= 2 && -1 == (which = name2num(argv[1])))
+ badargs = true;
+
+ if (badargs) {
+ launchctl_log(LOG_ERR, "usage: %s %s [", getprogname(), argv[0]);
+ for (i = 0; i < limlookupcnt; i++)
+ launchctl_log(LOG_ERR, "%s %s", limlookup[i].name, (i + 1) == limlookupcnt ? "" : "| ");
+ launchctl_log(LOG_ERR, "[both | soft hard]]");
+ return 1;
+ }
+
+ msg = launch_data_new_string(LAUNCH_KEY_GETRESOURCELIMITS);
+ resp = launch_msg(msg);
+ launch_data_free(msg);
+
+ if (resp == NULL) {
+ launchctl_log(LOG_ERR, "launch_msg(): %s", strerror(errno));
+ return 1;
+ } else if (launch_data_get_type(resp) == LAUNCH_DATA_OPAQUE) {
+ lmts = launch_data_get_opaque(resp);
+ lsz = launch_data_get_opaque_size(resp);
+ if (argc <= 2) {
+ for (i = 0; i < (lsz / sizeof(struct rlimit)); i++) {
+ if (argc == 2 && (size_t)which != i)
+ continue;
+ launchctl_log(LOG_NOTICE, "\t%-12s%-15s%-15s", num2name((int)i),
+ lim2str(lmts[i].rlim_cur, slimstr),
+ lim2str(lmts[i].rlim_max, hlimstr));
+ }
+ }
+ } else if (launch_data_get_type(resp) == LAUNCH_DATA_STRING) {
+ launchctl_log(LOG_ERR, "%s %s error: %s", getprogname(), argv[0], launch_data_get_string(resp));
+ r = 1;
+ } else {
+ launchctl_log(LOG_ERR, "%s %s returned unknown response", getprogname(), argv[0]);
+ r = 1;
+ }
+
+ if (argc <= 2 || r != 0) {
+ launch_data_free(resp);
+ return r;
+ } else {
+ resp1 = resp;
+ }
+
+ lmts[which].rlim_cur = slim;
+ lmts[which].rlim_max = hlim;
+
+ bool maxfiles_exceeded = false;
+ if (strncmp(argv[1], "maxfiles", sizeof("maxfiles")) == 0) {
+ if (argc > 2) {
+ maxfiles_exceeded = (strncmp(argv[2], "unlimited", sizeof("unlimited")) == 0);
+ }
+
+ if (argc > 3) {
+ maxfiles_exceeded = (maxfiles_exceeded || strncmp(argv[3], "unlimited", sizeof("unlimited")) == 0);
+ }
+
+ if (maxfiles_exceeded) {
+ launchctl_log(LOG_ERR, "Neither the hard nor soft limit for \"maxfiles\" can be unlimited. Please use a numeric parameter for both.");
+ return 1;
+ }
+ }
+
+ msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ tmp = launch_data_new_opaque(lmts, lsz);
+ launch_data_dict_insert(msg, tmp, LAUNCH_KEY_SETRESOURCELIMITS);
+ resp = launch_msg(msg);
+ launch_data_free(msg);
+
+ if (resp == NULL) {
+ launchctl_log(LOG_ERR, "launch_msg(): %s", strerror(errno));
+ return 1;
+ } else if (launch_data_get_type(resp) == LAUNCH_DATA_STRING) {
+ launchctl_log(LOG_ERR, "%s %s error: %s", getprogname(), argv[0], launch_data_get_string(resp));
+ r = 1;
+ } else if (launch_data_get_type(resp) != LAUNCH_DATA_OPAQUE) {
+ launchctl_log(LOG_ERR, "%s %s returned unknown response", getprogname(), argv[0]);
+ r = 1;
+ }
+
+ launch_data_free(resp);
+ launch_data_free(resp1);
+
+ return r;
+}
+
+int
+umask_cmd(int argc, char *const argv[])
+{
+ bool badargs = false;
+ char *endptr;
+ long m = 0;
+ int64_t inval, outval;
+
+ if (argc == 2) {
+ m = strtol(argv[1], &endptr, 8);
+ if (*endptr != '\0' || m > 0777)
+ badargs = true;
+ }
+
+ if (argc > 2 || badargs) {
+ launchctl_log(LOG_ERR, "usage: %s %s <mask>", getprogname(), argv[0]);
+ return 1;
+ }
+
+ inval = m;
+
+ if (vproc_swap_integer(NULL, VPROC_GSK_GLOBAL_UMASK, argc == 2 ? &inval : NULL, &outval) == NULL) {
+ if (argc == 1) {
+ launchctl_log(LOG_NOTICE, "%o", (unsigned int)outval);
+ }
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+void
+setup_system_context(void)
+{
+ if (getenv(LAUNCHD_SOCKET_ENV)) {
+ return;
+ }
+
+ if (getenv(LAUNCH_ENV_KEEPCONTEXT)) {
+ return;
+ }
+
+ if (geteuid() != 0) {
+ launchctl_log(LOG_ERR, "You must be the root user to perform this operation.");
+ return;
+ }
+
+ /* Use the system launchd's socket. */
+ setenv("__USE_SYSTEM_LAUNCHD", "1", 0);
+
+ /* Put ourselves in the system launchd's bootstrap. */
+ mach_port_t rootbs = str2bsport("/");
+ mach_port_deallocate(mach_task_self(), bootstrap_port);
+ task_set_bootstrap_port(mach_task_self(), rootbs);
+ bootstrap_port = rootbs;
+}
+
+int
+submit_cmd(int argc, char *const argv[])
+{
+ launch_data_t msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ launch_data_t job = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
+ launch_data_t resp, largv = launch_data_alloc(LAUNCH_DATA_ARRAY);
+ int ch, i, r = 0;
+
+ launch_data_dict_insert(job, launch_data_new_bool(false), LAUNCH_JOBKEY_ONDEMAND);
+
+ while ((ch = getopt(argc, argv, "l:p:o:e:")) != -1) {
+ switch (ch) {
+ case 'l':
+ launch_data_dict_insert(job, launch_data_new_string(optarg), LAUNCH_JOBKEY_LABEL);
+ break;
+ case 'p':
+ launch_data_dict_insert(job, launch_data_new_string(optarg), LAUNCH_JOBKEY_PROGRAM);
+ break;
+ case 'o':
+ launch_data_dict_insert(job, launch_data_new_string(optarg), LAUNCH_JOBKEY_STANDARDOUTPATH);
+ break;
+ case 'e':
+ launch_data_dict_insert(job, launch_data_new_string(optarg), LAUNCH_JOBKEY_STANDARDERRORPATH);
+ break;
+ default:
+ launchctl_log(LOG_ERR, "usage: %s submit ...", getprogname());
+ return 1;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ for (i = 0; argv[i]; i++) {
+ launch_data_array_append(largv, launch_data_new_string(argv[i]));
+ }
+
+ launch_data_dict_insert(job, largv, LAUNCH_JOBKEY_PROGRAMARGUMENTS);
+
+ launch_data_dict_insert(msg, job, LAUNCH_KEY_SUBMITJOB);
+
+ resp = launch_msg(msg);
+ launch_data_free(msg);
+
+ if (resp == NULL) {
+ launchctl_log(LOG_ERR, "launch_msg(): %s", strerror(errno));
+ return 1;
+ } else if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO) {
+ errno = launch_data_get_errno(resp);
+ if (errno) {
+ launchctl_log(LOG_ERR, "%s %s error: %s", getprogname(), argv[0], strerror(errno));
+ r = 1;
+ }
+ } else {
+ launchctl_log(LOG_ERR, "%s %s error: %s", getprogname(), argv[0], "unknown response");
+ }
+
+ launch_data_free(resp);
+
+ return r;
+}
+
+int
+getrusage_cmd(int argc, char *const argv[])
+{
+ launch_data_t resp, msg;
+ bool badargs = false;
+ int r = 0;
+
+ if (argc != 2)
+ badargs = true;
+ else if (strcmp(argv[1], "self") && strcmp(argv[1], "children"))
+ badargs = true;
+
+ if (badargs) {
+ launchctl_log(LOG_ERR, "usage: %s %s self | children", getprogname(), argv[0]);
+ return 1;
+ }
+
+ if (!strcmp(argv[1], "self")) {
+ msg = launch_data_new_string(LAUNCH_KEY_GETRUSAGESELF);
+ } else {
+ msg = launch_data_new_string(LAUNCH_KEY_GETRUSAGECHILDREN);
+ }
+
+ resp = launch_msg(msg);
+ launch_data_free(msg);
+
+ if (resp == NULL) {
+ launchctl_log(LOG_ERR, "launch_msg(): %s", strerror(errno));
+ return 1;
+ } else if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO) {
+ launchctl_log(LOG_ERR, "%s %s error: %s", getprogname(), argv[0], strerror(launch_data_get_errno(resp)));
+ r = 1;
+ } else if (launch_data_get_type(resp) == LAUNCH_DATA_OPAQUE) {
+ struct rusage *rusage = launch_data_get_opaque(resp);
+ launchctl_log(LOG_NOTICE, "\t%-10f\tuser time used",
+ (double)rusage->ru_utime.tv_sec + (double)rusage->ru_utime.tv_usec / (double)1000000);
+ launchctl_log(LOG_NOTICE, "\t%-10f\tsystem time used",
+ (double)rusage->ru_stime.tv_sec + (double)rusage->ru_stime.tv_usec / (double)1000000);
+ launchctl_log(LOG_NOTICE, "\t%-10ld\tmax resident set size", rusage->ru_maxrss);
+ launchctl_log(LOG_NOTICE, "\t%-10ld\tshared text memory size", rusage->ru_ixrss);
+ launchctl_log(LOG_NOTICE, "\t%-10ld\tunshared data size", rusage->ru_idrss);
+ launchctl_log(LOG_NOTICE, "\t%-10ld\tunshared stack size", rusage->ru_isrss);
+ launchctl_log(LOG_NOTICE, "\t%-10ld\tpage reclaims", rusage->ru_minflt);
+ launchctl_log(LOG_NOTICE, "\t%-10ld\tpage faults", rusage->ru_majflt);
+ launchctl_log(LOG_NOTICE, "\t%-10ld\tswaps", rusage->ru_nswap);
+ launchctl_log(LOG_NOTICE, "\t%-10ld\tblock input operations", rusage->ru_inblock);
+ launchctl_log(LOG_NOTICE, "\t%-10ld\tblock output operations", rusage->ru_oublock);
+ launchctl_log(LOG_NOTICE, "\t%-10ld\tmessages sent", rusage->ru_msgsnd);
+ launchctl_log(LOG_NOTICE, "\t%-10ld\tmessages received", rusage->ru_msgrcv);
+ launchctl_log(LOG_NOTICE, "\t%-10ld\tsignals received", rusage->ru_nsignals);
+ launchctl_log(LOG_NOTICE, "\t%-10ld\tvoluntary context switches", rusage->ru_nvcsw);
+ launchctl_log(LOG_NOTICE, "\t%-10ld\tinvoluntary context switches", rusage->ru_nivcsw);
+ } else {
+ launchctl_log(LOG_ERR, "%s %s returned unknown response", getprogname(), argv[0]);
+ r = 1;
+ }
+
+ launch_data_free(resp);
+
+ return r;
+}
+
+bool
+launch_data_array_append(launch_data_t a, launch_data_t o)
+{
+ size_t offt = launch_data_array_get_count(a);
+
+ return launch_data_array_set_index(a, o, offt);
+}
+
+mach_port_t
+str2bsport(const char *s)
+{
+ bool getrootbs = strcmp(s, "/") == 0;
+ mach_port_t last_bport, bport = bootstrap_port;
+ task_t task = mach_task_self();
+ kern_return_t result;
+
+ if (strcmp(s, "..") == 0 || getrootbs) {
+ do {
+ last_bport = bport;
+ result = bootstrap_parent(last_bport, &bport);
+
+ if (result == BOOTSTRAP_NOT_PRIVILEGED) {
+ launchctl_log(LOG_ERR, "Permission denied");
+ return 1;
+ } else if (result != BOOTSTRAP_SUCCESS) {
+ launchctl_log(LOG_ERR, "bootstrap_parent() %d", result);
+ return 1;
+ }
+ } while (getrootbs && last_bport != bport);
+ } else if (strcmp(s, "0") == 0 || strcmp(s, "NULL") == 0) {
+ bport = MACH_PORT_NULL;
+ } else {
+ int pid = atoi(s);
+
+ result = task_for_pid(mach_task_self(), pid, &task);
+
+ if (result != KERN_SUCCESS) {
+ launchctl_log(LOG_ERR, "task_for_pid() %s", mach_error_string(result));
+ return 1;
+ }
+
+ result = task_get_bootstrap_port(task, &bport);
+
+ if (result != KERN_SUCCESS) {
+ launchctl_log(LOG_ERR, "Couldn't get bootstrap port: %s", mach_error_string(result));
+ return 1;
+ }
+ }
+
+ return bport;
+}
+
+int
+bsexec_cmd(int argc, char *const argv[])
+{
+ kern_return_t result;
+ mach_port_t bport;
+
+ if (argc < 3) {
+ launchctl_log(LOG_ERR, "usage: %s bsexec <PID> prog...", getprogname());
+ return 1;
+ }
+
+ bport = str2bsport(argv[1]);
+
+ result = task_set_bootstrap_port(mach_task_self(), bport);
+
+ if (result != KERN_SUCCESS) {
+ launchctl_log(LOG_ERR, "Couldn't switch to new bootstrap port: %s", mach_error_string(result));
+ return 1;
+ }
+
+ setgid(getgid());
+ setuid(getuid());
+
+ setenv(LAUNCH_ENV_KEEPCONTEXT, "1", 1);
+ if (fwexec((const char *const *)argv + 2, NULL) == -1) {
+ launchctl_log(LOG_ERR, "%s bsexec failed: %s", getprogname(), strerror(errno));
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+_bslist_cmd(mach_port_t bport, unsigned int depth, bool show_job, bool local_only)
+{
+ kern_return_t result;
+ name_array_t service_names;
+ name_array_t service_jobs;
+ mach_msg_type_number_t service_cnt, service_jobs_cnt, service_active_cnt;
+ bootstrap_status_array_t service_actives;
+ unsigned int i;
+
+ if (bport == MACH_PORT_NULL) {
+ launchctl_log(LOG_ERR, "Invalid bootstrap port");
+ return 1;
+ }
+
+ uint64_t flags = 0;
+ flags |= local_only ? BOOTSTRAP_FORCE_LOCAL : 0;
+ result = bootstrap_info(bport, &service_names, &service_cnt, &service_jobs, &service_jobs_cnt, &service_actives, &service_active_cnt, flags);
+ if (result != BOOTSTRAP_SUCCESS) {
+ launchctl_log(LOG_ERR, "bootstrap_info(): %d", result);
+ return 1;
+ }
+
+#define bport_state(x) (((x) == BOOTSTRAP_STATUS_ACTIVE) ? "A" : ((x) == BOOTSTRAP_STATUS_ON_DEMAND) ? "D" : "I")
+
+ for (i = 0; i < service_cnt ; i++) {
+ if (!show_job) {
+ fprintf(stdout, "%*s%-3s%s\n", depth, "", bport_state((service_actives[i])), service_names[i]);
+ } else {
+ fprintf(stdout, "%*s%-3s%s (%s)\n", depth, "", bport_state((service_actives[i])), service_names[i], service_jobs[i]);
+ }
+ }
+
+ return 0;
+}
+
+int
+bslist_cmd(int argc, char *const argv[])
+{
+ if (_launchctl_is_managed) {
+ /* This output is meant for a command line, so don't print anything if
+ * we're managed by launchd.
+ */
+ return 1;
+ }
+
+ mach_port_t bport = bootstrap_port;
+ bool show_jobs = false;
+ if (argc > 2 && strcmp(argv[2], "-j") == 0) {
+ show_jobs = true;
+ }
+
+ if (argc > 1) {
+ if (show_jobs) {
+ bport = str2bsport(argv[1]);
+ } else if (strcmp(argv[1], "-j") == 0) {
+ show_jobs = true;
+ }
+ }
+
+ if (bport == MACH_PORT_NULL) {
+ launchctl_log(LOG_ERR, "Invalid bootstrap port");
+ return 1;
+ }
+
+ return _bslist_cmd(bport, 0, show_jobs, false);
+}
+
+int
+_bstree_cmd(mach_port_t bsport, unsigned int depth, bool show_jobs)
+{
+ if (bsport == MACH_PORT_NULL) {
+ launchctl_log(LOG_ERR, "No root port!");
+ return 1;
+ }
+
+ mach_port_array_t child_ports = NULL;
+ name_array_t child_names = NULL;
+ bootstrap_property_array_t child_props = NULL;
+ unsigned int cnt = 0;
+
+ kern_return_t kr = bootstrap_lookup_children(bsport, &child_ports, &child_names, &child_props, (mach_msg_type_number_t *)&cnt);
+ if (kr != BOOTSTRAP_SUCCESS && kr != BOOTSTRAP_NO_CHILDREN) {
+ if (kr == BOOTSTRAP_NOT_PRIVILEGED) {
+ launchctl_log(LOG_ERR, "You must be root to perform this operation.");
+ } else {
+ launchctl_log(LOG_ERR, "bootstrap_lookup_children(): %d", kr);
+ }
+
+ return 1;
+ }
+
+ unsigned int i = 0;
+ _bslist_cmd(bsport, depth, show_jobs, true);
+
+ for (i = 0; i < cnt; i++) {
+ char *type = NULL;
+ if (child_props[i] & BOOTSTRAP_PROPERTY_PERUSER) {
+ type = "Per-user";
+ } else if (child_props[i] & BOOTSTRAP_PROPERTY_EXPLICITSUBSET) {
+ type = "Explicit Subset";
+ } else if (child_props[i] & BOOTSTRAP_PROPERTY_IMPLICITSUBSET) {
+ type = "Implicit Subset";
+ } else if (child_props[i] & BOOTSTRAP_PROPERTY_MOVEDSUBSET) {
+ type = "Moved Subset";
+ } else if (child_props[i] & BOOTSTRAP_PROPERTY_XPC_SINGLETON) {
+ type = "XPC Singleton Domain";
+ } else if (child_props[i] & BOOTSTRAP_PROPERTY_XPC_DOMAIN) {
+ type = "XPC Private Domain";
+ } else {
+ type = "Unknown";
+ }
+
+ fprintf(stdout, "%*s%s (%s)/\n", depth, "", child_names[i], type);
+ if (child_ports[i] != MACH_PORT_NULL) {
+ _bstree_cmd(child_ports[i], depth + 4, show_jobs);
+ }
+ }
+
+ return 0;
+}
+
+int
+bstree_cmd(int argc, char * const argv[])
+{
+ if (_launchctl_is_managed) {
+ /* This output is meant for a command line, so don't print anything if
+ * we're managed by launchd.
+ */
+ return 1;
+ }
+
+ bool show_jobs = false;
+ if (geteuid() != 0) {
+ launchctl_log(LOG_ERR, "You must be root to perform this operation.");
+ return 1;
+ } else {
+ if (argc == 2 && strcmp(argv[1], "-j") == 0) {
+ show_jobs = true;
+ }
+ fprintf(stdout, "System/\n");
+ }
+
+ return _bstree_cmd(str2bsport("/"), 4, show_jobs);
+}
+
+int
+managerpid_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused)))
+{
+ int64_t manager_pid = 0;
+ vproc_err_t verr = vproc_swap_integer(NULL, VPROC_GSK_MGR_PID, NULL, (int64_t *)&manager_pid);
+ if (verr) {
+ launchctl_log(LOG_NOTICE, "Unknown job manager!");
+ return 1;
+ }
+
+ launchctl_log(LOG_NOTICE, "%d", (pid_t)manager_pid);
+ return 0;
+}
+
+int
+manageruid_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused)))
+{
+ int64_t manager_uid = 0;
+ vproc_err_t verr = vproc_swap_integer(NULL, VPROC_GSK_MGR_UID, NULL, (int64_t *)&manager_uid);
+ if (verr) {
+ launchctl_log(LOG_NOTICE, "Unknown job manager!");
+ return 1;
+ }
+
+ launchctl_log(LOG_NOTICE, "%lli", manager_uid);
+ return 0;
+}
+
+int
+managername_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused)))
+{
+ char *manager_name = NULL;
+ vproc_err_t verr = vproc_swap_string(NULL, VPROC_GSK_MGR_NAME, NULL, &manager_name);
+ if (verr) {
+ launchctl_log(LOG_NOTICE, "Unknown job manager!");
+ return 1;
+ }
+
+ launchctl_log(LOG_NOTICE, "%s", manager_name);
+ free(manager_name);
+
+ return 0;
+}
+
+int
+asuser_cmd(int argc, char * const argv[])
+{
+ /* This code plays fast and loose with Mach ports. Do NOT use it as any sort
+ * of reference for port handling. Or really anything else in this file.
+ */
+ uid_t req_uid = (uid_t)-2;
+ if (argc > 2) {
+ req_uid = atoi(argv[1]);
+ if (req_uid == (uid_t)-2) {
+ launchctl_log(LOG_ERR, "You cannot run a command nobody.");
+ return 1;
+ }
+ } else {
+ launchctl_log(LOG_ERR, "Usage: launchctl asuser <UID> <command> [arguments...].");
+ return 1;
+ }
+
+ if (geteuid() != 0) {
+ launchctl_log(LOG_ERR, "You must be root to run a command as another user.");
+ return 1;
+ }
+
+ mach_port_t rbs = MACH_PORT_NULL;
+ kern_return_t kr = bootstrap_get_root(bootstrap_port, &rbs);
+ if (kr != BOOTSTRAP_SUCCESS) {
+ launchctl_log(LOG_ERR, "bootstrap_get_root(): %u", kr);
+ return 1;
+ }
+
+ mach_port_t bp = MACH_PORT_NULL;
+ kr = bootstrap_look_up_per_user(rbs, NULL, req_uid, &bp);
+ if (kr != BOOTSTRAP_SUCCESS) {
+ launchctl_log(LOG_ERR, "bootstrap_look_up_per_user(): %u", kr);
+ return 1;
+ }
+
+ bootstrap_port = bp;
+ kr = task_set_bootstrap_port(mach_task_self(), bp);
+ if (kr != KERN_SUCCESS) {
+ launchctl_log(LOG_ERR, "task_set_bootstrap_port(): 0x%x: %s", kr, mach_error_string(kr));
+ return 1;
+ }
+
+ name_t sockpath;
+ sockpath[0] = 0;
+ kr = _vprocmgr_getsocket(sockpath);
+ if (kr != BOOTSTRAP_SUCCESS) {
+ launchctl_log(LOG_ERR, "_vprocmgr_getsocket(): %u", kr);
+ return 1;
+ }
+
+ setenv(LAUNCHD_SOCKET_ENV, sockpath, 1);
+ setenv(LAUNCH_ENV_KEEPCONTEXT, "1", 1);
+ if (fwexec((const char *const *)argv + 2, NULL) == -1) {
+ launchctl_log(LOG_ERR, "Couldn't spawn command: %s", argv[2]);
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+loopback_setup_ipv4(void)
+{
+ struct ifaliasreq ifra;
+ struct ifreq ifr;
+ int s;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strcpy(ifr.ifr_name, "lo0");
+
+ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
+ return;
+
+ if (posix_assumes_zero(ioctl(s, SIOCGIFFLAGS, &ifr)) != -1) {
+ ifr.ifr_flags |= IFF_UP;
+ (void)posix_assumes_zero(ioctl(s, SIOCSIFFLAGS, &ifr));
+ }
+
+ memset(&ifra, 0, sizeof(ifra));
+ strcpy(ifra.ifra_name, "lo0");
+ ((struct sockaddr_in *)&ifra.ifra_addr)->sin_family = AF_INET;
+ ((struct sockaddr_in *)&ifra.ifra_addr)->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ ((struct sockaddr_in *)&ifra.ifra_addr)->sin_len = sizeof(struct sockaddr_in);
+ ((struct sockaddr_in *)&ifra.ifra_mask)->sin_family = AF_INET;
+ ((struct sockaddr_in *)&ifra.ifra_mask)->sin_addr.s_addr = htonl(IN_CLASSA_NET);
+ ((struct sockaddr_in *)&ifra.ifra_mask)->sin_len = sizeof(struct sockaddr_in);
+
+ (void)posix_assumes_zero(ioctl(s, SIOCAIFADDR, &ifra));
+ (void)close(s);
+}
+
+void
+loopback_setup_ipv6(void)
+{
+ struct in6_aliasreq ifra6;
+ struct ifreq ifr;
+ int s6;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strcpy(ifr.ifr_name, "lo0");
+
+ if ((s6 = socket(AF_INET6, SOCK_DGRAM, 0)) == -1)
+ return;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strcpy(ifr.ifr_name, "lo0");
+
+ if (posix_assumes_zero(ioctl(s6, SIOCGIFFLAGS, &ifr)) != -1) {
+ ifr.ifr_flags |= IFF_UP;
+ (void)posix_assumes_zero(ioctl(s6, SIOCSIFFLAGS, &ifr));
+ }
+
+ memset(&ifra6, 0, sizeof(ifra6));
+ strcpy(ifra6.ifra_name, "lo0");
+
+ ifra6.ifra_addr.sin6_family = AF_INET6;
+ ifra6.ifra_addr.sin6_addr = in6addr_loopback;
+ ifra6.ifra_addr.sin6_len = sizeof(struct sockaddr_in6);
+ ifra6.ifra_prefixmask.sin6_family = AF_INET6;
+ memset(&ifra6.ifra_prefixmask.sin6_addr, 0xff, sizeof(struct in6_addr));
+ ifra6.ifra_prefixmask.sin6_len = sizeof(struct sockaddr_in6);
+ ifra6.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
+ ifra6.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;
+
+ if (ioctl(s6, SIOCAIFADDR_IN6, &ifra6) == -1 && errno != EEXIST) {
+ (void)osx_assumes_zero(errno);
+ }
+
+ (void)close(s6);
+}
+
+pid_t
+fwexec(const char *const *argv, int *wstatus)
+{
+ int wstatus2;
+ pid_t p;
+
+ /* We'd use posix_spawnp(), but we want to workaround: 6288899 */
+ if ((p = vfork()) == -1) {
+ return -1;
+ } else if (p == 0) {
+ execvp(argv[0], (char *const *)argv);
+ _exit(EXIT_FAILURE);
+ }
+
+ if (waitpid(p, wstatus ? wstatus : &wstatus2, 0) == -1) {
+ return -1;
+ }
+
+ if (wstatus) {
+ return p;
+ } else if (WIFEXITED(wstatus2) && WEXITSTATUS(wstatus2) == EXIT_SUCCESS) {
+ return p;
+ }
+
+ return -1;
+}
+
+void
+do_potential_fsck(void)
+{
+ /* XXX: This whole function's logic needs to be redone. */
+
+ const char *safe_fsck_tool[] = { "fsck", "-fy", NULL };
+ const char *fsck_tool[] = { "fsck", "-q", NULL };
+ const char *remount_tool[] = { "mount", "-uw", "/", NULL };
+#if TARGET_OS_EMBEDDED
+ const char *nvram_tool[] = { "/usr/sbin/nvram", "auto-boot=false", NULL };
+#endif /* TARGET_OS_EMBEDDED */
+ struct statfs sfs;
+ int status = 0;
+
+ if (posix_assumes_zero(statfs("/", &sfs)) == -1) {
+ return;
+ }
+
+ if (!(sfs.f_flags & MNT_RDONLY)) {
+ return;
+ }
+
+ if (!is_safeboot()) {
+#if 0
+ /* We have disabled this block for now. We need to revisit this optimization after Leopard. */
+ if (sfs.f_flags & MNT_JOURNALED) {
+ goto out;
+ }
+#endif
+ launchctl_log(LOG_NOTICE, "Running fsck on the boot volume...");
+ if (fwexec(fsck_tool, &status) != -1) {
+ if (WEXITSTATUS(status) != 0) {
+ launchctl_log(LOG_NOTICE, "fsck exited with status: %d", WEXITSTATUS(status));
+ } else {
+ goto out;
+ }
+ } else {
+ launchctl_log(LOG_NOTICE, "fwexec(): %d: %s", errno, strerror(errno));
+ }
+ }
+
+ launchctl_log(LOG_NOTICE, "Running safe fsck on the boot volume...");
+ if (fwexec(safe_fsck_tool, &status) != -1) {
+ if (WEXITSTATUS(status) != 0) {
+ launchctl_log(LOG_NOTICE, "Safe fsck exited with status: %d", WEXITSTATUS(status));
+ } else {
+ goto out;
+ }
+ } else {
+ launchctl_log(LOG_NOTICE, "fwexec(): %d: %s", errno, strerror(errno));
+ }
+
+ /* someday, we should keep booting read-only, but as of today, other sub-systems cannot handle that scenario */
+#if TARGET_OS_EMBEDDED
+ launchctl_log(LOG_NOTICE, "fsck failed! Booting into restore mode...");
+ (void)posix_assumes_zero(fwexec(nvram_tool, NULL));
+ (void)reboot(RB_AUTOBOOT);
+#else
+ launchctl_log(LOG_NOTICE, "fsck failed! Shutting down in 3 seconds.");
+ sleep(3);
+ (void)reboot(RB_HALT);
+#endif
+
+ return;
+out:
+ /*
+ * Once this is fixed:
+ *
+ * <rdar://problem/3948774> Mount flag updates should be possible with NULL as the forth argument to mount()
+ *
+ * We can then do this one system call instead of calling out a full blown process.
+ *
+ * assumes(mount(sfs.f_fstypename, "/", MNT_UPDATE, NULL) != -1);
+ */
+#if TARGET_OS_EMBEDDED
+ if (path_check("/etc/fstab")) {
+ const char *mount_tool[] = { "mount", "-vat", "nonfs", NULL };
+ if (posix_assumes_zero(fwexec(mount_tool, NULL)) == -1) {
+ (void)fwexec(nvram_tool, NULL);
+ (void)reboot(RB_AUTOBOOT);
+ }
+ } else
+#endif
+ {
+ (void)posix_assumes_zero(fwexec(remount_tool, NULL));
+ }
+
+ fix_bogus_file_metadata();
+}
+
+void
+fix_bogus_file_metadata(void)
+{
+ static const struct {
+ const char *path;
+ const uid_t owner;
+ const gid_t group;
+ const mode_t needed_bits;
+ const mode_t bad_bits;
+ const bool create;
+ } f[] = {
+ { "/sbin/launchd", 0, 0, S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH, S_ISUID|S_ISGID|S_ISVTX|S_IWOTH, false },
+ { _PATH_TMP, 0, 0, S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO, S_ISUID|S_ISGID, true },
+ { _PATH_VARTMP, 0, 0, S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO, S_ISUID|S_ISGID, true },
+ { "/var/folders", 0, 0, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, S_ISUID | S_ISGID, true },
+ { LAUNCHD_DB_PREFIX, 0, 0, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, S_IWGRP | S_IWOTH, true },
+ { LAUNCHD_DB_PREFIX "/com.apple.launchd", 0, 0, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, S_IWGRP | S_IWOTH, true },
+ // Fixing <rdar://problem/7571633>.
+ { _PATH_VARDB, 0, 0, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, S_IWGRP | S_IWOTH | S_ISUID | S_ISGID, true },
+#if !TARGET_OS_EMBEDDED
+ { _PATH_VARDB "mds/", 0, 0, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, S_IWGRP | S_IWOTH | S_ISUID | S_ISGID, true },
+ // Similar fix for <rdar://problem/6550172>.
+ { "/Library/StartupItems", 0, 0, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, S_IWGRP | S_IWOTH | S_ISUID | S_ISGID, true },
+#endif
+ };
+ struct stat sb;
+ size_t i;
+
+ for (i = 0; i < (sizeof(f) / sizeof(f[0])); i++) {
+ mode_t i_needed_bits;
+ mode_t i_bad_bits;
+ bool fix_mode = false;
+ bool fix_id = false;
+
+ if (stat(f[i].path, &sb) == -1) {
+ launchctl_log(LOG_NOTICE, "Crucial filesystem check: Path not present: %s. %s", f[i].path, f[i].create ? "Will create." : "");
+ if (f[i].create) {
+ if (posix_assumes_zero(mkdir(f[i].path, f[i].needed_bits)) == -1) {
+ continue;
+ } else if (posix_assumes_zero(stat(f[i].path, &sb)) == -1) {
+ continue;
+ }
+ } else {
+ continue;
+ }
+ }
+
+ i_needed_bits = ~sb.st_mode & f[i].needed_bits;
+ i_bad_bits = sb.st_mode & f[i].bad_bits;
+
+ if (i_bad_bits) {
+ launchctl_log(LOG_ERR, "Crucial filesystem check: Removing bogus mode bits 0%o on path: %s", i_bad_bits, f[i].path);
+ fix_mode = true;
+ }
+ if (i_needed_bits) {
+ launchctl_log(LOG_ERR, "Crucial filesystem check: Adding missing mode bits 0%o on path: %s", i_needed_bits, f[i].path);
+ fix_mode = true;
+ }
+ if (sb.st_uid != f[i].owner) {
+ launchctl_log(LOG_ERR, "Crucial filesystem check: Fixing bogus UID %u on path: %s", sb.st_uid, f[i].path);
+ fix_id = true;
+ }
+ if (sb.st_gid != f[i].group) {
+ launchctl_log(LOG_ERR, "Crucial filesystem check: Fixing bogus GID %u on path: %s", sb.st_gid, f[i].path);
+ fix_id = true;
+ }
+
+ if (fix_mode) {
+ (void)posix_assumes_zero(chmod(f[i].path, (sb.st_mode & ~i_bad_bits) | i_needed_bits));
+ }
+ if (fix_id) {
+ (void)posix_assumes_zero(chown(f[i].path, f[i].owner, f[i].group));
+ }
+ }
+}
+
+
+bool
+path_check(const char *path)
+{
+ struct stat sb;
+
+ if (stat(path, &sb) == 0)
+ return true;
+ return false;
+}
+
+bool
+is_safeboot(void)
+{
+ int sbmib[] = { CTL_KERN, KERN_SAFEBOOT };
+ uint32_t sb = 0;
+ size_t sbsz = sizeof(sb);
+
+ if (posix_assumes_zero(sysctl(sbmib, 2, &sb, &sbsz, NULL, 0)) == -1) {
+ return false;
+ }
+
+ return (bool)sb;
+}
+
+bool
+is_netboot(void)
+{
+ int nbmib[] = { CTL_KERN, KERN_NETBOOT };
+ uint32_t nb = 0;
+ size_t nbsz = sizeof(nb);
+
+ if (posix_assumes_zero(sysctl(nbmib, 2, &nb, &nbsz, NULL, 0)) == -1) {
+ return false;
+ }
+
+ return (bool)nb;
+}
+
+void
+empty_dir(const char *thedir, struct stat *psb)
+{
+ struct dirent *de;
+ struct stat psb2;
+ DIR *od;
+ int currend_dir_fd;
+
+ if (!psb) {
+ psb = &psb2;
+ if (posix_assumes_zero(lstat(thedir, psb)) == -1) {
+ return;
+ }
+ }
+
+ if (posix_assumes_zero(currend_dir_fd = open(".", 0)) == -1) {
+ return;
+ }
+
+ if (posix_assumes_zero(chdir(thedir)) == -1) {
+ goto out;
+ }
+
+ if (!(od = opendir("."))) {
+ (void)osx_assumes_zero(errno);
+ goto out;
+ }
+
+ while ((de = readdir(od))) {
+ struct stat sb;
+
+ if (strcmp(de->d_name, ".") == 0) {
+ continue;
+ }
+
+ if (strcmp(de->d_name, "..") == 0) {
+ continue;
+ }
+
+ if (posix_assumes_zero(lstat(de->d_name, &sb)) == -1) {
+ continue;
+ }
+
+ if (psb->st_dev != sb.st_dev) {
+ (void)posix_assumes_zero(unmount(de->d_name, MNT_FORCE));
+
+ /* Let's lstat() again to see if the unmount() worked and what was
+ * under it.
+ */
+ if (posix_assumes_zero(lstat(de->d_name, &sb)) == -1) {
+ continue;
+ }
+
+ if (osx_assumes(psb->st_dev == sb.st_dev)) {
+ continue;
+ }
+ }
+
+ if (S_ISDIR(sb.st_mode)) {
+ empty_dir(de->d_name, &sb);
+ }
+
+ (void)posix_assumes_zero(lchflags(de->d_name, 0));
+ (void)posix_assumes_zero(remove(de->d_name));
+ }
+
+ (void)closedir(od);
+
+out:
+ (void)posix_assumes_zero(fchdir(currend_dir_fd));
+ (void)posix_assumes_zero(close(currend_dir_fd));
+}
+
+int
+touch_file(const char *path, mode_t m)
+{
+ int fd = open(path, O_CREAT, m);
+
+ if (fd == -1)
+ return -1;
+
+ return close(fd);
+}
+
+void
+apply_sysctls_from_file(const char *thefile)
+{
+ const char *sysctl_tool[] = { "sysctl", "-w", NULL, NULL };
+ size_t ln_len = 0;
+ char *val, *tmpstr;
+ FILE *sf;
+
+ if (!(sf = fopen(thefile, "r")))
+ return;
+
+ while ((val = fgetln(sf, &ln_len))) {
+ if (ln_len == 0) {
+ continue;
+ }
+ if (!(tmpstr = malloc(ln_len + 1))) {
+ (void)osx_assumes_zero(errno);
+ continue;
+ }
+ memcpy(tmpstr, val, ln_len);
+ tmpstr[ln_len] = 0;
+ val = tmpstr;
+
+ if (val[ln_len - 1] == '\n' || val[ln_len - 1] == '\r') {
+ val[ln_len - 1] = '\0';
+ }
+
+ while (*val && isspace(*val))
+ val++;
+ if (*val == '\0' || *val == '#') {
+ goto skip_sysctl_tool;
+ }
+ sysctl_tool[2] = val;
+ (void)posix_assumes_zero(fwexec(sysctl_tool, NULL));
+skip_sysctl_tool:
+ free(tmpstr);
+ }
+
+ (void)fclose(sf);
+}
+
+static CFStringRef
+copySystemBuildVersion(void)
+{
+ CFStringRef build = NULL;
+ const char path[] = "/System/Library/CoreServices/SystemVersion.plist";
+ CFURLRef plistURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorSystemDefault, (const uint8_t *)path, sizeof(path) - 1, false);
+
+ CFPropertyListRef plist = NULL;
+ if (plistURL && (plist = CFPropertyListCreateFromFile(plistURL))) {
+ if (CFTypeCheck(plist, CFDictionary)) {
+ build = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)plist, _kCFSystemVersionBuildVersionKey);
+ if (build && CFTypeCheck(build, CFString)) {
+ CFRetain(build);
+ } else {
+ build = CFSTR("99Z999");
+ }
+ }
+
+ CFRelease(plist);
+ } else {
+ build = CFSTR("99Z999");
+ }
+
+ if (plistURL) {
+ CFRelease(plistURL);
+ }
+
+ return build;
+}
+
+void
+do_sysversion_sysctl(void)
+{
+ int mib[] = { CTL_KERN, KERN_OSVERSION };
+ CFStringRef buildvers;
+ char buf[1024];
+ size_t bufsz = sizeof(buf);
+
+ /* <rdar://problem/4477682> ER: launchd should set kern.osversion very early in boot */
+
+ if (sysctl(mib, 2, buf, &bufsz, NULL, 0) == -1) {
+ launchctl_log(LOG_ERR, "sysctl(): %s", strerror(errno));
+ return;
+ }
+
+ if (buf[0] != '\0') {
+ return;
+ }
+
+ buildvers = copySystemBuildVersion();
+ if (buildvers) {
+ CFStringGetCString(buildvers, buf, sizeof(buf), kCFStringEncodingUTF8);
+ (void)posix_assumes_zero(sysctl(mib, 2, NULL, 0, buf, strlen(buf) + 1));
+ }
+
+ CFRelease(buildvers);
+}
+
+void
+do_application_firewall_magic(int sfd, launch_data_t thejob)
+{
+ const char *prog = NULL, *partialprog = NULL;
+ char *path, *pathtmp, **pathstmp;
+ char *paths[100];
+ launch_data_t tmp;
+
+ /*
+ * Sigh...
+ * <rdar://problem/4684434> setsockopt() with the executable path as the argument
+ */
+
+ if ((tmp = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_PROGRAM))) {
+ prog = launch_data_get_string(tmp);
+ }
+
+ if (!prog) {
+ if ((tmp = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_PROGRAMARGUMENTS))) {
+ if ((tmp = launch_data_array_get_index(tmp, 0))) {
+ if ((partialprog = launch_data_get_string(tmp))) {
+ if (partialprog[0] == '/') {
+ prog = partialprog;
+ }
+ }
+ }
+ }
+ }
+
+ if (!prog) {
+ pathtmp = path = strdup(getenv("PATH"));
+
+ pathstmp = paths;
+
+ while ((*pathstmp = strsep(&pathtmp, ":"))) {
+ if (**pathstmp != '\0') {
+ pathstmp++;
+ }
+ }
+
+ free(path);
+ pathtmp = alloca(MAXPATHLEN);
+
+ pathstmp = paths;
+
+ for (; *pathstmp; pathstmp++) {
+ snprintf(pathtmp, MAXPATHLEN, "%s/%s", *pathstmp, partialprog);
+ if (path_check(pathtmp)) {
+ prog = pathtmp;
+ break;
+ }
+ }
+ }
+
+ if (prog != NULL) {
+ /* The networking team has asked us to ignore the failure of this API if
+ * errno == ENOPROTOOPT.
+ */
+ if (setsockopt(sfd, SOL_SOCKET, SO_EXECPATH, prog, (socklen_t)(strlen(prog) + 1)) == -1 && errno != ENOPROTOOPT) {
+ (void)osx_assumes_zero(errno);
+ }
+ }
+}
+
+
+void
+preheat_page_cache_hack(void)
+{
+ struct dirent *de;
+ DIR *thedir;
+
+ /* Disable this hack for now */
+ return;
+
+ if ((thedir = opendir("/etc/preheat_at_boot")) == NULL) {
+ return;
+ }
+
+ while ((de = readdir(thedir))) {
+ struct stat sb;
+ void *junkbuf;
+ int fd;
+
+ if (de->d_name[0] == '.') {
+ continue;
+ }
+
+ if ((fd = open(de->d_name, O_RDONLY)) == -1) {
+ continue;
+ }
+
+ if (fstat(fd, &sb) != -1) {
+ if ((sb.st_size < 10*1024*1024) && (junkbuf = malloc((size_t)sb.st_size)) != NULL) {
+ ssize_t n = read(fd, junkbuf, (size_t)sb.st_size);
+ if (posix_assumes_zero(n) != -1 && n != (ssize_t)sb.st_size) {
+ (void)osx_assumes_zero(n);
+ }
+ free(junkbuf);
+ }
+ }
+
+ close(fd);
+ }
+
+ closedir(thedir);
+}
+
+void
+do_bootroot_magic(void)
+{
+ const char *kextcache_tool[] = { "kextcache", "-U", "/", NULL };
+ CFTypeRef bootrootProp;
+ io_service_t chosen;
+ int wstatus;
+ pid_t p;
+
+ chosen = IORegistryEntryFromPath(kIOMasterPortDefault, "IODeviceTree:/chosen");
+
+ if (!osx_assumes(chosen)) {
+ return;
+ }
+
+ bootrootProp = IORegistryEntryCreateCFProperty(chosen, CFSTR(kBootRootActiveKey), kCFAllocatorDefault, 0);
+
+ IOObjectRelease(chosen);
+
+ if (!bootrootProp) {
+ return;
+ }
+
+ CFRelease(bootrootProp);
+
+ if (posix_assumes_zero(p = fwexec(kextcache_tool, &wstatus)) == -1) {
+ return;
+ }
+
+ if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == EX_OSFILE) {
+ (void)reboot(RB_AUTOBOOT);
+ }
+}
+
+void
+do_file_init(void)
+{
+ struct stat sb;
+
+ if (stat("/AppleInternal", &sb) == 0 && stat("/var/db/disableAppleInternal", &sb) == -1) {
+ _launchctl_apple_internal = true;
+ }
+
+ char bootargs[128];
+ size_t len = sizeof(bootargs);
+ int r = sysctlbyname("kern.bootargs", bootargs, &len, NULL, 0);
+ if (r == 0 && (strnstr(bootargs, "-v", len) != NULL || strnstr(bootargs, "-s", len))) {
+ _launchctl_verbose_boot = true;
+ }
+
+ if (stat("/var/db/.launchd_shutdown_debugging", &sb) == 0 && _launchctl_verbose_boot) {
+ _launchctl_startup_debugging = true;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2005 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 "config.h"
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/event.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <libgen.h>
+#include <getopt.h>
+#include <signal.h>
+#include <netdb.h>
+
+#if !TARGET_OS_EMBEDDED
+#include <bsm/audit.h>
+#include <bsm/audit_session.h>
+#endif // !TARGET_OS_EMBEDDED
+
+#include "launch.h"
+
+static int kq = 0;
+
+static void find_fds(launch_data_t o, const char *key __attribute__((unused)), void *context __attribute__((unused)))
+{
+ struct kevent kev;
+ size_t i;
+ int fd;
+
+ switch (launch_data_get_type(o)) {
+ case LAUNCH_DATA_FD:
+ fd = launch_data_get_fd(o);
+ if (-1 == fd)
+ break;
+ fcntl(fd, F_SETFD, 1);
+ EV_SET(&kev, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
+ if (kevent(kq, &kev, 1, NULL, 0, NULL) == -1)
+ syslog(LOG_DEBUG, "kevent(%d): %m", fd);
+ break;
+ case LAUNCH_DATA_ARRAY:
+ for (i = 0; i < launch_data_array_get_count(o); i++)
+ find_fds(launch_data_array_get_index(o, i), NULL, NULL);
+ break;
+ case LAUNCH_DATA_DICTIONARY:
+ launch_data_dict_iterate(o, find_fds, NULL);
+ break;
+ default:
+ break;
+ }
+}
+
+int main(int argc __attribute__((unused)), char *argv[])
+{
+ struct timespec timeout = { 10, 0 };
+ struct sockaddr_storage ss;
+ socklen_t slen = (socklen_t)sizeof ss;
+ struct kevent kev;
+ int r, ec = EXIT_FAILURE;
+ launch_data_t tmp, resp, msg = launch_data_alloc(LAUNCH_DATA_STRING);
+ const char *prog = argv[1];
+ bool w = false, dupstdout = true, dupstderr = true;
+
+ launch_data_set_string(msg, LAUNCH_KEY_CHECKIN);
+
+ openlog(getprogname(), LOG_PERROR|LOG_PID|LOG_CONS, LOG_LAUNCHD);
+
+ kq = kqueue();
+
+ if ((resp = launch_msg(msg)) == NULL) {
+ syslog(LOG_ERR, "launch_msg(%s): %m", LAUNCH_KEY_CHECKIN);
+ goto out;
+ }
+
+ launch_data_free(msg);
+
+ tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_SOCKETS);
+ if (tmp) {
+ find_fds(tmp, NULL, NULL);
+ } else {
+ syslog(LOG_ERR, "No FDs found to answer requests on!");
+ goto out;
+ }
+
+ tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_TIMEOUT);
+ if (tmp)
+ timeout.tv_sec = (int)launch_data_get_integer(tmp);
+
+ tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_PROGRAM);
+ if (tmp)
+ prog = launch_data_get_string(tmp);
+
+ tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_INETDCOMPATIBILITY);
+ if (tmp) {
+ tmp = launch_data_dict_lookup(tmp, LAUNCH_JOBINETDCOMPATIBILITY_WAIT);
+ if (tmp)
+ w = launch_data_get_bool(tmp);
+ }
+
+ if (launch_data_dict_lookup(resp, LAUNCH_JOBKEY_STANDARDOUTPATH))
+ dupstdout = false;
+
+ if (launch_data_dict_lookup(resp, LAUNCH_JOBKEY_STANDARDERRORPATH))
+ dupstderr = false;
+
+ if (!w)
+ signal(SIGCHLD, SIG_IGN);
+
+ for (;;) {
+ if ((r = kevent(kq, NULL, 0, &kev, 1, &timeout)) == -1) {
+ syslog(LOG_DEBUG, "kevent(): %m");
+ goto out;
+ } else if (r == 0) {
+ ec = EXIT_SUCCESS;
+ goto out;
+ }
+
+ if (w) {
+ dup2((int)kev.ident, STDIN_FILENO);
+ if (dupstdout)
+ dup2((int)kev.ident, STDOUT_FILENO);
+ if (dupstderr)
+ dup2((int)kev.ident, STDERR_FILENO);
+ execv(prog, argv + 1);
+ syslog(LOG_ERR, "execv(): %m");
+ exit(EXIT_FAILURE);
+ }
+
+ if ((r = accept((int)kev.ident, (struct sockaddr *)&ss, &slen)) == -1) {
+ if (errno == EWOULDBLOCK)
+ continue;
+ syslog(LOG_WARNING, "accept(): %m");
+ goto out;
+ } else {
+ if (ss.ss_family == AF_INET || ss.ss_family == AF_INET6) {
+ char fromhost[NI_MAXHOST];
+ char fromport[NI_MAXSERV];
+ int gni_r;
+
+ gni_r = getnameinfo((struct sockaddr *)&ss, slen,
+ fromhost, (socklen_t) sizeof fromhost,
+ fromport, (socklen_t) sizeof fromport,
+ NI_NUMERICHOST | NI_NUMERICSERV);
+
+ if (gni_r) {
+ syslog(LOG_WARNING, "%s: getnameinfo(): %s", prog, gai_strerror(gni_r));
+ } else {
+ syslog(LOG_INFO, "%s: Connection from: %s on port: %s", prog, fromhost, fromport);
+ }
+ } else {
+ syslog(LOG_WARNING, "%s: getnameinfo() only supports IPv4/IPv6. Connection from address family: %u", prog, ss.ss_family);
+ }
+
+ switch (fork()) {
+ case -1:
+ syslog(LOG_WARNING, "fork(): %m");
+ if (errno != ENOMEM) {
+ continue;
+ }
+ goto out;
+ case 0:
+ break;
+ default:
+ close(r);
+ continue;
+ }
+
+ setpgid(0, 0);
+
+#if !TARGET_OS_EMBEDDED
+ if ((tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_SESSIONCREATE)) && launch_data_get_bool(tmp)) {
+ auditinfo_addr_t auinfo = {
+ .ai_termid = { .at_type = AU_IPv4 },
+ .ai_asid = AU_ASSIGN_ASID,
+ .ai_auid = getuid(),
+ .ai_flags = 0,
+ };
+ if (setaudit_addr(&auinfo, sizeof(auinfo)) == 0) {
+ char session[16];
+ snprintf(session, sizeof(session), "%x", auinfo.ai_asid);
+ setenv("SECURITYSESSIONID", session, 1);
+ } else {
+ syslog(LOG_NOTICE, "%s: Setting Audit Session ID failed: %d", prog, errno);
+ }
+ }
+#endif // !TARGET_OS_EMBEDDED
+ fcntl(r, F_SETFL, 0);
+ fcntl(r, F_SETFD, 1);
+ dup2(r, STDIN_FILENO);
+ if (dupstdout)
+ dup2(r, STDOUT_FILENO);
+ if (dupstderr)
+ dup2(r, STDERR_FILENO);
+ signal(SIGCHLD, SIG_DFL);
+ execv(prog, argv + 1);
+ syslog(LOG_ERR, "execv(): %m");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+out:
+ exit(ec);
+}
--- /dev/null
+/*
+ * Copyright (c) 2005 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 <sys/types.h>
+#include <sys/stat.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+int main(int argc, char *argv[])
+{
+ int kq = kqueue();
+ struct kevent kev;
+ struct stat sb;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s <object on mount point>\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ EV_SET(&kev, 0, EVFILT_FS, EV_ADD, 0, 0, 0);
+
+ if (kevent(kq, &kev, 1, NULL, 0, NULL) == -1) {
+ fprintf(stderr, "adding EVFILT_FS to kqueue failed: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ if (stat(argv[1], &sb) == 0) {
+ exit(EXIT_SUCCESS);
+ }
+
+ for (;;) {
+ kevent(kq, NULL, 0, &kev, 1, NULL);
+ if (stat(argv[1], &sb) == 0) {
+ break;
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+#include "<DEVELOPER_DIR>/Makefiles/CoreOS/Xcode/BSD.xcconfig"
+
+BUILD_XCSUPPORT_DIR = $(PROJECT_DIR)/xcsupport
+BUILD_XCSCRIPTS_DIR = $(PROJECT_DIR)/xcscripts
+BUILD_XCCONFIG_DIR = $(PROJECT_DIR)/xcconfig
+
+CRASHREPORTER_LINKER_FLAGS[sdk=macosx*]= -lCrashReporterClient
+CRASHREPORTER_LINKER_FLAGS[sdk=macosx10.6] =
+CRASHREPORTER_LINKER_FLAGS[sdk=iphoneos*] =
+
+COPY_PHASE_STRIP = YES
+STRIP_STYLE = non-global
+
+VERSIONING_SYSTEM = apple-generic
+CURRENT_PROJECT_VERSION = $(RC_ProjectSourceVersion)
+DEAD_CODE_STRIPPING = YES
+DEBUG_INFORMATION_FORMAT = dwarf-with-dsym
+GCC_SYMBOLS_PRIVATE_EXTERN = YES
+GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES
+GCC_TREAT_WARNINGS_AS_ERRORS = YES
+GCC_WARN_ABOUT_MISSING_PROTOTYPES =
+GCC_WARN_SHADOW = YES
+INSTALL_MODE_FLAG = ugo-w,a+rX
+OTHER_CFLAGS = -D__MigTypeCheck=1 -Dmig_external=__private_extern__ -D_DARWIN_USE_64_BIT_INODE=1
+WARNING_CFLAGS = -Wall -Wextra -Waggregate-return
+HEADER_SEARCH_PATHS = $(SDKROOT)/System/Library/Frameworks/System.framework/PrivateHeaders $(PROJECT_DIR)/src $(PROJECT_DIR)/liblaunch
+USE_HEADERMAP = NO
--- /dev/null
+#include "launchd.xcconfig"
+
+INSTALL_PATH = /bin
+PRODUCT_NAME = launchctl
+// launchctl doesn't need to redirect osx_assumes() output.
+OTHER_LDFLAGS =
--- /dev/null
+#include "common.xcconfig"
+
+// Xcode-defined flags.
+ARCHS = $(ARCHS_STANDARD_32_64_BIT)
+ONLY_ACTIVE_ARCH = NO
+BUILD_VARIANTS = normal
+DEBUG_INFORMATION_FORMAT = dwarf-with-dsym
+GCC_TREAT_WARNINGS_AS_ERRORS = YES
+DEPLOYMENT_LOCATION = YES
+MACH_O_TYPE = mh_execute
+INSTALL_PATH = /sbin
+PRODUCT_NAME = launchd
+ALWAYS_SEARCH_USER_PATHS = NO
+GCC_ENABLE_BUILTIN_FUNCTIONS = YES
+OTHER_CFLAGS = $(OTHER_CFLAGS) -DXPC_BUILDING_LAUNCHD=1
+OTHER_MIGFLAGS = -DXPC_BUILDING_LAUNCHD=1 -I$(PROJECT_DIR)/src -I$(SDKROOT)/usr/local/include
+OTHER_LDFLAGS = -sectcreate __TEXT __osx_log_func $(BUILD_XCSUPPORT_DIR)/osx_redirect_name
--- /dev/null
+#include "common.xcconfig"
+
+// Xcode-defined flags.
+ARCHS = $(ARCHS_STANDARD_32_64_BIT)
+SUPPORTED_PLATFORMS = macosx iphoneos
+ONLY_ACTIVE_ARCH = NO
+BUILD_VARIANTS = normal debug
+DEBUG_INFORMATION_FORMAT = dwarf-with-dsym
+GCC_TREAT_WARNINGS_AS_ERRORS = YES
+DYLIB_CURRENT_VERSION = $(RC_ProjectSourceVersion)
+DYLIB_COMPATIBILITY_VERSION = 1
+DEPLOYMENT_LOCATION = YES
+INSTALL_PATH = /usr/lib/system
+ORDER_FILE[sdk=macosx*] = $(SDKROOT)/$(APPLE_INTERNAL_DIR)/OrderFiles/liblaunch.order
+ORDER_FILE[sdk=iphonesimulator*] =
+OTHER_LDFLAGS = -umbrella System $(CRASHREPORTER_LINKER_FLAGS)
+LD_DYLIB_INSTALL_NAME = $(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)
+MACH_O_TYPE = mh_dylib
+EXECUTABLE_PREFIX = lib
+PRODUCT_NAME = launch
+PUBLIC_HEADERS_FOLDER_PATH = /usr/include
+PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include
+ALWAYS_SEARCH_USER_PATHS = NO
+GCC_ENABLE_BUILTIN_FUNCTIONS = YES
+GCC_PREPROCESSOR_DEFINITIONS_normal = __MigTypeCheck=1 mig_external=__private_extern__
+GCC_PREPROCESSOR_DEFINITIONS_debug = $(GCC_PREPROCESSOR_DEFINITIONS_normal)
+OTHER_CFLAGS_normal = -DXPC_BUILDING_LAUNCHD=1 -D__DARWIN_NON_CANCELABLE=1 -D_DARWIN_USE_64_BIT_INODE=1
+OTHER_CFLAGS_debug = $(OTHER_CFLAGS_normal) -D__LAUNCH_DEBUG__=1 -O0
+VERSION_INFO_PREFIX = _
+USE_HEADERMAP = NO
--- /dev/null
+#!/bin/sh
+
+install -o "$INSTALL_OWNER" -g "$INSTALL_GROUP" -m 0755 -d "$DSTROOT"/System/Library/StartupItems
+install -o "$INSTALL_OWNER" -g "$INSTALL_GROUP" -m 0755 -d "$DSTROOT"/Library/StartupItems
--- /dev/null
+#!/bin/sh
+
+install -o "$INSTALL_OWNER" -g "$INSTALL_GROUP" -m 0755 -d "$DSTROOT"/System/Library/LaunchAgents
+install -o "$INSTALL_OWNER" -g "$INSTALL_GROUP" -m 0755 -d "$DSTROOT"/System/Library/LaunchDaemons
+install -o "$INSTALL_OWNER" -g "$INSTALL_GROUP" -m 0755 -d "$DSTROOT"/Library/LaunchAgents
+install -o "$INSTALL_OWNER" -g "$INSTALL_GROUP" -m 0755 -d "$DSTROOT"/Library/LaunchDaemons
--- /dev/null
+#!/bin/sh
+
+mkdir -p "$DSTROOT/private/var/db/launchd.db/com.apple.launchd"
+chown root:wheel "$DSTROOT/private/var/db/launchd.db"
+chown root:wheel "$DSTROOT/private/var/db/launchd.db/com.apple.launchd"
+
+mkdir -p "$DSTROOT/private/var/log/com.apple.launchd"
+chown root:wheel "$DSTROOT/private/var/log/com.apple.launchd"
+
+# These directories need to be here to satisfy certain third-party dependencies.
+mkdir -p "$DSTROOT/private/etc/mach_init.d"
+mkdir -p "$DSTROOT/private/etc/mach_init_per_user.d"
+mkdir -p "$DSTROOT/private/etc/mach_init_per_login_session.d"
+chown root:wheel "$DSTROOT/private/etc/mach_init.d"
+chown root:wheel "$DSTROOT/private/etc/mach_init_per_user.d"
+chown root:wheel "$DSTROOT/private/etc/mach_init_per_login_session.d"
--- /dev/null
+#!/bin/sh
+
+install -o "$INSTALL_OWNER" -g "$INSTALL_GROUP" -m 0755 -d "$DSTROOT"/usr/include/servers
+mv "$DSTROOT"/usr/include/bootstrap.h "$DSTROOT"/usr/include/servers/
+ln -sf bootstrap.h "$DSTROOT"/usr/include/servers/bootstrap_defs.h
--- /dev/null
+_launchd_osx_redirect
\ No newline at end of file