/ src / launchd / SystemStarter / StartupItems.c
StartupItems.c
   1  /**
   2   * StartupItems.c - Startup Item management routines
   3   * Wilfredo Sanchez  | wsanchez@opensource.apple.com
   4   * Kevin Van Vechten | kevinvv@uclink4.berkeley.edu
   5   * $Apple$
   6   **
   7   * Copyright (c) 1999-2002 Apple Computer, Inc. All rights reserved.
   8   *
   9   * @APPLE_APACHE_LICENSE_HEADER_START@
  10   * 
  11   * Licensed under the Apache License, Version 2.0 (the "License");
  12   * you may not use this file except in compliance with the License.
  13   * You may obtain a copy of the License at
  14   * 
  15   *     http://www.apache.org/licenses/LICENSE-2.0
  16   * 
  17   * Unless required by applicable law or agreed to in writing, software
  18   * distributed under the License is distributed on an "AS IS" BASIS,
  19   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20   * See the License for the specific language governing permissions and
  21   * limitations under the License.
  22   * 
  23   * @APPLE_APACHE_LICENSE_HEADER_END@
  24   **/
  25  
  26  #include <unistd.h>
  27  #include <sys/types.h>
  28  #include <sys/stat.h>
  29  #include <sys/sysctl.h>
  30  #include <sys/mman.h>
  31  #include <stdlib.h>
  32  #include <fcntl.h>
  33  #include <dirent.h>
  34  #include <limits.h>
  35  #include <errno.h>
  36  #include <string.h>
  37  #include <sysexits.h>
  38  #include <syslog.h>
  39  #include <CoreFoundation/CoreFoundation.h>
  40  #include "StartupItems.h"
  41  
  42  #define kStartupItemsPath	"/StartupItems"
  43  #define kParametersFile		"StartupParameters.plist"
  44  #define kDisabledFile		".disabled"
  45  
  46  #define kRunSuccess CFSTR("success")
  47  #define kRunFailure CFSTR("failure")
  48  
  49  static const char *argumentForAction(Action anAction)
  50  {
  51  	switch (anAction) {
  52  	case kActionStart:
  53  		return "start";
  54  	case kActionStop:
  55  		return "stop";
  56  	case kActionRestart:
  57  		return "restart";
  58  	default:
  59  		return NULL;
  60  	}
  61  }
  62  
  63  #define checkTypeOfValue(aKey,aTypeID)					\
  64    {									\
  65      CFStringRef aProperty = CFDictionaryGetValue(aConfig, aKey);	\
  66      if (aProperty && CFGetTypeID(aProperty) != aTypeID)			\
  67        return FALSE;							\
  68    }
  69  
  70  static int StartupItemValidate(CFDictionaryRef aConfig)
  71  {
  72  	if (aConfig && CFGetTypeID(aConfig) == CFDictionaryGetTypeID()) {
  73  		checkTypeOfValue(kProvidesKey, CFArrayGetTypeID());
  74  		checkTypeOfValue(kRequiresKey, CFArrayGetTypeID());
  75  
  76  		return TRUE;
  77  	}
  78  	return FALSE;
  79  }
  80  
  81  /*
  82   *	remove item from waiting list
  83   */
  84  void RemoveItemFromWaitingList(StartupContext aStartupContext, CFMutableDictionaryRef anItem)
  85  {
  86  	/* Remove the item from the waiting list. */
  87  	if (aStartupContext && anItem && aStartupContext->aWaitingList) {
  88  		CFRange aRange = { 0, CFArrayGetCount(aStartupContext->aWaitingList) };
  89  		CFIndex anIndex = CFArrayGetFirstIndexOfValue(aStartupContext->aWaitingList, aRange, anItem);
  90  
  91  		if (anIndex >= 0) {
  92  			CFArrayRemoveValueAtIndex(aStartupContext->aWaitingList, anIndex);
  93  		}
  94  	}
  95  }
  96  
  97  /*
  98   *	add item to failed list, create list if it doesn't exist
  99   *	return and fail quietly if it can't create list
 100   */
 101  void AddItemToFailedList(StartupContext aStartupContext, CFMutableDictionaryRef anItem)
 102  {
 103  	if (aStartupContext && anItem) {
 104  		/* create the failed list if it doesn't exist */
 105  		if (!aStartupContext->aFailedList) {
 106  			aStartupContext->aFailedList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
 107  		}
 108  		if (aStartupContext->aFailedList) {
 109  			CFArrayAppendValue(aStartupContext->aFailedList, anItem);
 110  		}
 111  	}
 112  }
 113  
 114  /**
 115   * startupItemListCopyMatches returns an array of items which contain the string aService in the key aKey
 116   **/
 117  static CFMutableArrayRef startupItemListCopyMatches(CFArrayRef anItemList, CFStringRef aKey, CFStringRef aService)
 118  {
 119  	CFMutableArrayRef aResult = NULL;
 120  
 121  	if (anItemList && aKey && aService) {
 122  		CFIndex anItemCount = CFArrayGetCount(anItemList);
 123  		CFIndex anItemIndex = 0;
 124  
 125  		aResult = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
 126  
 127  		for (anItemIndex = 0; anItemIndex < anItemCount; ++anItemIndex) {
 128  			CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(anItemList, anItemIndex);
 129  			CFArrayRef aList = CFDictionaryGetValue(anItem, aKey);
 130  
 131  			if (aList) {
 132  				if (CFArrayContainsValue(aList, CFRangeMake(0, CFArrayGetCount(aList)), aService) &&
 133  				    !CFArrayContainsValue(aResult, CFRangeMake(0, CFArrayGetCount(aResult)), anItem)) {
 134  					CFArrayAppendValue(aResult, anItem);
 135  				}
 136  			}
 137  		}
 138  	}
 139  	return aResult;
 140  }
 141  
 142  static void SpecialCasesStartupItemHandler(CFMutableDictionaryRef aConfig)
 143  {
 144  	static const CFStringRef stubitems[] = {
 145  		CFSTR("Accounting"),
 146  		CFSTR("System Tuning"),
 147  		CFSTR("SecurityServer"),
 148  		CFSTR("Portmap"),
 149  		CFSTR("System Log"),
 150  		CFSTR("Resolver"),
 151  		CFSTR("LDAP"),
 152  		CFSTR("NetInfo"),
 153  		CFSTR("NetworkExtensions"),
 154  		CFSTR("DirectoryServices"),
 155  		CFSTR("Network Configuration"),
 156  		CFSTR("mDNSResponder"),
 157  		CFSTR("Cron"),
 158  		CFSTR("Core Graphics"),
 159  		CFSTR("Core Services"),
 160  		CFSTR("Network"),
 161  		CFSTR("TIM"),
 162  		CFSTR("Disks"),
 163  		CFSTR("NIS"),
 164  		NULL
 165  	};
 166  	CFMutableArrayRef aList, aNewList;
 167  	CFIndex i, aCount;
 168  	CFStringRef ci, type = kRequiresKey;
 169  	const CFStringRef *c;
 170  
 171        again:
 172  	aList = (CFMutableArrayRef) CFDictionaryGetValue(aConfig, type);
 173  	if (aList) {
 174  		aCount = CFArrayGetCount(aList);
 175  
 176  		aNewList = CFArrayCreateMutable(kCFAllocatorDefault, aCount, &kCFTypeArrayCallBacks);
 177  
 178  		for (i = 0; i < aCount; i++) {
 179  			ci = CFArrayGetValueAtIndex(aList, i);
 180  			CF_syslog(LOG_DEBUG, CFSTR("%@: Evaluating %@"), type, ci);
 181  			for (c = stubitems; *c; c++) {
 182  				if (CFEqual(*c, ci))
 183  					break;
 184  			}
 185  			if (*c == NULL) {
 186  				CFArrayAppendValue(aNewList, ci);
 187  				CF_syslog(LOG_DEBUG, CFSTR("%@: Keeping %@"), type, ci);
 188  			}
 189  		}
 190  
 191  		CFDictionaryReplaceValue(aConfig, type, aNewList);
 192  		CFRelease(aNewList);
 193  	}
 194  	if (type == kUsesKey)
 195  		return;
 196  	type = kUsesKey;
 197  	goto again;
 198  }
 199  
 200  CFIndex StartupItemListCountServices(CFArrayRef anItemList)
 201  {
 202  	CFIndex aResult = 0;
 203  
 204  	if (anItemList) {
 205  		CFIndex anItemCount = CFArrayGetCount(anItemList);
 206  		CFIndex anItemIndex = 0;
 207  
 208  		for (anItemIndex = 0; anItemIndex < anItemCount; ++anItemIndex) {
 209  			CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
 210  			CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
 211  
 212  			if (aProvidesList)
 213  				aResult += CFArrayGetCount(aProvidesList);
 214  		}
 215  	}
 216  	return aResult;
 217  }
 218  
 219  bool StartupItemSecurityCheck(const char *aPath)
 220  {
 221  	static struct timeval boot_time;
 222  	struct stat aStatBuf;
 223  	bool r = true;
 224  
 225  	if (boot_time.tv_sec == 0) {
 226  		int mib[] = { CTL_KERN, KERN_BOOTTIME };
 227  		size_t boot_time_sz = sizeof(boot_time);
 228  		int rv;
 229  
 230  		rv = sysctl(mib, sizeof(mib) / sizeof(mib[0]), &boot_time, &boot_time_sz, NULL, 0);
 231  
 232  		assert(rv != -1);
 233  		assert(boot_time_sz == sizeof(boot_time));
 234  	}
 235  
 236  	/* should use lstatx_np() on Tiger? */
 237  	if (lstat(aPath, &aStatBuf) == -1) {
 238  		if (errno != ENOENT)
 239  			syslog(LOG_ERR, "lstat(\"%s\"): %m", aPath);
 240  		return false;
 241  	}
 242  	/*
 243  	 * We check the boot time because of 5409386.
 244  	 * We ignore the boot time if PPID != 1 because of 5503536.
 245  	 */
 246  	if ((aStatBuf.st_ctimespec.tv_sec > boot_time.tv_sec) && (getppid() == 1)) {
 247  		syslog(LOG_WARNING, "\"%s\" failed sanity check: path was created after boot up", aPath);
 248  		return false;
 249  	}
 250  	if (!(S_ISREG(aStatBuf.st_mode) || S_ISDIR(aStatBuf.st_mode))) {
 251  		syslog(LOG_WARNING, "\"%s\" failed security check: not a directory or regular file", aPath);
 252  		r = false;
 253  	}
 254  	if (aStatBuf.st_mode & S_IWOTH) {
 255  		syslog(LOG_WARNING, "\"%s\" failed security check: world writable", aPath);
 256  		r = false;
 257  	}
 258  	if (aStatBuf.st_mode & S_IWGRP) {
 259  		syslog(LOG_WARNING, "\"%s\" failed security check: group writable", aPath);
 260  		r = false;
 261  	}
 262  	if (aStatBuf.st_uid != 0) {
 263  		syslog(LOG_WARNING, "\"%s\" failed security check: not owned by UID 0", aPath);
 264  		r = false;
 265  	}
 266  	if (aStatBuf.st_gid != 0) {
 267  		syslog(LOG_WARNING, "\"%s\" failed security check: not owned by GID 0", aPath);
 268  		r = false;
 269  	}
 270  	if (r == false) {
 271  		mkdir(kFixerDir, ACCESSPERMS);
 272  		close(open(kFixerPath, O_RDWR|O_CREAT|O_NOCTTY, DEFFILEMODE));
 273  	}
 274  	return r;
 275  }
 276  
 277  CFMutableArrayRef StartupItemListCreateWithMask(NSSearchPathDomainMask aMask)
 278  {
 279  	CFMutableArrayRef anItemList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
 280  
 281  	char aPath[PATH_MAX];
 282  	CFIndex aDomainIndex = 0;
 283  
 284  	NSSearchPathEnumerationState aState = NSStartSearchPathEnumeration(NSLibraryDirectory, aMask);
 285  
 286  	while ((aState = NSGetNextSearchPathEnumeration(aState, aPath))) {
 287  		DIR *aDirectory;
 288  
 289  		strlcat(aPath, kStartupItemsPath, sizeof(aPath));
 290  		++aDomainIndex;
 291  
 292  		/* 5485016
 293  		 *
 294  		 * Just in case...
 295  		 */
 296  		mkdir(aPath, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
 297  
 298  		if (!StartupItemSecurityCheck(aPath))
 299  			continue;
 300  
 301  		if ((aDirectory = opendir(aPath))) {
 302  			struct dirent *aBundle;
 303  
 304  			while ((aBundle = readdir(aDirectory))) {
 305  				struct stat aStatBuf;
 306  				char *aBundleName = aBundle->d_name;
 307  				char aBundlePath[PATH_MAX];
 308  				char aBundleExecutablePath[PATH_MAX];
 309  				char aConfigFile[PATH_MAX];
 310  				char aDisabledFile[PATH_MAX];
 311  
 312  				if (aBundleName[0] == '.')
 313  					continue;
 314  
 315  				syslog(LOG_DEBUG, "Found item: %s", aBundleName);
 316  
 317  				sprintf(aBundlePath, "%s/%s", aPath, aBundleName);
 318  				sprintf(aBundleExecutablePath, "%s/%s", aBundlePath, aBundleName);
 319  				sprintf(aConfigFile, "%s/%s", aBundlePath, kParametersFile);
 320  				sprintf(aDisabledFile, "%s/%s", aBundlePath, kDisabledFile);
 321  
 322  				if (lstat(aDisabledFile, &aStatBuf) == 0) {
 323  					syslog(LOG_NOTICE, "Skipping disabled StartupItem: %s", aBundlePath);
 324  					continue;
 325  				}
 326  				if (!StartupItemSecurityCheck(aBundlePath))
 327  					continue;
 328  				if (!StartupItemSecurityCheck(aBundleExecutablePath))
 329  					continue;
 330  				if (!StartupItemSecurityCheck(aConfigFile))
 331  					continue;
 332  
 333  				/* Stow away the plist data for each bundle */
 334  				{
 335  					int aConfigFileDescriptor;
 336  
 337  					if ((aConfigFileDescriptor = open(aConfigFile, O_RDONLY|O_NOCTTY, (mode_t) 0)) != -1) {
 338  						struct stat aConfigFileStatBuffer;
 339  
 340  						if (stat(aConfigFile, &aConfigFileStatBuffer) != -1) {
 341  							off_t aConfigFileContentsSize = aConfigFileStatBuffer.st_size;
 342  							char *aConfigFileContentsBuffer;
 343  
 344  							if ((aConfigFileContentsBuffer =
 345  							     mmap((caddr_t) 0, aConfigFileContentsSize,
 346  								  PROT_READ, MAP_FILE | MAP_PRIVATE,
 347  								  aConfigFileDescriptor, (off_t) 0)) != (caddr_t) - 1) {
 348  								CFDataRef aConfigData = NULL;
 349  								CFMutableDictionaryRef aConfig = NULL;
 350  
 351  								aConfigData =
 352  									CFDataCreateWithBytesNoCopy(NULL,
 353  												    (const UInt8 *)aConfigFileContentsBuffer,
 354  												    aConfigFileContentsSize,
 355  												    kCFAllocatorNull);
 356  
 357  								if (aConfigData) {
 358  									aConfig = (CFMutableDictionaryRef)
 359  									    CFPropertyListCreateFromXMLData(NULL, aConfigData,
 360  													    kCFPropertyListMutableContainers,
 361  													    NULL);
 362  								}
 363  								if (StartupItemValidate(aConfig)) {
 364  									CFStringRef aBundlePathString =
 365  									    CFStringCreateWithCString(NULL, aBundlePath,
 366  												      kCFStringEncodingUTF8);
 367  
 368  									CFNumberRef aDomainNumber =
 369  									    CFNumberCreate(NULL, kCFNumberCFIndexType,
 370  											   &aDomainIndex);
 371  
 372  									CFDictionarySetValue(aConfig, kBundlePathKey,
 373  											     aBundlePathString);
 374  									CFDictionarySetValue(aConfig, kDomainKey, aDomainNumber);
 375  									CFRelease(aDomainNumber);
 376  									SpecialCasesStartupItemHandler(aConfig);
 377  									CFArrayAppendValue(anItemList, aConfig);
 378  
 379  									CFRelease(aBundlePathString);
 380  								} else {
 381  									syslog(LOG_ERR, "Malformatted parameters file: %s",
 382  									       aConfigFile);
 383  								}
 384  
 385  								if (aConfig)
 386  									CFRelease(aConfig);
 387  								if (aConfigData)
 388  									CFRelease(aConfigData);
 389  
 390  								if (munmap(aConfigFileContentsBuffer, aConfigFileContentsSize) ==
 391  								    -1) {
 392  									syslog(LOG_WARNING,
 393  									       "Unable to unmap parameters file %s for item %s: %m",
 394  									       aConfigFile, aBundleName);
 395  								}
 396  							} else {
 397  								syslog(LOG_ERR,
 398  								       "Unable to map parameters file %s for item %s: %m",
 399  								       aConfigFile, aBundleName);
 400  							}
 401  						} else {
 402  							syslog(LOG_ERR, "Unable to stat parameters file %s for item %s: %m",
 403  							       aConfigFile, aBundleName);
 404  						}
 405  
 406  						if (close(aConfigFileDescriptor) == -1) {
 407  							syslog(LOG_ERR, "Unable to close parameters file %s for item %s: %m",
 408  							       aConfigFile, aBundleName);
 409  						}
 410  					} else {
 411  						syslog(LOG_ERR, "Unable to open parameters file %s for item %s: %m", aConfigFile,
 412  						       aBundleName);
 413  					}
 414  				}
 415  			}
 416  			if (closedir(aDirectory) == -1) {
 417  				syslog(LOG_WARNING, "Unable to directory bundle %s: %m", aPath);
 418  			}
 419  		} else {
 420  			if (errno != ENOENT) {
 421  				syslog(LOG_WARNING, "Open on directory %s failed: %m", aPath);
 422  				return (NULL);
 423  			}
 424  		}
 425  	}
 426  
 427  	return anItemList;
 428  }
 429  
 430  CFMutableDictionaryRef StartupItemListGetProvider(CFArrayRef anItemList, CFStringRef aService)
 431  {
 432  	CFMutableDictionaryRef aResult = NULL;
 433  	CFMutableArrayRef aList = startupItemListCopyMatches(anItemList, kProvidesKey, aService);
 434  
 435  	if (aList && CFArrayGetCount(aList) > 0)
 436  		aResult = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aList, 0);
 437  
 438  	if (aList) CFRelease(aList);
 439  
 440  	return aResult;
 441  }
 442  
 443  CFArrayRef StartupItemListCreateFromRunning(CFArrayRef anItemList)
 444  {
 445  	CFMutableArrayRef aResult = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
 446  	if (aResult) {
 447  		CFIndex anIndex, aCount = CFArrayGetCount(anItemList);
 448  		for (anIndex = 0; anIndex < aCount; ++anIndex) {
 449  			CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anIndex);
 450  			if (anItem) {
 451  				CFNumberRef aPID = CFDictionaryGetValue(anItem, kPIDKey);
 452  				if (aPID)
 453  					CFArrayAppendValue(aResult, anItem);
 454  			}
 455  		}
 456  	}
 457  	return aResult;
 458  }
 459  
 460  /*
 461   * Append items in anItemList to aDependents which depend on
 462   *  aParentItem.
 463   * If anAction is kActionStart, dependent items are those which
 464   *  require any service provided by aParentItem.
 465   * If anAction is kActionStop, dependent items are those which provide
 466   *  any service required by aParentItem.
 467   */
 468  static void appendDependents(CFMutableArrayRef aDependents, CFArrayRef anItemList, CFDictionaryRef aParentItem, Action anAction)
 469  {
 470  	CFStringRef anInnerKey, anOuterKey;
 471  	CFArrayRef anOuterList;
 472  
 473  	/* Append the parent item to the list (avoiding duplicates) */
 474  	if (!CFArrayContainsValue(aDependents, CFRangeMake(0, CFArrayGetCount(aDependents)), aParentItem))
 475  		CFArrayAppendValue(aDependents, aParentItem);
 476  
 477  	/**
 478           * Recursively append any children of the parent item for kStartAction and kStopAction.
 479           * Do nothing for other actions.
 480           **/
 481  	switch (anAction) {
 482  	case kActionStart:
 483  		anInnerKey = kProvidesKey;
 484  		anOuterKey = kRequiresKey;
 485  		break;
 486  	case kActionStop:
 487  		anInnerKey = kRequiresKey;
 488  		anOuterKey = kProvidesKey;
 489  		break;
 490  	default:
 491  		return;
 492  	}
 493  
 494  	anOuterList = CFDictionaryGetValue(aParentItem, anOuterKey);
 495  
 496  	if (anOuterList) {
 497  		CFIndex anOuterCount = CFArrayGetCount(anOuterList);
 498  		CFIndex anOuterIndex;
 499  
 500  		for (anOuterIndex = 0; anOuterIndex < anOuterCount; anOuterIndex++) {
 501  			CFStringRef anOuterElement = CFArrayGetValueAtIndex(anOuterList, anOuterIndex);
 502  			CFIndex anItemCount = CFArrayGetCount(anItemList);
 503  			CFIndex anItemIndex;
 504  
 505  			for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
 506  				CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
 507  				CFArrayRef anInnerList = CFDictionaryGetValue(anItem, anInnerKey);
 508  
 509  				if (anInnerList &&
 510  				    CFArrayContainsValue(anInnerList, CFRangeMake(0, CFArrayGetCount(anInnerList)),
 511  							 anOuterElement)
 512  				    && !CFArrayContainsValue(aDependents, CFRangeMake(0, CFArrayGetCount(aDependents)), anItem))
 513  					appendDependents(aDependents, anItemList, anItem, anAction);
 514  			}
 515  		}
 516  	}
 517  }
 518  
 519  CFMutableArrayRef StartupItemListCreateDependentsList(CFMutableArrayRef anItemList, CFStringRef aService, Action anAction)
 520  {
 521  	CFMutableArrayRef aDependents = NULL;
 522  	CFMutableDictionaryRef anItem = NULL;
 523  
 524  	if (anItemList && aService)
 525  		anItem = StartupItemListGetProvider(anItemList, aService);
 526  
 527  	if (anItem) {
 528  		switch (anAction) {
 529  		case kActionRestart:
 530  		case kActionStart:
 531  		case kActionStop:
 532  			aDependents = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
 533  
 534  			if (!aDependents) {
 535  				CF_syslog(LOG_EMERG, CFSTR("Failed to allocate dependancy list for item %@"), anItem);
 536  				return NULL;
 537  			}
 538  			appendDependents(aDependents, anItemList, anItem, anAction);
 539  			break;
 540  
 541  		default:
 542  			break;
 543  		}
 544  	}
 545  	return aDependents;
 546  }
 547  
 548  /**
 549   * countUnmetRequirements counts the number of items in anItemList
 550   * which are pending in aStatusDict.
 551   **/
 552  static int countUnmetRequirements(CFDictionaryRef aStatusDict, CFArrayRef anItemList)
 553  {
 554  	int aCount = 0;
 555  	CFIndex anItemCount = CFArrayGetCount(anItemList);
 556  	CFIndex anItemIndex;
 557  
 558  	for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
 559  		CFStringRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
 560  		CFStringRef aStatus = CFDictionaryGetValue(aStatusDict, anItem);
 561  
 562  		if (!aStatus || !CFEqual(aStatus, kRunSuccess)) {
 563  			CF_syslog(LOG_DEBUG, CFSTR("\tFailed requirement/uses: %@"), anItem);
 564  			aCount++;
 565  		}
 566  	}
 567  
 568  	return aCount;
 569  }
 570  
 571  /**
 572   * countDependantsPresent counts the number of items in aWaitingList
 573   * which depend on items in anItemList.
 574   **/
 575  static int countDependantsPresent(CFArrayRef aWaitingList, CFArrayRef anItemList, CFStringRef aKey)
 576  {
 577  	int aCount = 0;
 578  	CFIndex anItemCount = CFArrayGetCount(anItemList);
 579  	CFIndex anItemIndex;
 580  
 581  	for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
 582  		CFStringRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
 583  		CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, aKey, anItem);
 584  
 585  		if (aMatchesList) {
 586  			aCount = aCount + CFArrayGetCount(aMatchesList);
 587  			CFRelease(aMatchesList);
 588  		}
 589  	}
 590  
 591  	return aCount;
 592  }
 593  
 594  /**
 595   * pendingAntecedents returns TRUE if any antecedents of this item
 596   * are currently running, have not yet run, or none exist.
 597   **/
 598  static Boolean
 599  pendingAntecedents(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, CFArrayRef anAntecedentList, Action anAction)
 600  {
 601  	int aPendingFlag = FALSE;
 602  
 603  	CFIndex anAntecedentCount = CFArrayGetCount(anAntecedentList);
 604  	CFIndex anAntecedentIndex;
 605  
 606  	for (anAntecedentIndex = 0; anAntecedentIndex < anAntecedentCount; ++anAntecedentIndex) {
 607  		CFStringRef anAntecedent = CFArrayGetValueAtIndex(anAntecedentList, anAntecedentIndex);
 608  		CFStringRef aKey = (anAction == kActionStart) ? kProvidesKey : kUsesKey;
 609  		CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, aKey, anAntecedent);
 610  
 611  		if (aMatchesList) {
 612  			CFIndex aMatchesListCount = CFArrayGetCount(aMatchesList);
 613  			CFIndex aMatchesListIndex;
 614  
 615  			for (aMatchesListIndex = 0; aMatchesListIndex < aMatchesListCount; ++aMatchesListIndex) {
 616  				CFDictionaryRef anItem = CFArrayGetValueAtIndex(aMatchesList, aMatchesListIndex);
 617  
 618  				if (!anItem ||
 619  				    !CFDictionaryGetValue(anItem, kPIDKey) || !CFDictionaryGetValue(aStatusDict, anAntecedent)) {
 620  					aPendingFlag = TRUE;
 621  					break;
 622  				}
 623  			}
 624  
 625  			CFRelease(aMatchesList);
 626  
 627  			if (aPendingFlag)
 628  				break;
 629  		}
 630  	}
 631  	return (aPendingFlag);
 632  }
 633  
 634  /**
 635   * checkForDuplicates returns TRUE if an item provides the same service as a
 636   * pending item, or an item that already succeeded.
 637   **/
 638  static Boolean checkForDuplicates(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, CFDictionaryRef anItem)
 639  {
 640  	int aDuplicateFlag = FALSE;
 641  
 642  	CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
 643  	CFIndex aProvidesCount = aProvidesList ? CFArrayGetCount(aProvidesList) : 0;
 644  	CFIndex aProvidesIndex;
 645  
 646  	for (aProvidesIndex = 0; aProvidesIndex < aProvidesCount; ++aProvidesIndex) {
 647  		CFStringRef aProvides = CFArrayGetValueAtIndex(aProvidesList, aProvidesIndex);
 648  
 649  		/* If the service succeeded, return true. */
 650  		CFStringRef aStatus = CFDictionaryGetValue(aStatusDict, aProvides);
 651  		if (aStatus && CFEqual(aStatus, kRunSuccess)) {
 652  			aDuplicateFlag = TRUE;
 653  			break;
 654  		}
 655  		/*
 656  		 * Otherwise test if any item is currently running which
 657  		 * might provide that service.
 658  		 */
 659  		else {
 660  			CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, kProvidesKey, aProvides);
 661  			if (aMatchesList) {
 662  				CFIndex aMatchesListCount = CFArrayGetCount(aMatchesList);
 663  				CFIndex aMatchesListIndex;
 664  
 665  				for (aMatchesListIndex = 0; aMatchesListIndex < aMatchesListCount; ++aMatchesListIndex) {
 666  					CFDictionaryRef anDupItem = CFArrayGetValueAtIndex(aMatchesList, aMatchesListIndex);
 667  					if (anDupItem && CFDictionaryGetValue(anDupItem, kPIDKey)) {
 668  						/*
 669  						 * Item is running, avoid
 670  						 * race condition.
 671  						 */
 672  						aDuplicateFlag = TRUE;
 673  						break;
 674  					} else {
 675  						CFNumberRef anItemDomain = CFDictionaryGetValue(anItem, kDomainKey);
 676  						CFNumberRef anotherItemDomain = CFDictionaryGetValue(anDupItem, kDomainKey);
 677  						/*
 678  						 * If anItem was found later
 679  						 * than aDupItem, stall
 680  						 * anItem until aDupItem
 681  						 * runs.
 682  						 */
 683  						if (anItemDomain &&
 684  						    anotherItemDomain &&
 685  						    CFNumberCompare(anItemDomain, anotherItemDomain,
 686  								    NULL) == kCFCompareGreaterThan) {
 687  							/*
 688  							 * Item not running,
 689  							 * but takes
 690  							 * precedence.
 691  							 */
 692  							aDuplicateFlag = TRUE;
 693  							break;
 694  						}
 695  					}
 696  				}
 697  
 698  				CFRelease(aMatchesList);
 699  				if (aDuplicateFlag)
 700  					break;
 701  			}
 702  		}
 703  	}
 704  	return (aDuplicateFlag);
 705  }
 706  
 707  CFMutableDictionaryRef StartupItemListGetNext(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, Action anAction)
 708  {
 709  	CFMutableDictionaryRef aNextItem = NULL;
 710  	CFIndex aWaitingCount = CFArrayGetCount(aWaitingList);
 711  	int aMinFailedAntecedents = INT_MAX;
 712  	CFIndex aWaitingIndex;
 713  
 714  	switch (anAction) {
 715  	case kActionStart:
 716  		break;
 717  	case kActionStop:
 718  		break;
 719  	case kActionRestart:
 720  		break;
 721  	default:
 722  		return NULL;
 723  	}
 724  
 725  	if (!aWaitingList || !aStatusDict || aWaitingCount <= 0)
 726  		return NULL;
 727  
 728  	/**
 729           * Iterate through the items in aWaitingList and look for an optimally ready item.
 730           **/
 731  	for (aWaitingIndex = 0; aWaitingIndex < aWaitingCount; aWaitingIndex++) {
 732  		CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aWaitingList, aWaitingIndex);
 733  		CFArrayRef anAntecedentList;
 734  		int aFailedAntecedentsCount = 0;	/* Number of unmet soft
 735  							 * depenancies */
 736  		Boolean aBestPick = FALSE;	/* Is this the best pick
 737  						 * so far?    */
 738  
 739  		/* Filter out running items. */
 740  		if (CFDictionaryGetValue(anItem, kPIDKey))
 741  			continue;
 742  
 743  		/*
 744  		 * Filter out dupilicate services; if someone has
 745  		 * provided what we provide, we don't run.
 746  		 */
 747  		if (checkForDuplicates(aWaitingList, aStatusDict, anItem)) {
 748  			CF_syslog(LOG_DEBUG, CFSTR("Skipping %@ because of duplicate service."),
 749  				  CFDictionaryGetValue(anItem, kDescriptionKey));
 750  			continue;
 751  		}
 752  		/*
 753  		 * Dependencies don't matter when restarting an item;
 754  		 * stop here.
 755  		 */
 756  		if (anAction == kActionRestart) {
 757  			aNextItem = anItem;
 758  			break;
 759  		}
 760  		anAntecedentList = CFDictionaryGetValue(anItem, ((anAction == kActionStart) ? kRequiresKey : kProvidesKey));
 761  
 762  		CF_syslog(LOG_DEBUG, CFSTR("Checking %@"), CFDictionaryGetValue(anItem, kDescriptionKey));
 763  
 764  		if (anAntecedentList)
 765  			CF_syslog(LOG_DEBUG, CFSTR("Antecedents: %@"), anAntecedentList);
 766  		else
 767  			syslog(LOG_DEBUG, "No antecedents");
 768  
 769  			/**
 770  	                 * Filter out the items which have unsatisfied antecedents.
 771  	                 **/
 772  		if (anAntecedentList &&
 773  		    ((anAction == kActionStart) ?
 774  		     countUnmetRequirements(aStatusDict, anAntecedentList) :
 775  		     countDependantsPresent(aWaitingList, anAntecedentList, kRequiresKey)))
 776  			continue;
 777  
 778  			/**
 779  	                 * anItem has all hard dependancies met; check for soft dependancies.
 780  	                 * We'll favor the item with the fewest unmet soft dependancies here.
 781  	                 **/
 782  		anAntecedentList = CFDictionaryGetValue(anItem, ((anAction == kActionStart) ? kUsesKey : kProvidesKey));
 783  
 784  		if (anAntecedentList)
 785  			CF_syslog(LOG_DEBUG, CFSTR("Soft dependancies: %@"), anAntecedentList);
 786  		else
 787  			syslog(LOG_DEBUG, "No soft dependancies");
 788  
 789  		if (anAntecedentList) {
 790  			aFailedAntecedentsCount =
 791  			    ((anAction == kActionStart) ?
 792  			     countUnmetRequirements(aStatusDict, anAntecedentList) :
 793  			     countDependantsPresent(aWaitingList, anAntecedentList, kUsesKey));
 794  		} else {
 795  			if (aMinFailedAntecedents > 0)
 796  				aBestPick = TRUE;
 797  		}
 798  
 799  		/*
 800  		 * anItem has unmet dependencies that will
 801  		 * likely be met in the future, so delay it
 802  		 */
 803  		if (aFailedAntecedentsCount > 0 && pendingAntecedents(aWaitingList, aStatusDict, anAntecedentList, anAction)) {
 804  			continue;
 805  		}
 806  		if (aFailedAntecedentsCount > 0)
 807  			syslog(LOG_DEBUG, "Total: %d", aFailedAntecedentsCount);
 808  
 809  		if (aFailedAntecedentsCount > aMinFailedAntecedents)
 810  			continue;	/* Another item already won out */
 811  
 812  		if (aFailedAntecedentsCount < aMinFailedAntecedents)
 813  			aBestPick = TRUE;
 814  
 815  		if (!aBestPick)
 816  			continue;
 817  
 818  		/*
 819  		 * anItem has less unmet
 820  		 * dependancies than any
 821  		 * other item so far, so it
 822  		 * wins.
 823  		 */
 824  		syslog(LOG_DEBUG, "Best pick so far, based on failed dependancies (%d->%d)",
 825  		       aMinFailedAntecedents, aFailedAntecedentsCount);
 826  
 827  		/*
 828  		 * We have a winner!  Update success
 829  		 * parameters to match anItem.
 830  		 */
 831  		aMinFailedAntecedents = aFailedAntecedentsCount;
 832  		aNextItem = anItem;
 833  
 834  	}			/* End of waiting list loop. */
 835  
 836  	return aNextItem;
 837  }
 838  
 839  CFStringRef StartupItemCreateDescription(CFMutableDictionaryRef anItem)
 840  {
 841  	CFStringRef aString = NULL;
 842  
 843  	if (anItem)
 844  		aString = CFDictionaryGetValue(anItem, kDescriptionKey);
 845  	if (aString)
 846  		CFRetain(aString);
 847  	return aString;
 848  }
 849  
 850  pid_t StartupItemGetPID(CFDictionaryRef anItem)
 851  {
 852  	CFIndex anItemPID = 0;
 853  	CFNumberRef aPIDNumber = anItem ? CFDictionaryGetValue(anItem, kPIDKey) : NULL;
 854  	if (aPIDNumber && CFNumberGetValue(aPIDNumber, kCFNumberCFIndexType, &anItemPID))
 855  		return (pid_t) anItemPID;
 856  	else
 857  		return 0;
 858  }
 859  
 860  CFMutableDictionaryRef StartupItemWithPID(CFArrayRef anItemList, pid_t aPID)
 861  {
 862  	CFIndex anItemCount = CFArrayGetCount(anItemList);
 863  	CFIndex anItemIndex;
 864  
 865  	for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
 866  		CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(anItemList, anItemIndex);
 867  		CFNumberRef aPIDNumber = CFDictionaryGetValue(anItem, kPIDKey);
 868  		CFIndex anItemPID;
 869  
 870  		if (aPIDNumber) {
 871  			CFNumberGetValue(aPIDNumber, kCFNumberCFIndexType, &anItemPID);
 872  
 873  			if ((pid_t) anItemPID == aPID)
 874  				return anItem;
 875  		}
 876  	}
 877  
 878  	return NULL;
 879  }
 880  
 881  int StartupItemRun(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Action anAction)
 882  {
 883  	int             anError = -1;
 884  	CFArrayRef      aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
 885  	static const CFStringRef stubitems[] = {
 886  		CFSTR("BootROMUpdater"),		/* 3893064 */
 887  		CFSTR("FCUUpdater"),			/* 3893064 */
 888  		CFSTR("AutoProtect Daemon"),		/* 3965785 */
 889  		CFSTR("Check For Missed Tasks"),	/* 3965785 */
 890  		CFSTR("Privacy"),			/* 3933484 */
 891  		CFSTR("Firmware Update Checking"),	/* 4001504 */
 892  
 893  		CFSTR("M-Audio FireWire Audio Support"),		/* 3931757 */
 894  		CFSTR("help for M-Audio Delta Family"),			/* 3931757 */
 895  		CFSTR("help for M-Audio Devices"),			/* 3931757 */
 896  		CFSTR("help for M-Audio Revo 5.1"),			/* 3931757 */
 897  		CFSTR("M-Audio USB Duo Configuration Service"),		/* 3931757 */
 898  		CFSTR("firmware loader for M-Audio devices"),		/* 3931757 */
 899  		CFSTR("M-Audio MobilePre USB Configuration Service"),	/* 3931757 */
 900  		CFSTR("M-Audio OmniStudio USB Configuration Service"),	/* 3931757 */
 901  		CFSTR("M-Audio Transit USB Configuration Service"),	/* 3931757 */
 902  		CFSTR("M-Audio Audiophile USB Configuration Service"),	/* 3931757 */
 903  		NULL
 904  	};
 905  	const CFStringRef *c;
 906  
 907  	if (aProvidesList && anAction == kActionStop) {
 908  		CFIndex aProvidesCount = CFArrayGetCount(aProvidesList);
 909  		for (c = stubitems; *c; c++) {
 910  			if (CFArrayContainsValue(aProvidesList, CFRangeMake(0, aProvidesCount), *c)) {
 911  				CFIndex         aPID = -1;
 912  				CFNumberRef     aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
 913  
 914  				CFDictionarySetValue(anItem, kPIDKey, aProcessNumber);
 915  				CFRelease(aProcessNumber);
 916  
 917  				StartupItemExit(aStatusDict, anItem, TRUE);
 918  				return -1;
 919  			}
 920  		}
 921  	}
 922  
 923  	if (anAction == kActionNone) {
 924  		StartupItemExit(aStatusDict, anItem, TRUE);
 925  		anError = 0;
 926  	} else {
 927  		CFStringRef aBundlePathString = CFDictionaryGetValue(anItem, kBundlePathKey);
 928  		char aBundlePath[PATH_MAX];
 929  		char anExecutable[PATH_MAX];
 930  		char *tmp;
 931  
 932  		if (!CFStringGetCString(aBundlePathString, aBundlePath, sizeof(aBundlePath), kCFStringEncodingUTF8)) {
 933  			CF_syslog(LOG_EMERG, CFSTR("Internal error while running item %@"), aBundlePathString);
 934  			return (anError);
 935  		}
 936  		/* Compute path to excecutable */
 937  		tmp = rindex(aBundlePath, '/');
 938  		snprintf(anExecutable, sizeof(anExecutable), "%s%s", aBundlePath, tmp);
 939  
 940  		/**
 941  	         * Run the bundle
 942  	         **/
 943  
 944  		if (access(anExecutable, X_OK)) {
 945  			/*
 946  			 * Add PID key so that this item is marked as having
 947  			 * been run.
 948  			 */
 949  			CFIndex aPID = -1;
 950  			CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
 951  
 952  			CFDictionarySetValue(anItem, kPIDKey, aProcessNumber);
 953  			CFRelease(aProcessNumber);
 954  
 955  			CFDictionarySetValue(anItem, kErrorKey, kErrorPermissions);
 956  			StartupItemExit(aStatusDict, anItem, FALSE);
 957  			syslog(LOG_ERR, "No executable file %s", anExecutable);
 958  		} else {
 959  			pid_t aProccessID = fork();
 960  
 961  			switch (aProccessID) {
 962  			case -1:	/* SystemStarter (fork failed) */
 963  				CFDictionarySetValue(anItem, kErrorKey, kErrorFork);
 964  				StartupItemExit(aStatusDict, anItem, FALSE);
 965  
 966  				CF_syslog(LOG_ERR, CFSTR("Failed to fork for item %@: %s"), aBundlePathString, strerror(errno));
 967  
 968  				break;
 969  
 970  			default:	/* SystemStarter (fork succeeded) */
 971  				{
 972  					CFIndex aPID = (CFIndex) aProccessID;
 973  					CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
 974  
 975  					CFDictionarySetValue(anItem, kPIDKey, aProcessNumber);
 976  					CFRelease(aProcessNumber);
 977  
 978  					syslog(LOG_DEBUG, "Running command (%d): %s %s",
 979  					       aProccessID, anExecutable, argumentForAction(anAction));
 980  					anError = 0;
 981  				}
 982  				break;
 983  
 984  			case 0:	/* Child */
 985  				{
 986  					if (setsid() == -1)
 987  						syslog(LOG_WARNING, "Unable to create session for item %s: %m", anExecutable);
 988  
 989  					anError = execl(anExecutable, anExecutable, argumentForAction(anAction), NULL);
 990  
 991  					/* We shouldn't get here. */
 992  
 993  					syslog(LOG_ERR, "execl(\"%s\"): %m", anExecutable);
 994  
 995  					exit(anError);
 996  				}
 997  			}
 998  		}
 999  	}
1000  
1001  	return (anError);
1002  }
1003  
1004  void
1005  StartupItemSetStatus(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, CFStringRef aServiceName,
1006  		     Boolean aSuccess, Boolean aReplaceFlag)
1007  {
1008  	void (*anAction) (CFMutableDictionaryRef, const void *, const void *) = aReplaceFlag ?
1009  	    CFDictionarySetValue : CFDictionaryAddValue;
1010  
1011  	if (aStatusDict && anItem) {
1012  		CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
1013  		if (aProvidesList) {
1014  			CFIndex aProvidesCount = CFArrayGetCount(aProvidesList);
1015  			CFIndex aProvidesIndex;
1016  
1017  			/*
1018  			 * If a service name was specified, and it is valid,
1019  			 * use only it.
1020  			 */
1021  			if (aServiceName && CFArrayContainsValue(aProvidesList, CFRangeMake(0, aProvidesCount), aServiceName)) {
1022  				aProvidesList = CFArrayCreate(NULL, (const void **)&aServiceName, 1, &kCFTypeArrayCallBacks);
1023  				aProvidesCount = 1;
1024  			} else {
1025  				CFRetain(aProvidesList);
1026  			}
1027  
1028  			for (aProvidesIndex = 0; aProvidesIndex < aProvidesCount; aProvidesIndex++) {
1029  				CFStringRef aService = CFArrayGetValueAtIndex(aProvidesList, aProvidesIndex);
1030  
1031  				if (aSuccess)
1032  					anAction(aStatusDict, aService, kRunSuccess);
1033  				else
1034  					anAction(aStatusDict, aService, kRunFailure);
1035  			}
1036  
1037  			CFRelease(aProvidesList);
1038  		}
1039  	}
1040  }
1041  
1042  void StartupItemExit(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Boolean aSuccess)
1043  {
1044  	StartupItemSetStatus(aStatusDict, anItem, NULL, aSuccess, FALSE);
1045  }