]> git.saurik.com Git - apple/launchd.git/blame - SystemStarter/StartupItems.c
launchd-842.92.1.tar.gz
[apple/launchd.git] / SystemStarter / 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) {
e91b9f68
A
186 CFArrayAppendValue(aNewList, ci);
187 CF_syslog(LOG_DEBUG, CFSTR("%@: Keeping %@"), type, ci);
188 }
189 }
190
191 CFDictionaryReplaceValue(aConfig, type, aNewList);
ddbbfbc1 192 CFRelease(aNewList);
e91b9f68
A
193 }
194 if (type == kUsesKey)
195 return;
196 type = kUsesKey;
197 goto again;
198}
199
ed34e3c3 200CFIndex StartupItemListCountServices(CFArrayRef anItemList)
e91b9f68 201{
ed34e3c3 202 CFIndex aResult = 0;
e91b9f68
A
203
204 if (anItemList) {
ed34e3c3
A
205 CFIndex anItemCount = CFArrayGetCount(anItemList);
206 CFIndex anItemIndex = 0;
e91b9f68
A
207
208 for (anItemIndex = 0; anItemIndex < anItemCount; ++anItemIndex) {
209 CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
ed34e3c3 210 CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
e91b9f68
A
211
212 if (aProvidesList)
213 aResult += CFArrayGetCount(aProvidesList);
214 }
215 }
216 return aResult;
217}
218
ddbbfbc1 219bool StartupItemSecurityCheck(const char *aPath)
e91b9f68 220{
5b0a4722 221 static struct timeval boot_time;
e91b9f68
A
222 struct stat aStatBuf;
223 bool r = true;
224
5b0a4722
A
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
e91b9f68
A
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 }
5b0a4722
A
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 }
e91b9f68
A
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 }
5b0a4722
A
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);
e91b9f68
A
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);
ed34e3c3 272 close(open(kFixerPath, O_RDWR|O_CREAT|O_NOCTTY, DEFFILEMODE));
e91b9f68
A
273 }
274 return r;
275}
276
ed34e3c3 277CFMutableArrayRef StartupItemListCreateWithMask(NSSearchPathDomainMask aMask)
e91b9f68
A
278{
279 CFMutableArrayRef anItemList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
280
ed34e3c3
A
281 char aPath[PATH_MAX];
282 CFIndex aDomainIndex = 0;
e91b9f68
A
283
284 NSSearchPathEnumerationState aState = NSStartSearchPathEnumeration(NSLibraryDirectory, aMask);
285
286 while ((aState = NSGetNextSearchPathEnumeration(aState, aPath))) {
ed34e3c3 287 DIR *aDirectory;
e91b9f68 288
ddbbfbc1 289 strlcat(aPath, kStartupItemsPath, sizeof(aPath));
e91b9f68
A
290 ++aDomainIndex;
291
5b0a4722
A
292 /* 5485016
293 *
294 * Just in case...
295 */
ddbbfbc1 296 mkdir(aPath, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
5b0a4722 297
e91b9f68
A
298 if (!StartupItemSecurityCheck(aPath))
299 continue;
300
301 if ((aDirectory = opendir(aPath))) {
ed34e3c3 302 struct dirent *aBundle;
e91b9f68
A
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
ed34e3c3 317 sprintf(aBundlePath, "%s/%s", aPath, aBundleName);
e91b9f68 318 sprintf(aBundleExecutablePath, "%s/%s", aBundlePath, aBundleName);
ed34e3c3
A
319 sprintf(aConfigFile, "%s/%s", aBundlePath, kParametersFile);
320 sprintf(aDisabledFile, "%s/%s", aBundlePath, kDisabledFile);
e91b9f68
A
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 {
ed34e3c3 335 int aConfigFileDescriptor;
e91b9f68 336
ed34e3c3
A
337 if ((aConfigFileDescriptor = open(aConfigFile, O_RDONLY|O_NOCTTY, (mode_t) 0)) != -1) {
338 struct stat aConfigFileStatBuffer;
e91b9f68
A
339
340 if (stat(aConfigFile, &aConfigFileStatBuffer) != -1) {
ed34e3c3
A
341 off_t aConfigFileContentsSize = aConfigFileStatBuffer.st_size;
342 char *aConfigFileContentsBuffer;
e91b9f68
A
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) {
ed34e3c3 348 CFDataRef aConfigData = NULL;
e91b9f68
A
349 CFMutableDictionaryRef aConfig = NULL;
350
351 aConfigData =
352 CFDataCreateWithBytesNoCopy(NULL,
aa59983a 353 (const UInt8 *)aConfigFileContentsBuffer,
e91b9f68
A
354 aConfigFileContentsSize,
355 kCFAllocatorNull);
356
357 if (aConfigData) {
358 aConfig = (CFMutableDictionaryRef)
ed34e3c3
A
359 CFPropertyListCreateFromXMLData(NULL, aConfigData,
360 kCFPropertyListMutableContainers,
361 NULL);
e91b9f68
A
362 }
363 if (StartupItemValidate(aConfig)) {
ed34e3c3
A
364 CFStringRef aBundlePathString =
365 CFStringCreateWithCString(NULL, aBundlePath,
366 kCFStringEncodingUTF8);
e91b9f68 367
ed34e3c3
A
368 CFNumberRef aDomainNumber =
369 CFNumberCreate(NULL, kCFNumberCFIndexType,
370 &aDomainIndex);
e91b9f68 371
ed34e3c3
A
372 CFDictionarySetValue(aConfig, kBundlePathKey,
373 aBundlePathString);
e91b9f68
A
374 CFDictionarySetValue(aConfig, kDomainKey, aDomainNumber);
375 CFRelease(aDomainNumber);
376 SpecialCasesStartupItemHandler(aConfig);
377 CFArrayAppendValue(anItemList, aConfig);
378
379 CFRelease(aBundlePathString);
380 } else {
ed34e3c3
A
381 syslog(LOG_ERR, "Malformatted parameters file: %s",
382 aConfigFile);
e91b9f68
A
383 }
384
385 if (aConfig)
386 CFRelease(aConfig);
387 if (aConfigData)
388 CFRelease(aConfigData);
389
ed34e3c3
A
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);
e91b9f68
A
395 }
396 } else {
ed34e3c3
A
397 syslog(LOG_ERR,
398 "Unable to map parameters file %s for item %s: %m",
399 aConfigFile, aBundleName);
e91b9f68
A
400 }
401 } else {
ed34e3c3
A
402 syslog(LOG_ERR, "Unable to stat parameters file %s for item %s: %m",
403 aConfigFile, aBundleName);
e91b9f68
A
404 }
405
406 if (close(aConfigFileDescriptor) == -1) {
ed34e3c3
A
407 syslog(LOG_ERR, "Unable to close parameters file %s for item %s: %m",
408 aConfigFile, aBundleName);
e91b9f68
A
409 }
410 } else {
ed34e3c3
A
411 syslog(LOG_ERR, "Unable to open parameters file %s for item %s: %m", aConfigFile,
412 aBundleName);
e91b9f68
A
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
ed34e3c3 430CFMutableDictionaryRef StartupItemListGetProvider(CFArrayRef anItemList, CFStringRef aService)
e91b9f68
A
431{
432 CFMutableDictionaryRef aResult = NULL;
ddbbfbc1 433 CFMutableArrayRef aList = startupItemListCopyMatches(anItemList, kProvidesKey, aService);
e91b9f68
A
434
435 if (aList && CFArrayGetCount(aList) > 0)
436 aResult = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(aList, 0);
437
ddbbfbc1
A
438 if (aList) CFRelease(aList);
439
e91b9f68
A
440 return aResult;
441}
442
dcace88f 443CFArrayRef StartupItemListCreateFromRunning(CFArrayRef anItemList)
e91b9f68
A
444{
445 CFMutableArrayRef aResult = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
446 if (aResult) {
ed34e3c3 447 CFIndex anIndex, aCount = CFArrayGetCount(anItemList);
e91b9f68
A
448 for (anIndex = 0; anIndex < aCount; ++anIndex) {
449 CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anIndex);
450 if (anItem) {
ed34e3c3 451 CFNumberRef aPID = CFDictionaryGetValue(anItem, kPIDKey);
e91b9f68
A
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 */
ed34e3c3 468static void appendDependents(CFMutableArrayRef aDependents, CFArrayRef anItemList, CFDictionaryRef aParentItem, Action anAction)
e91b9f68 469{
ed34e3c3
A
470 CFStringRef anInnerKey, anOuterKey;
471 CFArrayRef anOuterList;
e91b9f68
A
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) {
ed34e3c3
A
497 CFIndex anOuterCount = CFArrayGetCount(anOuterList);
498 CFIndex anOuterIndex;
e91b9f68
A
499
500 for (anOuterIndex = 0; anOuterIndex < anOuterCount; anOuterIndex++) {
ed34e3c3
A
501 CFStringRef anOuterElement = CFArrayGetValueAtIndex(anOuterList, anOuterIndex);
502 CFIndex anItemCount = CFArrayGetCount(anItemList);
503 CFIndex anItemIndex;
e91b9f68
A
504
505 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
506 CFDictionaryRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
ed34e3c3 507 CFArrayRef anInnerList = CFDictionaryGetValue(anItem, anInnerKey);
e91b9f68
A
508
509 if (anInnerList &&
ed34e3c3
A
510 CFArrayContainsValue(anInnerList, CFRangeMake(0, CFArrayGetCount(anInnerList)),
511 anOuterElement)
512 && !CFArrayContainsValue(aDependents, CFRangeMake(0, CFArrayGetCount(aDependents)), anItem))
e91b9f68
A
513 appendDependents(aDependents, anItemList, anItem, anAction);
514 }
515 }
516 }
517}
518
ed34e3c3 519CFMutableArrayRef StartupItemListCreateDependentsList(CFMutableArrayRef anItemList, CFStringRef aService, Action anAction)
e91b9f68
A
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 **/
ed34e3c3 552static int countUnmetRequirements(CFDictionaryRef aStatusDict, CFArrayRef anItemList)
e91b9f68 553{
ed34e3c3
A
554 int aCount = 0;
555 CFIndex anItemCount = CFArrayGetCount(anItemList);
556 CFIndex anItemIndex;
e91b9f68
A
557
558 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
ed34e3c3
A
559 CFStringRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
560 CFStringRef aStatus = CFDictionaryGetValue(aStatusDict, anItem);
e91b9f68
A
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 **/
ed34e3c3 575static int countDependantsPresent(CFArrayRef aWaitingList, CFArrayRef anItemList, CFStringRef aKey)
e91b9f68 576{
ed34e3c3
A
577 int aCount = 0;
578 CFIndex anItemCount = CFArrayGetCount(anItemList);
579 CFIndex anItemIndex;
e91b9f68
A
580
581 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
ed34e3c3 582 CFStringRef anItem = CFArrayGetValueAtIndex(anItemList, anItemIndex);
ddbbfbc1 583 CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, aKey, anItem);
e91b9f68
A
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 **/
ed34e3c3 598static Boolean
e91b9f68
A
599pendingAntecedents(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, CFArrayRef anAntecedentList, Action anAction)
600{
ed34e3c3 601 int aPendingFlag = FALSE;
e91b9f68 602
ed34e3c3
A
603 CFIndex anAntecedentCount = CFArrayGetCount(anAntecedentList);
604 CFIndex anAntecedentIndex;
e91b9f68
A
605
606 for (anAntecedentIndex = 0; anAntecedentIndex < anAntecedentCount; ++anAntecedentIndex) {
ed34e3c3
A
607 CFStringRef anAntecedent = CFArrayGetValueAtIndex(anAntecedentList, anAntecedentIndex);
608 CFStringRef aKey = (anAction == kActionStart) ? kProvidesKey : kUsesKey;
ddbbfbc1 609 CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, aKey, anAntecedent);
e91b9f68
A
610
611 if (aMatchesList) {
ed34e3c3
A
612 CFIndex aMatchesListCount = CFArrayGetCount(aMatchesList);
613 CFIndex aMatchesListIndex;
e91b9f68
A
614
615 for (aMatchesListIndex = 0; aMatchesListIndex < aMatchesListCount; ++aMatchesListIndex) {
616 CFDictionaryRef anItem = CFArrayGetValueAtIndex(aMatchesList, aMatchesListIndex);
617
618 if (!anItem ||
ed34e3c3 619 !CFDictionaryGetValue(anItem, kPIDKey) || !CFDictionaryGetValue(aStatusDict, anAntecedent)) {
e91b9f68
A
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 **/
ed34e3c3 638static Boolean checkForDuplicates(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, CFDictionaryRef anItem)
e91b9f68 639{
ed34e3c3 640 int aDuplicateFlag = FALSE;
e91b9f68 641
ed34e3c3
A
642 CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
643 CFIndex aProvidesCount = aProvidesList ? CFArrayGetCount(aProvidesList) : 0;
644 CFIndex aProvidesIndex;
e91b9f68
A
645
646 for (aProvidesIndex = 0; aProvidesIndex < aProvidesCount; ++aProvidesIndex) {
ed34e3c3 647 CFStringRef aProvides = CFArrayGetValueAtIndex(aProvidesList, aProvidesIndex);
e91b9f68
A
648
649 /* If the service succeeded, return true. */
ed34e3c3 650 CFStringRef aStatus = CFDictionaryGetValue(aStatusDict, aProvides);
e91b9f68
A
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 {
ddbbfbc1 660 CFArrayRef aMatchesList = startupItemListCopyMatches(aWaitingList, kProvidesKey, aProvides);
e91b9f68 661 if (aMatchesList) {
ed34e3c3
A
662 CFIndex aMatchesListCount = CFArrayGetCount(aMatchesList);
663 CFIndex aMatchesListIndex;
e91b9f68
A
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 {
ed34e3c3
A
675 CFNumberRef anItemDomain = CFDictionaryGetValue(anItem, kDomainKey);
676 CFNumberRef anotherItemDomain = CFDictionaryGetValue(anDupItem, kDomainKey);
e91b9f68
A
677 /*
678 * If anItem was found later
679 * than aDupItem, stall
680 * anItem until aDupItem
681 * runs.
682 */
683 if (anItemDomain &&
684 anotherItemDomain &&
ed34e3c3
A
685 CFNumberCompare(anItemDomain, anotherItemDomain,
686 NULL) == kCFCompareGreaterThan) {
e91b9f68
A
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
ed34e3c3 707CFMutableDictionaryRef StartupItemListGetNext(CFArrayRef aWaitingList, CFDictionaryRef aStatusDict, Action anAction)
e91b9f68
A
708{
709 CFMutableDictionaryRef aNextItem = NULL;
ed34e3c3
A
710 CFIndex aWaitingCount = CFArrayGetCount(aWaitingList);
711 int aMinFailedAntecedents = INT_MAX;
712 CFIndex aWaitingIndex;
e91b9f68
A
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
ed34e3c3
A
725 if (!aWaitingList || !aStatusDict || aWaitingCount <= 0)
726 return NULL;
e91b9f68 727
ed34e3c3
A
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;
e91b9f68 742
ed34e3c3
A
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));
e91b9f68 761
ed34e3c3 762 CF_syslog(LOG_DEBUG, CFSTR("Checking %@"), CFDictionaryGetValue(anItem, kDescriptionKey));
e91b9f68 763
ed34e3c3
A
764 if (anAntecedentList)
765 CF_syslog(LOG_DEBUG, CFSTR("Antecedents: %@"), anAntecedentList);
766 else
767 syslog(LOG_DEBUG, "No antecedents");
e91b9f68
A
768
769 /**
770 * Filter out the items which have unsatisfied antecedents.
771 **/
ed34e3c3
A
772 if (anAntecedentList &&
773 ((anAction == kActionStart) ?
774 countUnmetRequirements(aStatusDict, anAntecedentList) :
775 countDependantsPresent(aWaitingList, anAntecedentList, kRequiresKey)))
776 continue;
e91b9f68
A
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 **/
ed34e3c3 782 anAntecedentList = CFDictionaryGetValue(anItem, ((anAction == kActionStart) ? kUsesKey : kProvidesKey));
e91b9f68 783
ed34e3c3
A
784 if (anAntecedentList)
785 CF_syslog(LOG_DEBUG, CFSTR("Soft dependancies: %@"), anAntecedentList);
786 else
787 syslog(LOG_DEBUG, "No soft dependancies");
e91b9f68 788
ed34e3c3
A
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 }
e91b9f68 798
ed34e3c3
A
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);
e91b9f68 808
ed34e3c3
A
809 if (aFailedAntecedentsCount > aMinFailedAntecedents)
810 continue; /* Another item already won out */
e91b9f68 811
ed34e3c3
A
812 if (aFailedAntecedentsCount < aMinFailedAntecedents)
813 aBestPick = TRUE;
e91b9f68 814
ed34e3c3
A
815 if (!aBestPick)
816 continue;
e91b9f68 817
ed34e3c3
A
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);
e91b9f68 826
ed34e3c3
A
827 /*
828 * We have a winner! Update success
829 * parameters to match anItem.
830 */
831 aMinFailedAntecedents = aFailedAntecedentsCount;
832 aNextItem = anItem;
e91b9f68 833
ed34e3c3 834 } /* End of waiting list loop. */
e91b9f68 835
e91b9f68
A
836 return aNextItem;
837}
838
dcace88f 839CFStringRef StartupItemCreateDescription(CFMutableDictionaryRef anItem)
e91b9f68 840{
ed34e3c3 841 CFStringRef aString = NULL;
e91b9f68
A
842
843 if (anItem)
844 aString = CFDictionaryGetValue(anItem, kDescriptionKey);
845 if (aString)
846 CFRetain(aString);
847 return aString;
848}
849
ed34e3c3 850pid_t StartupItemGetPID(CFDictionaryRef anItem)
e91b9f68 851{
ed34e3c3
A
852 CFIndex anItemPID = 0;
853 CFNumberRef aPIDNumber = anItem ? CFDictionaryGetValue(anItem, kPIDKey) : NULL;
e91b9f68
A
854 if (aPIDNumber && CFNumberGetValue(aPIDNumber, kCFNumberCFIndexType, &anItemPID))
855 return (pid_t) anItemPID;
856 else
857 return 0;
858}
859
ed34e3c3 860CFMutableDictionaryRef StartupItemWithPID(CFArrayRef anItemList, pid_t aPID)
e91b9f68 861{
ed34e3c3
A
862 CFIndex anItemCount = CFArrayGetCount(anItemList);
863 CFIndex anItemIndex;
e91b9f68
A
864
865 for (anItemIndex = 0; anItemIndex < anItemCount; anItemIndex++) {
866 CFMutableDictionaryRef anItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(anItemList, anItemIndex);
ed34e3c3
A
867 CFNumberRef aPIDNumber = CFDictionaryGetValue(anItem, kPIDKey);
868 CFIndex anItemPID;
e91b9f68
A
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
ed34e3c3 881int StartupItemRun(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Action anAction)
e91b9f68
A
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 {
ed34e3c3 927 CFStringRef aBundlePathString = CFDictionaryGetValue(anItem, kBundlePathKey);
ddbbfbc1
A
928 char aBundlePath[PATH_MAX];
929 char anExecutable[PATH_MAX];
930 char *tmp;
e91b9f68 931
ddbbfbc1 932 if (!CFStringGetCString(aBundlePathString, aBundlePath, sizeof(aBundlePath), kCFStringEncodingUTF8)) {
e91b9f68
A
933 CF_syslog(LOG_EMERG, CFSTR("Internal error while running item %@"), aBundlePathString);
934 return (anError);
935 }
936 /* Compute path to excecutable */
ddbbfbc1
A
937 tmp = rindex(aBundlePath, '/');
938 snprintf(anExecutable, sizeof(anExecutable), "%s%s", aBundlePath, tmp);
e91b9f68
A
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 */
ed34e3c3
A
949 CFIndex aPID = -1;
950 CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
e91b9f68
A
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 {
ed34e3c3 959 pid_t aProccessID = fork();
e91b9f68
A
960
961 switch (aProccessID) {
962 case -1: /* SystemStarter (fork failed) */
963 CFDictionarySetValue(anItem, kErrorKey, kErrorFork);
964 StartupItemExit(aStatusDict, anItem, FALSE);
965
ed34e3c3 966 CF_syslog(LOG_ERR, CFSTR("Failed to fork for item %@: %s"), aBundlePathString, strerror(errno));
e91b9f68
A
967
968 break;
969
970 default: /* SystemStarter (fork succeeded) */
971 {
ed34e3c3
A
972 CFIndex aPID = (CFIndex) aProccessID;
973 CFNumberRef aProcessNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);
e91b9f68
A
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
ed34e3c3 984 case 0: /* Child */
e91b9f68 985 {
e91b9f68
A
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
ed34e3c3
A
1004void
1005StartupItemSetStatus(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, CFStringRef aServiceName,
1006 Boolean aSuccess, Boolean aReplaceFlag)
e91b9f68 1007{
ed34e3c3
A
1008 void (*anAction) (CFMutableDictionaryRef, const void *, const void *) = aReplaceFlag ?
1009 CFDictionarySetValue : CFDictionaryAddValue;
e91b9f68
A
1010
1011 if (aStatusDict && anItem) {
ed34e3c3 1012 CFArrayRef aProvidesList = CFDictionaryGetValue(anItem, kProvidesKey);
e91b9f68 1013 if (aProvidesList) {
ed34e3c3
A
1014 CFIndex aProvidesCount = CFArrayGetCount(aProvidesList);
1015 CFIndex aProvidesIndex;
e91b9f68
A
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)) {
ed34e3c3 1022 aProvidesList = CFArrayCreate(NULL, (const void **)&aServiceName, 1, &kCFTypeArrayCallBacks);
e91b9f68
A
1023 aProvidesCount = 1;
1024 } else {
1025 CFRetain(aProvidesList);
1026 }
1027
1028 for (aProvidesIndex = 0; aProvidesIndex < aProvidesCount; aProvidesIndex++) {
ed34e3c3 1029 CFStringRef aService = CFArrayGetValueAtIndex(aProvidesList, aProvidesIndex);
e91b9f68
A
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
ed34e3c3 1042void StartupItemExit(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem, Boolean aSuccess)
e91b9f68
A
1043{
1044 StartupItemSetStatus(aStatusDict, anItem, NULL, aSuccess, FALSE);
1045}