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