]> git.saurik.com Git - apple/launchd.git/blame - launchd/src/StartupItems.c
launchd-258.12.tar.gz
[apple/launchd.git] / launchd / src / StartupItems.c
CommitLineData
e91b9f68
A
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 *
ed34e3c3
A
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@
e91b9f68
A
24 **/
25
26#include <unistd.h>
27#include <sys/types.h>
28#include <sys/stat.h>
5b0a4722 29#include <sys/sysctl.h>
e91b9f68
A
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
ed34e3c3 49static const char *argumentForAction(Action anAction)
e91b9f68
A
50{
51 switch (anAction) {
ed34e3c3
A
52 case kActionStart:
53 return "start";
e91b9f68
A
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
ed34e3c3 70static int StartupItemValidate(CFDictionaryRef aConfig)
e91b9f68
A
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 */
ed34e3c3 84void RemoveItemFromWaitingList(StartupContext aStartupContext, CFMutableDictionaryRef anItem)
e91b9f68
A
85{
86 /* Remove the item from the waiting list. */
87 if (aStartupContext && anItem && aStartupContext->aWaitingList) {
ed34e3c3
A
88 CFRange aRange = { 0, CFArrayGetCount(aStartupContext->aWaitingList) };
89 CFIndex anIndex = CFArrayGetFirstIndexOfValue(aStartupContext->aWaitingList, aRange, anItem);
e91b9f68
A
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 */
ed34e3c3 101void AddItemToFailedList(StartupContext aStartupContext, CFMutableDictionaryRef anItem)
e91b9f68
A
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
e91b9f68
A
114/**
115 * startupItemListGetMatches returns an array of items which contain the string aService in the key aKey
116 **/
ed34e3c3 117static CFMutableArrayRef startupItemListGetMatches(CFArrayRef anItemList, CFStringRef aKey, CFStringRef aService)
e91b9f68
A
118{
119 CFMutableArrayRef aResult = NULL;
120
121 if (anItemList && aKey && aService) {
ed34e3c3
A
122 CFIndex anItemCount = CFArrayGetCount(anItemList);
123 CFIndex anItemIndex = 0;
e91b9f68
A
124
125 aResult = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
126
127 for (anItemIndex = 0; anItemIndex < anItemCount; ++anItemIndex) {
128 CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(anItemList, anItemIndex);
ed34e3c3 129 CFArrayRef aList = CFDictionaryGetValue(anItem, aKey);
e91b9f68
A
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
ed34e3c3 142static void SpecialCasesStartupItemHandler(CFMutableDictionaryRef aConfig)
e91b9f68
A
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"),
ed34e3c3 159 CFSTR("Core Services"),
e91b9f68 160 CFSTR("Network"),
ed34e3c3 161 CFSTR("TIM"),
5b0a4722
A
162 CFSTR("Disks"),
163 CFSTR("NIS"),
e91b9f68
A
164 NULL
165 };
166 CFMutableArrayRef aList, aNewList;
ed34e3c3 167 CFIndex i, aCount;
e91b9f68
A
168 CFStringRef ci, type = kRequiresKey;
169 const CFStringRef *c;
170
ed34e3c3 171 again:
e91b9f68
A
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 CFRetain(ci);
187 CFArrayAppendValue(aNewList, ci);
188 CF_syslog(LOG_DEBUG, CFSTR("%@: Keeping %@"), type, ci);
189 }
190 }
191
192 CFDictionaryReplaceValue(aConfig, type, aNewList);
193 }
194 if (type == kUsesKey)
195 return;
196 type = kUsesKey;
197 goto again;
198}
199
ed34e3c3 200CFIndex StartupItemListCountServices(CFArrayRef anItemList)
e91b9f68 201{
ed34e3c3 202 CFIndex aResult = 0;
e91b9f68
A
203
204 if (anItemList) {
ed34e3c3
A
205 CFIndex anItemCount = CFArrayGetCount(anItemList);
206 CFIndex anItemIndex = 0;
e91b9f68
A
207
208 for (anItemIndex = 0; anItemIndex < anItemCount; ++anItemIndex) {
209 CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
ed34e3c3 210 CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
e91b9f68
A
211
212 if (aProvidesList)
213 aResult += CFArrayGetCount(aProvidesList);
214 }
215 }
216 return aResult;
217}
218
ed34e3c3 219static bool StartupItemSecurityCheck(const char *aPath)
e91b9f68 220{
5b0a4722 221 static struct timeval boot_time;
e91b9f68
A
222 struct stat aStatBuf;
223 bool r = true;
224
5b0a4722
A
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
e91b9f68
A
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 }
5b0a4722
A
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 }
e91b9f68
A
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 }
5b0a4722
A
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);
e91b9f68
A
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);
ed34e3c3 272 close(open(kFixerPath, O_RDWR|O_CREAT|O_NOCTTY, DEFFILEMODE));
e91b9f68
A
273 }
274 return r;
275}
276
ed34e3c3 277CFMutableArrayRef StartupItemListCreateWithMask(NSSearchPathDomainMask aMask)
e91b9f68
A
278{
279 CFMutableArrayRef anItemList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
280
ed34e3c3
A
281 char aPath[PATH_MAX];
282 CFIndex aDomainIndex = 0;
e91b9f68
A
283
284 NSSearchPathEnumerationState aState = NSStartSearchPathEnumeration(NSLibraryDirectory, aMask);
285
286 while ((aState = NSGetNextSearchPathEnumeration(aState, aPath))) {
ed34e3c3 287 DIR *aDirectory;
e91b9f68
A
288
289 strcpy(aPath + strlen(aPath), kStartupItemsPath);
290 ++aDomainIndex;
291
5b0a4722
A
292 /* 5485016
293 *
294 * Just in case...
295 */
296 mkdir(aPath, S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH);
297
e91b9f68
A
298 if (!StartupItemSecurityCheck(aPath))
299 continue;
300
301 if ((aDirectory = opendir(aPath))) {
ed34e3c3 302 struct dirent *aBundle;
e91b9f68
A
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
ed34e3c3 317 sprintf(aBundlePath, "%s/%s", aPath, aBundleName);
e91b9f68 318 sprintf(aBundleExecutablePath, "%s/%s", aBundlePath, aBundleName);
ed34e3c3
A
319 sprintf(aConfigFile, "%s/%s", aBundlePath, kParametersFile);
320 sprintf(aDisabledFile, "%s/%s", aBundlePath, kDisabledFile);
e91b9f68
A
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 {
ed34e3c3 335 int aConfigFileDescriptor;
e91b9f68 336
ed34e3c3
A
337 if ((aConfigFileDescriptor = open(aConfigFile, O_RDONLY|O_NOCTTY, (mode_t) 0)) != -1) {
338 struct stat aConfigFileStatBuffer;
e91b9f68
A
339
340 if (stat(aConfigFile, &aConfigFileStatBuffer) != -1) {
ed34e3c3
A
341 off_t aConfigFileContentsSize = aConfigFileStatBuffer.st_size;
342 char *aConfigFileContentsBuffer;
e91b9f68
A
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) {
ed34e3c3 348 CFDataRef aConfigData = NULL;
e91b9f68
A
349 CFMutableDictionaryRef aConfig = NULL;
350
351 aConfigData =
352 CFDataCreateWithBytesNoCopy(NULL,
aa59983a 353 (const UInt8 *)aConfigFileContentsBuffer,
e91b9f68
A
354 aConfigFileContentsSize,
355 kCFAllocatorNull);
356
357 if (aConfigData) {
358 aConfig = (CFMutableDictionaryRef)
ed34e3c3
A
359 CFPropertyListCreateFromXMLData(NULL, aConfigData,
360 kCFPropertyListMutableContainers,
361 NULL);
e91b9f68
A
362 }
363 if (StartupItemValidate(aConfig)) {
ed34e3c3
A
364 CFStringRef aBundlePathString =
365 CFStringCreateWithCString(NULL, aBundlePath,
366 kCFStringEncodingUTF8);
e91b9f68 367
ed34e3c3
A
368 CFNumberRef aDomainNumber =
369 CFNumberCreate(NULL, kCFNumberCFIndexType,
370 &aDomainIndex);
e91b9f68 371
ed34e3c3
A
372 CFDictionarySetValue(aConfig, kBundlePathKey,
373 aBundlePathString);
e91b9f68
A
374 CFDictionarySetValue(aConfig, kDomainKey, aDomainNumber);
375 CFRelease(aDomainNumber);
376 SpecialCasesStartupItemHandler(aConfig);
377 CFArrayAppendValue(anItemList, aConfig);
378
379 CFRelease(aBundlePathString);
380 } else {
ed34e3c3
A
381 syslog(LOG_ERR, "Malformatted parameters file: %s",
382 aConfigFile);
e91b9f68
A
383 }
384
385 if (aConfig)
386 CFRelease(aConfig);
387 if (aConfigData)
388 CFRelease(aConfigData);
389
ed34e3c3
A
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);
e91b9f68
A
395 }
396 } else {
ed34e3c3
A
397 syslog(LOG_ERR,
398 "Unable to map parameters file %s for item %s: %m",
399 aConfigFile, aBundleName);
e91b9f68
A
400 }
401 } else {
ed34e3c3
A
402 syslog(LOG_ERR, "Unable to stat parameters file %s for item %s: %m",
403 aConfigFile, aBundleName);
e91b9f68
A
404 }
405
406 if (close(aConfigFileDescriptor) == -1) {
ed34e3c3
A
407 syslog(LOG_ERR, "Unable to close parameters file %s for item %s: %m",
408 aConfigFile, aBundleName);
e91b9f68
A
409 }
410 } else {
ed34e3c3
A
411 syslog(LOG_ERR, "Unable to open parameters file %s for item %s: %m", aConfigFile,
412 aBundleName);
e91b9f68
A
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
ed34e3c3 430CFMutableDictionaryRef StartupItemListGetProvider(CFArrayRef anItemList, CFStringRef aService)
e91b9f68
A
431{
432 CFMutableDictionaryRef aResult = NULL;
433 CFMutableArrayRef aList = startupItemListGetMatches(anItemList, kProvidesKey, aService);
434
435 if (aList && CFArrayGetCount(aList) > 0)
436 aResult = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aList, 0);
437
438 return aResult;
439}
440
ed34e3c3 441CFArrayRef StartupItemListGetRunning(CFArrayRef anItemList)
e91b9f68
A
442{
443 CFMutableArrayRef aResult = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
444 if (aResult) {
ed34e3c3 445 CFIndex anIndex, aCount = CFArrayGetCount(anItemList);
e91b9f68
A
446 for (anIndex = 0; anIndex < aCount; ++anIndex) {
447 CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anIndex);
448 if (anItem) {
ed34e3c3 449 CFNumberRef aPID = CFDictionaryGetValue(anItem, kPIDKey);
e91b9f68
A
450 if (aPID)
451 CFArrayAppendValue(aResult, anItem);
452 }
453 }
454 }
455 return aResult;
456}
457
458/*
459 * Append items in anItemList to aDependents which depend on
460 * aParentItem.
461 * If anAction is kActionStart, dependent items are those which
462 * require any service provided by aParentItem.
463 * If anAction is kActionStop, dependent items are those which provide
464 * any service required by aParentItem.
465 */
ed34e3c3 466static void appendDependents(CFMutableArrayRef aDependents, CFArrayRef anItemList, CFDictionaryRef aParentItem, Action anAction)
e91b9f68 467{
ed34e3c3
A
468 CFStringRef anInnerKey, anOuterKey;
469 CFArrayRef anOuterList;
e91b9f68
A
470
471 /* Append the parent item to the list (avoiding duplicates) */
472 if (!CFArrayContainsValue(aDependents, CFRangeMake(0, CFArrayGetCount(aDependents)), aParentItem))
473 CFArrayAppendValue(aDependents, aParentItem);
474
475 /**
476 * Recursively append any children of the parent item for kStartAction and kStopAction.
477 * Do nothing for other actions.
478 **/
479 switch (anAction) {
480 case kActionStart:
481 anInnerKey = kProvidesKey;
482 anOuterKey = kRequiresKey;
483 break;
484 case kActionStop:
485 anInnerKey = kRequiresKey;
486 anOuterKey = kProvidesKey;
487 break;
488 default:
489 return;
490 }
491
492 anOuterList = CFDictionaryGetValue(aParentItem, anOuterKey);
493
494 if (anOuterList) {
ed34e3c3
A
495 CFIndex anOuterCount = CFArrayGetCount(anOuterList);
496 CFIndex anOuterIndex;
e91b9f68
A
497
498 for (anOuterIndex = 0; anOuterIndex < anOuterCount; anOuterIndex++) {
ed34e3c3
A
499 CFStringRef anOuterElement = CFArrayGetValueAtIndex(anOuterList, anOuterIndex);
500 CFIndex anItemCount = CFArrayGetCount(anItemList);
501 CFIndex anItemIndex;
e91b9f68
A
502
503 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
504 CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
ed34e3c3 505 CFArrayRef anInnerList = CFDictionaryGetValue(anItem, anInnerKey);
e91b9f68
A
506
507 if (anInnerList &&
ed34e3c3
A
508 CFArrayContainsValue(anInnerList, CFRangeMake(0, CFArrayGetCount(anInnerList)),
509 anOuterElement)
510 && !CFArrayContainsValue(aDependents, CFRangeMake(0, CFArrayGetCount(aDependents)), anItem))
e91b9f68
A
511 appendDependents(aDependents, anItemList, anItem, anAction);
512 }
513 }
514 }
515}
516
ed34e3c3 517CFMutableArrayRef StartupItemListCreateDependentsList(CFMutableArrayRef anItemList, CFStringRef aService, Action anAction)
e91b9f68
A
518{
519 CFMutableArrayRef aDependents = NULL;
520 CFMutableDictionaryRef anItem = NULL;
521
522 if (anItemList && aService)
523 anItem = StartupItemListGetProvider(anItemList, aService);
524
525 if (anItem) {
526 switch (anAction) {
527 case kActionRestart:
528 case kActionStart:
529 case kActionStop:
530 aDependents = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
531
532 if (!aDependents) {
533 CF_syslog(LOG_EMERG, CFSTR("Failed to allocate dependancy list for item %@"), anItem);
534 return NULL;
535 }
536 appendDependents(aDependents, anItemList, anItem, anAction);
537 break;
538
539 default:
540 break;
541 }
542 }
543 return aDependents;
544}
545
546/**
547 * countUnmetRequirements counts the number of items in anItemList
548 * which are pending in aStatusDict.
549 **/
ed34e3c3 550static int countUnmetRequirements(CFDictionaryRef aStatusDict, CFArrayRef anItemList)
e91b9f68 551{
ed34e3c3
A
552 int aCount = 0;
553 CFIndex anItemCount = CFArrayGetCount(anItemList);
554 CFIndex anItemIndex;
e91b9f68
A
555
556 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
ed34e3c3
A
557 CFStringRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
558 CFStringRef aStatus = CFDictionaryGetValue(aStatusDict, anItem);
e91b9f68
A
559
560 if (!aStatus || !CFEqual(aStatus, kRunSuccess)) {
561 CF_syslog(LOG_DEBUG, CFSTR("\tFailed requirement/uses: %@"), anItem);
562 aCount++;
563 }
564 }
565
566 return aCount;
567}
568
569/**
570 * countDependantsPresent counts the number of items in aWaitingList
571 * which depend on items in anItemList.
572 **/
ed34e3c3 573static int countDependantsPresent(CFArrayRef aWaitingList, CFArrayRef anItemList, CFStringRef aKey)
e91b9f68 574{
ed34e3c3
A
575 int aCount = 0;
576 CFIndex anItemCount = CFArrayGetCount(anItemList);
577 CFIndex anItemIndex;
e91b9f68
A
578
579 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
ed34e3c3
A
580 CFStringRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
581 CFArrayRef aMatchesList = startupItemListGetMatches(aWaitingList, aKey, anItem);
e91b9f68
A
582
583 if (aMatchesList) {
584 aCount = aCount + CFArrayGetCount(aMatchesList);
585 CFRelease(aMatchesList);
586 }
587 }
588
589 return aCount;
590}
591
592/**
593 * pendingAntecedents returns TRUE if any antecedents of this item
594 * are currently running, have not yet run, or none exist.
595 **/
ed34e3c3 596static Boolean
e91b9f68
A
597pendingAntecedents(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, CFArrayRef anAntecedentList, Action anAction)
598{
ed34e3c3 599 int aPendingFlag = FALSE;
e91b9f68 600
ed34e3c3
A
601 CFIndex anAntecedentCount = CFArrayGetCount(anAntecedentList);
602 CFIndex anAntecedentIndex;
e91b9f68
A
603
604 for (anAntecedentIndex = 0; anAntecedentIndex < anAntecedentCount; ++anAntecedentIndex) {
ed34e3c3
A
605 CFStringRef anAntecedent = CFArrayGetValueAtIndex(anAntecedentList, anAntecedentIndex);
606 CFStringRef aKey = (anAction == kActionStart) ? kProvidesKey : kUsesKey;
607 CFArrayRef aMatchesList = startupItemListGetMatches(aWaitingList, aKey, anAntecedent);
e91b9f68
A
608
609 if (aMatchesList) {
ed34e3c3
A
610 CFIndex aMatchesListCount = CFArrayGetCount(aMatchesList);
611 CFIndex aMatchesListIndex;
e91b9f68
A
612
613 for (aMatchesListIndex = 0; aMatchesListIndex < aMatchesListCount; ++aMatchesListIndex) {
614 CFDictionaryRef anItem = CFArrayGetValueAtIndex(aMatchesList, aMatchesListIndex);
615
616 if (!anItem ||
ed34e3c3 617 !CFDictionaryGetValue(anItem, kPIDKey) || !CFDictionaryGetValue(aStatusDict, anAntecedent)) {
e91b9f68
A
618 aPendingFlag = TRUE;
619 break;
620 }
621 }
622
623 CFRelease(aMatchesList);
624
625 if (aPendingFlag)
626 break;
627 }
628 }
629 return (aPendingFlag);
630}
631
632/**
633 * checkForDuplicates returns TRUE if an item provides the same service as a
634 * pending item, or an item that already succeeded.
635 **/
ed34e3c3 636static Boolean checkForDuplicates(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, CFDictionaryRef anItem)
e91b9f68 637{
ed34e3c3 638 int aDuplicateFlag = FALSE;
e91b9f68 639
ed34e3c3
A
640 CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
641 CFIndex aProvidesCount = aProvidesList ? CFArrayGetCount(aProvidesList) : 0;
642 CFIndex aProvidesIndex;
e91b9f68
A
643
644 for (aProvidesIndex = 0; aProvidesIndex < aProvidesCount; ++aProvidesIndex) {
ed34e3c3 645 CFStringRef aProvides = CFArrayGetValueAtIndex(aProvidesList, aProvidesIndex);
e91b9f68
A
646
647 /* If the service succeeded, return true. */
ed34e3c3 648 CFStringRef aStatus = CFDictionaryGetValue(aStatusDict, aProvides);
e91b9f68
A
649 if (aStatus && CFEqual(aStatus, kRunSuccess)) {
650 aDuplicateFlag = TRUE;
651 break;
652 }
653 /*
654 * Otherwise test if any item is currently running which
655 * might provide that service.
656 */
657 else {
ed34e3c3 658 CFArrayRef aMatchesList = startupItemListGetMatches(aWaitingList, kProvidesKey, aProvides);
e91b9f68 659 if (aMatchesList) {
ed34e3c3
A
660 CFIndex aMatchesListCount = CFArrayGetCount(aMatchesList);
661 CFIndex aMatchesListIndex;
e91b9f68
A
662
663 for (aMatchesListIndex = 0; aMatchesListIndex < aMatchesListCount; ++aMatchesListIndex) {
664 CFDictionaryRef anDupItem = CFArrayGetValueAtIndex(aMatchesList, aMatchesListIndex);
665 if (anDupItem && CFDictionaryGetValue(anDupItem, kPIDKey)) {
666 /*
667 * Item is running, avoid
668 * race condition.
669 */
670 aDuplicateFlag = TRUE;
671 break;
672 } else {
ed34e3c3
A
673 CFNumberRef anItemDomain = CFDictionaryGetValue(anItem, kDomainKey);
674 CFNumberRef anotherItemDomain = CFDictionaryGetValue(anDupItem, kDomainKey);
e91b9f68
A
675 /*
676 * If anItem was found later
677 * than aDupItem, stall
678 * anItem until aDupItem
679 * runs.
680 */
681 if (anItemDomain &&
682 anotherItemDomain &&
ed34e3c3
A
683 CFNumberCompare(anItemDomain, anotherItemDomain,
684 NULL) == kCFCompareGreaterThan) {
e91b9f68
A
685 /*
686 * Item not running,
687 * but takes
688 * precedence.
689 */
690 aDuplicateFlag = TRUE;
691 break;
692 }
693 }
694 }
695
696 CFRelease(aMatchesList);
697 if (aDuplicateFlag)
698 break;
699 }
700 }
701 }
702 return (aDuplicateFlag);
703}
704
ed34e3c3 705CFMutableDictionaryRef StartupItemListGetNext(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, Action anAction)
e91b9f68
A
706{
707 CFMutableDictionaryRef aNextItem = NULL;
ed34e3c3
A
708 CFIndex aWaitingCount = CFArrayGetCount(aWaitingList);
709 int aMinFailedAntecedents = INT_MAX;
710 CFIndex aWaitingIndex;
e91b9f68
A
711
712 switch (anAction) {
713 case kActionStart:
714 break;
715 case kActionStop:
716 break;
717 case kActionRestart:
718 break;
719 default:
720 return NULL;
721 }
722
ed34e3c3
A
723 if (!aWaitingList || !aStatusDict || aWaitingCount <= 0)
724 return NULL;
e91b9f68 725
ed34e3c3
A
726 /**
727 * Iterate through the items in aWaitingList and look for an optimally ready item.
728 **/
729 for (aWaitingIndex = 0; aWaitingIndex < aWaitingCount; aWaitingIndex++) {
730 CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aWaitingList, aWaitingIndex);
731 CFArrayRef anAntecedentList;
732 int aFailedAntecedentsCount = 0; /* Number of unmet soft
733 * depenancies */
734 Boolean aBestPick = FALSE; /* Is this the best pick
735 * so far? */
736
737 /* Filter out running items. */
738 if (CFDictionaryGetValue(anItem, kPIDKey))
739 continue;
e91b9f68 740
ed34e3c3
A
741 /*
742 * Filter out dupilicate services; if someone has
743 * provided what we provide, we don't run.
744 */
745 if (checkForDuplicates(aWaitingList, aStatusDict, anItem)) {
746 CF_syslog(LOG_DEBUG, CFSTR("Skipping %@ because of duplicate service."),
747 CFDictionaryGetValue(anItem, kDescriptionKey));
748 continue;
749 }
750 /*
751 * Dependencies don't matter when restarting an item;
752 * stop here.
753 */
754 if (anAction == kActionRestart) {
755 aNextItem = anItem;
756 break;
757 }
758 anAntecedentList = CFDictionaryGetValue(anItem, ((anAction == kActionStart) ? kRequiresKey : kProvidesKey));
e91b9f68 759
ed34e3c3 760 CF_syslog(LOG_DEBUG, CFSTR("Checking %@"), CFDictionaryGetValue(anItem, kDescriptionKey));
e91b9f68 761
ed34e3c3
A
762 if (anAntecedentList)
763 CF_syslog(LOG_DEBUG, CFSTR("Antecedents: %@"), anAntecedentList);
764 else
765 syslog(LOG_DEBUG, "No antecedents");
e91b9f68
A
766
767 /**
768 * Filter out the items which have unsatisfied antecedents.
769 **/
ed34e3c3
A
770 if (anAntecedentList &&
771 ((anAction == kActionStart) ?
772 countUnmetRequirements(aStatusDict, anAntecedentList) :
773 countDependantsPresent(aWaitingList, anAntecedentList, kRequiresKey)))
774 continue;
e91b9f68
A
775
776 /**
777 * anItem has all hard dependancies met; check for soft dependancies.
778 * We'll favor the item with the fewest unmet soft dependancies here.
779 **/
ed34e3c3 780 anAntecedentList = CFDictionaryGetValue(anItem, ((anAction == kActionStart) ? kUsesKey : kProvidesKey));
e91b9f68 781
ed34e3c3
A
782 if (anAntecedentList)
783 CF_syslog(LOG_DEBUG, CFSTR("Soft dependancies: %@"), anAntecedentList);
784 else
785 syslog(LOG_DEBUG, "No soft dependancies");
e91b9f68 786
ed34e3c3
A
787 if (anAntecedentList) {
788 aFailedAntecedentsCount =
789 ((anAction == kActionStart) ?
790 countUnmetRequirements(aStatusDict, anAntecedentList) :
791 countDependantsPresent(aWaitingList, anAntecedentList, kUsesKey));
792 } else {
793 if (aMinFailedAntecedents > 0)
794 aBestPick = TRUE;
795 }
e91b9f68 796
ed34e3c3
A
797 /*
798 * anItem has unmet dependencies that will
799 * likely be met in the future, so delay it
800 */
801 if (aFailedAntecedentsCount > 0 && pendingAntecedents(aWaitingList, aStatusDict, anAntecedentList, anAction)) {
802 continue;
803 }
804 if (aFailedAntecedentsCount > 0)
805 syslog(LOG_DEBUG, "Total: %d", aFailedAntecedentsCount);
e91b9f68 806
ed34e3c3
A
807 if (aFailedAntecedentsCount > aMinFailedAntecedents)
808 continue; /* Another item already won out */
e91b9f68 809
ed34e3c3
A
810 if (aFailedAntecedentsCount < aMinFailedAntecedents)
811 aBestPick = TRUE;
e91b9f68 812
ed34e3c3
A
813 if (!aBestPick)
814 continue;
e91b9f68 815
ed34e3c3
A
816 /*
817 * anItem has less unmet
818 * dependancies than any
819 * other item so far, so it
820 * wins.
821 */
822 syslog(LOG_DEBUG, "Best pick so far, based on failed dependancies (%d->%d)",
823 aMinFailedAntecedents, aFailedAntecedentsCount);
e91b9f68 824
ed34e3c3
A
825 /*
826 * We have a winner! Update success
827 * parameters to match anItem.
828 */
829 aMinFailedAntecedents = aFailedAntecedentsCount;
830 aNextItem = anItem;
e91b9f68 831
ed34e3c3 832 } /* End of waiting list loop. */
e91b9f68 833
e91b9f68
A
834 return aNextItem;
835}
836
ed34e3c3 837CFStringRef StartupItemGetDescription(CFMutableDictionaryRef anItem)
e91b9f68 838{
ed34e3c3 839 CFStringRef aString = NULL;
e91b9f68
A
840
841 if (anItem)
842 aString = CFDictionaryGetValue(anItem, kDescriptionKey);
843 if (aString)
844 CFRetain(aString);
845 return aString;
846}
847
ed34e3c3 848pid_t StartupItemGetPID(CFDictionaryRef anItem)
e91b9f68 849{
ed34e3c3
A
850 CFIndex anItemPID = 0;
851 CFNumberRef aPIDNumber = anItem ? CFDictionaryGetValue(anItem, kPIDKey) : NULL;
e91b9f68
A
852 if (aPIDNumber && CFNumberGetValue(aPIDNumber, kCFNumberCFIndexType, &anItemPID))
853 return (pid_t) anItemPID;
854 else
855 return 0;
856}
857
ed34e3c3 858CFMutableDictionaryRef StartupItemWithPID(CFArrayRef anItemList, pid_t aPID)
e91b9f68 859{
ed34e3c3
A
860 CFIndex anItemCount = CFArrayGetCount(anItemList);
861 CFIndex anItemIndex;
e91b9f68
A
862
863 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
864 CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(anItemList, anItemIndex);
ed34e3c3
A
865 CFNumberRef aPIDNumber = CFDictionaryGetValue(anItem, kPIDKey);
866 CFIndex anItemPID;
e91b9f68
A
867
868 if (aPIDNumber) {
869 CFNumberGetValue(aPIDNumber, kCFNumberCFIndexType, &anItemPID);
870
871 if ((pid_t) anItemPID == aPID)
872 return anItem;
873 }
874 }
875
876 return NULL;
877}
878
ed34e3c3 879int StartupItemRun(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Action anAction)
e91b9f68
A
880{
881 int anError = -1;
882 CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
883 static const CFStringRef stubitems[] = {
884 CFSTR("BootROMUpdater"), /* 3893064 */
885 CFSTR("FCUUpdater"), /* 3893064 */
886 CFSTR("AutoProtect Daemon"), /* 3965785 */
887 CFSTR("Check For Missed Tasks"), /* 3965785 */
888 CFSTR("Privacy"), /* 3933484 */
889 CFSTR("Firmware Update Checking"), /* 4001504 */
890
891 CFSTR("M-Audio FireWire Audio Support"), /* 3931757 */
892 CFSTR("help for M-Audio Delta Family"), /* 3931757 */
893 CFSTR("help for M-Audio Devices"), /* 3931757 */
894 CFSTR("help for M-Audio Revo 5.1"), /* 3931757 */
895 CFSTR("M-Audio USB Duo Configuration Service"), /* 3931757 */
896 CFSTR("firmware loader for M-Audio devices"), /* 3931757 */
897 CFSTR("M-Audio MobilePre USB Configuration Service"), /* 3931757 */
898 CFSTR("M-Audio OmniStudio USB Configuration Service"), /* 3931757 */
899 CFSTR("M-Audio Transit USB Configuration Service"), /* 3931757 */
900 CFSTR("M-Audio Audiophile USB Configuration Service"), /* 3931757 */
901 NULL
902 };
903 const CFStringRef *c;
904
905 if (aProvidesList && anAction == kActionStop) {
906 CFIndex aProvidesCount = CFArrayGetCount(aProvidesList);
907 for (c = stubitems; *c; c++) {
908 if (CFArrayContainsValue(aProvidesList, CFRangeMake(0, aProvidesCount), *c)) {
909 CFIndex aPID = -1;
910 CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
911
912 CFDictionarySetValue(anItem, kPIDKey, aProcessNumber);
913 CFRelease(aProcessNumber);
914
915 StartupItemExit(aStatusDict, anItem, TRUE);
916 return -1;
917 }
918 }
919 }
920
921 if (anAction == kActionNone) {
922 StartupItemExit(aStatusDict, anItem, TRUE);
923 anError = 0;
924 } else {
ed34e3c3
A
925 CFStringRef aBundlePathString = CFDictionaryGetValue(anItem, kBundlePathKey);
926 size_t aBundlePathCLength =
927 CFStringGetMaximumSizeForEncoding(CFStringGetLength(aBundlePathString), kCFStringEncodingUTF8) + 1;
928 char *aBundlePath = (char *)malloc(aBundlePathCLength);
929 char anExecutable[PATH_MAX] = "";
e91b9f68
A
930
931 if (!aBundlePath) {
932 syslog(LOG_EMERG, "malloc() failed; out of memory while running item %s", aBundlePathString);
933 return (anError);
934 }
935 if (!CFStringGetCString(aBundlePathString, aBundlePath, aBundlePathCLength, kCFStringEncodingUTF8)) {
936 CF_syslog(LOG_EMERG, CFSTR("Internal error while running item %@"), aBundlePathString);
937 return (anError);
938 }
939 /* Compute path to excecutable */
940 {
941 char *tmp;
ab36757d 942 strncpy(anExecutable, aBundlePath, sizeof(anExecutable)); /* .../foo */
e91b9f68
A
943 tmp = rindex(anExecutable, '/'); /* /foo */
944 strncat(anExecutable, tmp, strlen(tmp)); /* .../foo/foo */
945 }
946
947 free(aBundlePath);
948
949 /**
950 * Run the bundle
951 **/
952
953 if (access(anExecutable, X_OK)) {
954 /*
955 * Add PID key so that this item is marked as having
956 * been run.
957 */
ed34e3c3
A
958 CFIndex aPID = -1;
959 CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
e91b9f68
A
960
961 CFDictionarySetValue(anItem, kPIDKey, aProcessNumber);
962 CFRelease(aProcessNumber);
963
964 CFDictionarySetValue(anItem, kErrorKey, kErrorPermissions);
965 StartupItemExit(aStatusDict, anItem, FALSE);
966 syslog(LOG_ERR, "No executable file %s", anExecutable);
967 } else {
ed34e3c3 968 pid_t aProccessID = fork();
e91b9f68
A
969
970 switch (aProccessID) {
971 case -1: /* SystemStarter (fork failed) */
972 CFDictionarySetValue(anItem, kErrorKey, kErrorFork);
973 StartupItemExit(aStatusDict, anItem, FALSE);
974
ed34e3c3 975 CF_syslog(LOG_ERR, CFSTR("Failed to fork for item %@: %s"), aBundlePathString, strerror(errno));
e91b9f68
A
976
977 break;
978
979 default: /* SystemStarter (fork succeeded) */
980 {
ed34e3c3
A
981 CFIndex aPID = (CFIndex) aProccessID;
982 CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
e91b9f68
A
983
984 CFDictionarySetValue(anItem, kPIDKey, aProcessNumber);
985 CFRelease(aProcessNumber);
986
987 syslog(LOG_DEBUG, "Running command (%d): %s %s",
988 aProccessID, anExecutable, argumentForAction(anAction));
989 anError = 0;
990 }
991 break;
992
ed34e3c3 993 case 0: /* Child */
e91b9f68 994 {
e91b9f68
A
995 if (setsid() == -1)
996 syslog(LOG_WARNING, "Unable to create session for item %s: %m", anExecutable);
997
998 anError = execl(anExecutable, anExecutable, argumentForAction(anAction), NULL);
999
1000 /* We shouldn't get here. */
1001
1002 syslog(LOG_ERR, "execl(\"%s\"): %m", anExecutable);
1003
1004 exit(anError);
1005 }
1006 }
1007 }
1008 }
1009
1010 return (anError);
1011}
1012
ed34e3c3
A
1013void
1014StartupItemSetStatus(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, CFStringRef aServiceName,
1015 Boolean aSuccess, Boolean aReplaceFlag)
e91b9f68 1016{
ed34e3c3
A
1017 void (*anAction) (CFMutableDictionaryRef, const void *, const void *) = aReplaceFlag ?
1018 CFDictionarySetValue : CFDictionaryAddValue;
e91b9f68
A
1019
1020 if (aStatusDict && anItem) {
ed34e3c3 1021 CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
e91b9f68 1022 if (aProvidesList) {
ed34e3c3
A
1023 CFIndex aProvidesCount = CFArrayGetCount(aProvidesList);
1024 CFIndex aProvidesIndex;
e91b9f68
A
1025
1026 /*
1027 * If a service name was specified, and it is valid,
1028 * use only it.
1029 */
1030 if (aServiceName && CFArrayContainsValue(aProvidesList, CFRangeMake(0, aProvidesCount), aServiceName)) {
ed34e3c3 1031 aProvidesList = CFArrayCreate(NULL, (const void **)&aServiceName, 1, &kCFTypeArrayCallBacks);
e91b9f68
A
1032 aProvidesCount = 1;
1033 } else {
1034 CFRetain(aProvidesList);
1035 }
1036
1037 for (aProvidesIndex = 0; aProvidesIndex < aProvidesCount; aProvidesIndex++) {
ed34e3c3 1038 CFStringRef aService = CFArrayGetValueAtIndex(aProvidesList, aProvidesIndex);
e91b9f68
A
1039
1040 if (aSuccess)
1041 anAction(aStatusDict, aService, kRunSuccess);
1042 else
1043 anAction(aStatusDict, aService, kRunFailure);
1044 }
1045
1046 CFRelease(aProvidesList);
1047 }
1048 }
1049}
1050
ed34e3c3 1051void StartupItemExit(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Boolean aSuccess)
e91b9f68
A
1052{
1053 StartupItemSetStatus(aStatusDict, anItem, NULL, aSuccess, FALSE);
1054}