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