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