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