]> git.saurik.com Git - apple/launchd.git/blame - launchd/src/StartupItems.c
launchd-329.3.tar.gz
[apple/launchd.git] / launchd / src / StartupItems.c
CommitLineData
e91b9f68
A
1/**
2 * StartupItems.c - Startup Item management routines
3 * Wilfredo Sanchez | wsanchez@opensource.apple.com
4 * Kevin Van Vechten | kevinvv@uclink4.berkeley.edu
5 * $Apple$
6 **
7 * Copyright (c) 1999-2002 Apple Computer, Inc. All rights reserved.
8 *
ed34e3c3
A
9 * @APPLE_APACHE_LICENSE_HEADER_START@
10 *
11 * Licensed under the Apache License, Version 2.0 (the "License");
12 * you may not use this file except in compliance with the License.
13 * You may obtain a copy of the License at
14 *
15 * http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 * See the License for the specific language governing permissions and
21 * limitations under the License.
22 *
23 * @APPLE_APACHE_LICENSE_HEADER_END@
e91b9f68
A
24 **/
25
26#include <unistd.h>
27#include <sys/types.h>
28#include <sys/stat.h>
5b0a4722 29#include <sys/sysctl.h>
e91b9f68
A
30#include <sys/mman.h>
31#include <stdlib.h>
32#include <fcntl.h>
33#include <dirent.h>
34#include <limits.h>
35#include <errno.h>
36#include <string.h>
37#include <sysexits.h>
38#include <syslog.h>
39#include <CoreFoundation/CoreFoundation.h>
40#include "StartupItems.h"
41
42#define kStartupItemsPath "/StartupItems"
43#define kParametersFile "StartupParameters.plist"
44#define kDisabledFile ".disabled"
45
46#define kRunSuccess CFSTR("success")
47#define kRunFailure CFSTR("failure")
48
ed34e3c3 49static const char *argumentForAction(Action anAction)
e91b9f68
A
50{
51 switch (anAction) {
ed34e3c3
A
52 case kActionStart:
53 return "start";
e91b9f68
A
54 case kActionStop:
55 return "stop";
56 case kActionRestart:
57 return "restart";
58 default:
59 return NULL;
60 }
61}
62
63#define checkTypeOfValue(aKey,aTypeID) \
64 { \
65 CFStringRef aProperty = CFDictionaryGetValue(aConfig, aKey); \
66 if (aProperty && CFGetTypeID(aProperty) != aTypeID) \
67 return FALSE; \
68 }
69
ed34e3c3 70static int StartupItemValidate(CFDictionaryRef aConfig)
e91b9f68
A
71{
72 if (aConfig && CFGetTypeID(aConfig) == CFDictionaryGetTypeID()) {
73 checkTypeOfValue(kProvidesKey, CFArrayGetTypeID());
74 checkTypeOfValue(kRequiresKey, CFArrayGetTypeID());
75
76 return TRUE;
77 }
78 return FALSE;
79}
80
81/*
82 * remove item from waiting list
83 */
ed34e3c3 84void RemoveItemFromWaitingList(StartupContext aStartupContext, CFMutableDictionaryRef anItem)
e91b9f68
A
85{
86 /* Remove the item from the waiting list. */
87 if (aStartupContext && anItem && aStartupContext->aWaitingList) {
ed34e3c3
A
88 CFRange aRange = { 0, CFArrayGetCount(aStartupContext->aWaitingList) };
89 CFIndex anIndex = CFArrayGetFirstIndexOfValue(aStartupContext->aWaitingList, aRange, anItem);
e91b9f68
A
90
91 if (anIndex >= 0) {
92 CFArrayRemoveValueAtIndex(aStartupContext->aWaitingList, anIndex);
93 }
94 }
95}
96
97/*
98 * add item to failed list, create list if it doesn't exist
99 * return and fail quietly if it can't create list
100 */
ed34e3c3 101void AddItemToFailedList(StartupContext aStartupContext, CFMutableDictionaryRef anItem)
e91b9f68
A
102{
103 if (aStartupContext && anItem) {
104 /* create the failed list if it doesn't exist */
105 if (!aStartupContext->aFailedList) {
106 aStartupContext->aFailedList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
107 }
108 if (aStartupContext->aFailedList) {
109 CFArrayAppendValue(aStartupContext->aFailedList, anItem);
110 }
111 }
112}
113
e91b9f68 114/**
ddbbfbc1 115 * startupItemListCopyMatches returns an array of items which contain the string aService in the key aKey
e91b9f68 116 **/
ddbbfbc1 117static CFMutableArrayRef startupItemListCopyMatches(CFArrayRef anItemList, CFStringRef aKey, CFStringRef aService)
e91b9f68
A
118{
119 CFMutableArrayRef aResult = NULL;
120
121 if (anItemList && aKey && aService) {
ed34e3c3
A
122 CFIndex anItemCount = CFArrayGetCount(anItemList);
123 CFIndex anItemIndex = 0;
e91b9f68
A
124
125 aResult = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
126
127 for (anItemIndex = 0; anItemIndex < anItemCount; ++anItemIndex) {
128 CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(anItemList, anItemIndex);
ed34e3c3 129 CFArrayRef aList = CFDictionaryGetValue(anItem, aKey);
e91b9f68
A
130
131 if (aList) {
132 if (CFArrayContainsValue(aList, CFRangeMake(0, CFArrayGetCount(aList)), aService) &&
133 !CFArrayContainsValue(aResult, CFRangeMake(0, CFArrayGetCount(aResult)), anItem)) {
134 CFArrayAppendValue(aResult, anItem);
135 }
136 }
137 }
138 }
139 return aResult;
140}
141
ed34e3c3 142static void SpecialCasesStartupItemHandler(CFMutableDictionaryRef aConfig)
e91b9f68
A
143{
144 static const CFStringRef stubitems[] = {
145 CFSTR("Accounting"),
146 CFSTR("System Tuning"),
147 CFSTR("SecurityServer"),
148 CFSTR("Portmap"),
149 CFSTR("System Log"),
150 CFSTR("Resolver"),
151 CFSTR("LDAP"),
152 CFSTR("NetInfo"),
153 CFSTR("NetworkExtensions"),
154 CFSTR("DirectoryServices"),
155 CFSTR("Network Configuration"),
156 CFSTR("mDNSResponder"),
157 CFSTR("Cron"),
158 CFSTR("Core Graphics"),
ed34e3c3 159 CFSTR("Core Services"),
e91b9f68 160 CFSTR("Network"),
ed34e3c3 161 CFSTR("TIM"),
5b0a4722
A
162 CFSTR("Disks"),
163 CFSTR("NIS"),
e91b9f68
A
164 NULL
165 };
166 CFMutableArrayRef aList, aNewList;
ed34e3c3 167 CFIndex i, aCount;
e91b9f68
A
168 CFStringRef ci, type = kRequiresKey;
169 const CFStringRef *c;
170
ed34e3c3 171 again:
e91b9f68
A
172 aList = (CFMutableArrayRef) CFDictionaryGetValue(aConfig, type);
173 if (aList) {
174 aCount = CFArrayGetCount(aList);
175
176 aNewList = CFArrayCreateMutable(kCFAllocatorDefault, aCount, &kCFTypeArrayCallBacks);
177
178 for (i = 0; i < aCount; i++) {
179 ci = CFArrayGetValueAtIndex(aList, i);
180 CF_syslog(LOG_DEBUG, CFSTR("%@: Evaluating %@"), type, ci);
181 for (c = stubitems; *c; c++) {
182 if (CFEqual(*c, ci))
183 break;
184 }
185 if (*c == NULL) {
186 CFRetain(ci);
187 CFArrayAppendValue(aNewList, ci);
188 CF_syslog(LOG_DEBUG, CFSTR("%@: Keeping %@"), type, ci);
189 }
190 }
191
192 CFDictionaryReplaceValue(aConfig, type, aNewList);
ddbbfbc1 193 CFRelease(aNewList);
e91b9f68
A
194 }
195 if (type == kUsesKey)
196 return;
197 type = kUsesKey;
198 goto again;
199}
200
ed34e3c3 201CFIndex StartupItemListCountServices(CFArrayRef anItemList)
e91b9f68 202{
ed34e3c3 203 CFIndex aResult = 0;
e91b9f68
A
204
205 if (anItemList) {
ed34e3c3
A
206 CFIndex anItemCount = CFArrayGetCount(anItemList);
207 CFIndex anItemIndex = 0;
e91b9f68
A
208
209 for (anItemIndex = 0; anItemIndex < anItemCount; ++anItemIndex) {
210 CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
ed34e3c3 211 CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
e91b9f68
A
212
213 if (aProvidesList)
214 aResult += CFArrayGetCount(aProvidesList);
215 }
216 }
217 return aResult;
218}
219
ddbbfbc1 220bool StartupItemSecurityCheck(const char *aPath)
e91b9f68 221{
5b0a4722 222 static struct timeval boot_time;
e91b9f68
A
223 struct stat aStatBuf;
224 bool r = true;
225
5b0a4722
A
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
e91b9f68
A
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 }
5b0a4722
A
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 }
e91b9f68
A
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 }
5b0a4722
A
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);
e91b9f68
A
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);
ed34e3c3 273 close(open(kFixerPath, O_RDWR|O_CREAT|O_NOCTTY, DEFFILEMODE));
e91b9f68
A
274 }
275 return r;
276}
277
ed34e3c3 278CFMutableArrayRef StartupItemListCreateWithMask(NSSearchPathDomainMask aMask)
e91b9f68
A
279{
280 CFMutableArrayRef anItemList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
281
ed34e3c3
A
282 char aPath[PATH_MAX];
283 CFIndex aDomainIndex = 0;
e91b9f68
A
284
285 NSSearchPathEnumerationState aState = NSStartSearchPathEnumeration(NSLibraryDirectory, aMask);
286
287 while ((aState = NSGetNextSearchPathEnumeration(aState, aPath))) {
ed34e3c3 288 DIR *aDirectory;
e91b9f68 289
ddbbfbc1 290 strlcat(aPath, kStartupItemsPath, sizeof(aPath));
e91b9f68
A
291 ++aDomainIndex;
292
5b0a4722
A
293 /* 5485016
294 *
295 * Just in case...
296 */
ddbbfbc1 297 mkdir(aPath, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
5b0a4722 298
e91b9f68
A
299 if (!StartupItemSecurityCheck(aPath))
300 continue;
301
302 if ((aDirectory = opendir(aPath))) {
ed34e3c3 303 struct dirent *aBundle;
e91b9f68
A
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
ed34e3c3 318 sprintf(aBundlePath, "%s/%s", aPath, aBundleName);
e91b9f68 319 sprintf(aBundleExecutablePath, "%s/%s", aBundlePath, aBundleName);
ed34e3c3
A
320 sprintf(aConfigFile, "%s/%s", aBundlePath, kParametersFile);
321 sprintf(aDisabledFile, "%s/%s", aBundlePath, kDisabledFile);
e91b9f68
A
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 {
ed34e3c3 336 int aConfigFileDescriptor;
e91b9f68 337
ed34e3c3
A
338 if ((aConfigFileDescriptor = open(aConfigFile, O_RDONLY|O_NOCTTY, (mode_t) 0)) != -1) {
339 struct stat aConfigFileStatBuffer;
e91b9f68
A
340
341 if (stat(aConfigFile, &aConfigFileStatBuffer) != -1) {
ed34e3c3
A
342 off_t aConfigFileContentsSize = aConfigFileStatBuffer.st_size;
343 char *aConfigFileContentsBuffer;
e91b9f68
A
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) {
ed34e3c3 349 CFDataRef aConfigData = NULL;
e91b9f68
A
350 CFMutableDictionaryRef aConfig = NULL;
351
352 aConfigData =
353 CFDataCreateWithBytesNoCopy(NULL,
aa59983a 354 (const UInt8 *)aConfigFileContentsBuffer,
e91b9f68
A
355 aConfigFileContentsSize,
356 kCFAllocatorNull);
357
358 if (aConfigData) {
359 aConfig = (CFMutableDictionaryRef)
ed34e3c3
A
360 CFPropertyListCreateFromXMLData(NULL, aConfigData,
361 kCFPropertyListMutableContainers,
362 NULL);
e91b9f68
A
363 }
364 if (StartupItemValidate(aConfig)) {
ed34e3c3
A
365 CFStringRef aBundlePathString =
366 CFStringCreateWithCString(NULL, aBundlePath,
367 kCFStringEncodingUTF8);
e91b9f68 368
ed34e3c3
A
369 CFNumberRef aDomainNumber =
370 CFNumberCreate(NULL, kCFNumberCFIndexType,
371 &aDomainIndex);
e91b9f68 372
ed34e3c3
A
373 CFDictionarySetValue(aConfig, kBundlePathKey,
374 aBundlePathString);
e91b9f68
A
375 CFDictionarySetValue(aConfig, kDomainKey, aDomainNumber);
376 CFRelease(aDomainNumber);
377 SpecialCasesStartupItemHandler(aConfig);
378 CFArrayAppendValue(anItemList, aConfig);
379
380 CFRelease(aBundlePathString);
381 } else {
ed34e3c3
A
382 syslog(LOG_ERR, "Malformatted parameters file: %s",
383 aConfigFile);
e91b9f68
A
384 }
385
386 if (aConfig)
387 CFRelease(aConfig);
388 if (aConfigData)
389 CFRelease(aConfigData);
390
ed34e3c3
A
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);
e91b9f68
A
396 }
397 } else {
ed34e3c3
A
398 syslog(LOG_ERR,
399 "Unable to map parameters file %s for item %s: %m",
400 aConfigFile, aBundleName);
e91b9f68
A
401 }
402 } else {
ed34e3c3
A
403 syslog(LOG_ERR, "Unable to stat parameters file %s for item %s: %m",
404 aConfigFile, aBundleName);
e91b9f68
A
405 }
406
407 if (close(aConfigFileDescriptor) == -1) {
ed34e3c3
A
408 syslog(LOG_ERR, "Unable to close parameters file %s for item %s: %m",
409 aConfigFile, aBundleName);
e91b9f68
A
410 }
411 } else {
ed34e3c3
A
412 syslog(LOG_ERR, "Unable to open parameters file %s for item %s: %m", aConfigFile,
413 aBundleName);
e91b9f68
A
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
ed34e3c3 431CFMutableDictionaryRef StartupItemListGetProvider(CFArrayRef anItemList, CFStringRef aService)
e91b9f68
A
432{
433 CFMutableDictionaryRef aResult = NULL;
ddbbfbc1 434 CFMutableArrayRef aList = startupItemListCopyMatches(anItemList, kProvidesKey, aService);
e91b9f68
A
435
436 if (aList && CFArrayGetCount(aList) > 0)
437 aResult = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aList, 0);
438
ddbbfbc1
A
439 if (aList) CFRelease(aList);
440
e91b9f68
A
441 return aResult;
442}
443
ed34e3c3 444CFArrayRef StartupItemListGetRunning(CFArrayRef anItemList)
e91b9f68
A
445{
446 CFMutableArrayRef aResult = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
447 if (aResult) {
ed34e3c3 448 CFIndex anIndex, aCount = CFArrayGetCount(anItemList);
e91b9f68
A
449 for (anIndex = 0; anIndex < aCount; ++anIndex) {
450 CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anIndex);
451 if (anItem) {
ed34e3c3 452 CFNumberRef aPID = CFDictionaryGetValue(anItem, kPIDKey);
e91b9f68
A
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 */
ed34e3c3 469static void appendDependents(CFMutableArrayRef aDependents, CFArrayRef anItemList, CFDictionaryRef aParentItem, Action anAction)
e91b9f68 470{
ed34e3c3
A
471 CFStringRef anInnerKey, anOuterKey;
472 CFArrayRef anOuterList;
e91b9f68
A
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) {
ed34e3c3
A
498 CFIndex anOuterCount = CFArrayGetCount(anOuterList);
499 CFIndex anOuterIndex;
e91b9f68
A
500
501 for (anOuterIndex = 0; anOuterIndex < anOuterCount; anOuterIndex++) {
ed34e3c3
A
502 CFStringRef anOuterElement = CFArrayGetValueAtIndex(anOuterList, anOuterIndex);
503 CFIndex anItemCount = CFArrayGetCount(anItemList);
504 CFIndex anItemIndex;
e91b9f68
A
505
506 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
507 CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
ed34e3c3 508 CFArrayRef anInnerList = CFDictionaryGetValue(anItem, anInnerKey);
e91b9f68
A
509
510 if (anInnerList &&
ed34e3c3
A
511 CFArrayContainsValue(anInnerList, CFRangeMake(0, CFArrayGetCount(anInnerList)),
512 anOuterElement)
513 && !CFArrayContainsValue(aDependents, CFRangeMake(0, CFArrayGetCount(aDependents)), anItem))
e91b9f68
A
514 appendDependents(aDependents, anItemList, anItem, anAction);
515 }
516 }
517 }
518}
519
ed34e3c3 520CFMutableArrayRef StartupItemListCreateDependentsList(CFMutableArrayRef anItemList, CFStringRef aService, Action anAction)
e91b9f68
A
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 **/
ed34e3c3 553static int countUnmetRequirements(CFDictionaryRef aStatusDict, CFArrayRef anItemList)
e91b9f68 554{
ed34e3c3
A
555 int aCount = 0;
556 CFIndex anItemCount = CFArrayGetCount(anItemList);
557 CFIndex anItemIndex;
e91b9f68
A
558
559 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
ed34e3c3
A
560 CFStringRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
561 CFStringRef aStatus = CFDictionaryGetValue(aStatusDict, anItem);
e91b9f68
A
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 **/
ed34e3c3 576static int countDependantsPresent(CFArrayRef aWaitingList, CFArrayRef anItemList, CFStringRef aKey)
e91b9f68 577{
ed34e3c3
A
578 int aCount = 0;
579 CFIndex anItemCount = CFArrayGetCount(anItemList);
580 CFIndex anItemIndex;
e91b9f68
A
581
582 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
ed34e3c3 583 CFStringRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
ddbbfbc1 584 CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, aKey, anItem);
e91b9f68
A
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 **/
ed34e3c3 599static Boolean
e91b9f68
A
600pendingAntecedents(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, CFArrayRef anAntecedentList, Action anAction)
601{
ed34e3c3 602 int aPendingFlag = FALSE;
e91b9f68 603
ed34e3c3
A
604 CFIndex anAntecedentCount = CFArrayGetCount(anAntecedentList);
605 CFIndex anAntecedentIndex;
e91b9f68
A
606
607 for (anAntecedentIndex = 0; anAntecedentIndex < anAntecedentCount; ++anAntecedentIndex) {
ed34e3c3
A
608 CFStringRef anAntecedent = CFArrayGetValueAtIndex(anAntecedentList, anAntecedentIndex);
609 CFStringRef aKey = (anAction == kActionStart) ? kProvidesKey : kUsesKey;
ddbbfbc1 610 CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, aKey, anAntecedent);
e91b9f68
A
611
612 if (aMatchesList) {
ed34e3c3
A
613 CFIndex aMatchesListCount = CFArrayGetCount(aMatchesList);
614 CFIndex aMatchesListIndex;
e91b9f68
A
615
616 for (aMatchesListIndex = 0; aMatchesListIndex < aMatchesListCount; ++aMatchesListIndex) {
617 CFDictionaryRef anItem = CFArrayGetValueAtIndex(aMatchesList, aMatchesListIndex);
618
619 if (!anItem ||
ed34e3c3 620 !CFDictionaryGetValue(anItem, kPIDKey) || !CFDictionaryGetValue(aStatusDict, anAntecedent)) {
e91b9f68
A
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 **/
ed34e3c3 639static Boolean checkForDuplicates(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, CFDictionaryRef anItem)
e91b9f68 640{
ed34e3c3 641 int aDuplicateFlag = FALSE;
e91b9f68 642
ed34e3c3
A
643 CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
644 CFIndex aProvidesCount = aProvidesList ? CFArrayGetCount(aProvidesList) : 0;
645 CFIndex aProvidesIndex;
e91b9f68
A
646
647 for (aProvidesIndex = 0; aProvidesIndex < aProvidesCount; ++aProvidesIndex) {
ed34e3c3 648 CFStringRef aProvides = CFArrayGetValueAtIndex(aProvidesList, aProvidesIndex);
e91b9f68
A
649
650 /* If the service succeeded, return true. */
ed34e3c3 651 CFStringRef aStatus = CFDictionaryGetValue(aStatusDict, aProvides);
e91b9f68
A
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 {
ddbbfbc1 661 CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, kProvidesKey, aProvides);
e91b9f68 662 if (aMatchesList) {
ed34e3c3
A
663 CFIndex aMatchesListCount = CFArrayGetCount(aMatchesList);
664 CFIndex aMatchesListIndex;
e91b9f68
A
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 {
ed34e3c3
A
676 CFNumberRef anItemDomain = CFDictionaryGetValue(anItem, kDomainKey);
677 CFNumberRef anotherItemDomain = CFDictionaryGetValue(anDupItem, kDomainKey);
e91b9f68
A
678 /*
679 * If anItem was found later
680 * than aDupItem, stall
681 * anItem until aDupItem
682 * runs.
683 */
684 if (anItemDomain &&
685 anotherItemDomain &&
ed34e3c3
A
686 CFNumberCompare(anItemDomain, anotherItemDomain,
687 NULL) == kCFCompareGreaterThan) {
e91b9f68
A
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
ed34e3c3 708CFMutableDictionaryRef StartupItemListGetNext(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, Action anAction)
e91b9f68
A
709{
710 CFMutableDictionaryRef aNextItem = NULL;
ed34e3c3
A
711 CFIndex aWaitingCount = CFArrayGetCount(aWaitingList);
712 int aMinFailedAntecedents = INT_MAX;
713 CFIndex aWaitingIndex;
e91b9f68
A
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
ed34e3c3
A
726 if (!aWaitingList || !aStatusDict || aWaitingCount <= 0)
727 return NULL;
e91b9f68 728
ed34e3c3
A
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;
e91b9f68 743
ed34e3c3
A
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));
e91b9f68 762
ed34e3c3 763 CF_syslog(LOG_DEBUG, CFSTR("Checking %@"), CFDictionaryGetValue(anItem, kDescriptionKey));
e91b9f68 764
ed34e3c3
A
765 if (anAntecedentList)
766 CF_syslog(LOG_DEBUG, CFSTR("Antecedents: %@"), anAntecedentList);
767 else
768 syslog(LOG_DEBUG, "No antecedents");
e91b9f68
A
769
770 /**
771 * Filter out the items which have unsatisfied antecedents.
772 **/
ed34e3c3
A
773 if (anAntecedentList &&
774 ((anAction == kActionStart) ?
775 countUnmetRequirements(aStatusDict, anAntecedentList) :
776 countDependantsPresent(aWaitingList, anAntecedentList, kRequiresKey)))
777 continue;
e91b9f68
A
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 **/
ed34e3c3 783 anAntecedentList = CFDictionaryGetValue(anItem, ((anAction == kActionStart) ? kUsesKey : kProvidesKey));
e91b9f68 784
ed34e3c3
A
785 if (anAntecedentList)
786 CF_syslog(LOG_DEBUG, CFSTR("Soft dependancies: %@"), anAntecedentList);
787 else
788 syslog(LOG_DEBUG, "No soft dependancies");
e91b9f68 789
ed34e3c3
A
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 }
e91b9f68 799
ed34e3c3
A
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);
e91b9f68 809
ed34e3c3
A
810 if (aFailedAntecedentsCount > aMinFailedAntecedents)
811 continue; /* Another item already won out */
e91b9f68 812
ed34e3c3
A
813 if (aFailedAntecedentsCount < aMinFailedAntecedents)
814 aBestPick = TRUE;
e91b9f68 815
ed34e3c3
A
816 if (!aBestPick)
817 continue;
e91b9f68 818
ed34e3c3
A
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);
e91b9f68 827
ed34e3c3
A
828 /*
829 * We have a winner! Update success
830 * parameters to match anItem.
831 */
832 aMinFailedAntecedents = aFailedAntecedentsCount;
833 aNextItem = anItem;
e91b9f68 834
ed34e3c3 835 } /* End of waiting list loop. */
e91b9f68 836
e91b9f68
A
837 return aNextItem;
838}
839
ed34e3c3 840CFStringRef StartupItemGetDescription(CFMutableDictionaryRef anItem)
e91b9f68 841{
ed34e3c3 842 CFStringRef aString = NULL;
e91b9f68
A
843
844 if (anItem)
845 aString = CFDictionaryGetValue(anItem, kDescriptionKey);
846 if (aString)
847 CFRetain(aString);
848 return aString;
849}
850
ed34e3c3 851pid_t StartupItemGetPID(CFDictionaryRef anItem)
e91b9f68 852{
ed34e3c3
A
853 CFIndex anItemPID = 0;
854 CFNumberRef aPIDNumber = anItem ? CFDictionaryGetValue(anItem, kPIDKey) : NULL;
e91b9f68
A
855 if (aPIDNumber && CFNumberGetValue(aPIDNumber, kCFNumberCFIndexType, &anItemPID))
856 return (pid_t) anItemPID;
857 else
858 return 0;
859}
860
ed34e3c3 861CFMutableDictionaryRef StartupItemWithPID(CFArrayRef anItemList, pid_t aPID)
e91b9f68 862{
ed34e3c3
A
863 CFIndex anItemCount = CFArrayGetCount(anItemList);
864 CFIndex anItemIndex;
e91b9f68
A
865
866 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
867 CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(anItemList, anItemIndex);
ed34e3c3
A
868 CFNumberRef aPIDNumber = CFDictionaryGetValue(anItem, kPIDKey);
869 CFIndex anItemPID;
e91b9f68
A
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
ed34e3c3 882int StartupItemRun(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Action anAction)
e91b9f68
A
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 {
ed34e3c3 928 CFStringRef aBundlePathString = CFDictionaryGetValue(anItem, kBundlePathKey);
ddbbfbc1
A
929 char aBundlePath[PATH_MAX];
930 char anExecutable[PATH_MAX];
931 char *tmp;
e91b9f68 932
ddbbfbc1 933 if (!CFStringGetCString(aBundlePathString, aBundlePath, sizeof(aBundlePath), kCFStringEncodingUTF8)) {
e91b9f68
A
934 CF_syslog(LOG_EMERG, CFSTR("Internal error while running item %@"), aBundlePathString);
935 return (anError);
936 }
937 /* Compute path to excecutable */
ddbbfbc1
A
938 tmp = rindex(aBundlePath, '/');
939 snprintf(anExecutable, sizeof(anExecutable), "%s%s", aBundlePath, tmp);
e91b9f68
A
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 */
ed34e3c3
A
950 CFIndex aPID = -1;
951 CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
e91b9f68
A
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 {
ed34e3c3 960 pid_t aProccessID = fork();
e91b9f68
A
961
962 switch (aProccessID) {
963 case -1: /* SystemStarter (fork failed) */
964 CFDictionarySetValue(anItem, kErrorKey, kErrorFork);
965 StartupItemExit(aStatusDict, anItem, FALSE);
966
ed34e3c3 967 CF_syslog(LOG_ERR, CFSTR("Failed to fork for item %@: %s"), aBundlePathString, strerror(errno));
e91b9f68
A
968
969 break;
970
971 default: /* SystemStarter (fork succeeded) */
972 {
ed34e3c3
A
973 CFIndex aPID = (CFIndex) aProccessID;
974 CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
e91b9f68
A
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
ed34e3c3 985 case 0: /* Child */
e91b9f68 986 {
e91b9f68
A
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
ed34e3c3
A
1005void
1006StartupItemSetStatus(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, CFStringRef aServiceName,
1007 Boolean aSuccess, Boolean aReplaceFlag)
e91b9f68 1008{
ed34e3c3
A
1009 void (*anAction) (CFMutableDictionaryRef, const void *, const void *) = aReplaceFlag ?
1010 CFDictionarySetValue : CFDictionaryAddValue;
e91b9f68
A
1011
1012 if (aStatusDict && anItem) {
ed34e3c3 1013 CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
e91b9f68 1014 if (aProvidesList) {
ed34e3c3
A
1015 CFIndex aProvidesCount = CFArrayGetCount(aProvidesList);
1016 CFIndex aProvidesIndex;
e91b9f68
A
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)) {
ed34e3c3 1023 aProvidesList = CFArrayCreate(NULL, (const void **)&aServiceName, 1, &kCFTypeArrayCallBacks);
e91b9f68
A
1024 aProvidesCount = 1;
1025 } else {
1026 CFRetain(aProvidesList);
1027 }
1028
1029 for (aProvidesIndex = 0; aProvidesIndex < aProvidesCount; aProvidesIndex++) {
ed34e3c3 1030 CFStringRef aService = CFArrayGetValueAtIndex(aProvidesList, aProvidesIndex);
e91b9f68
A
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
ed34e3c3 1043void StartupItemExit(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Boolean aSuccess)
e91b9f68
A
1044{
1045 StartupItemSetStatus(aStatusDict, anItem, NULL, aSuccess, FALSE);
1046}