2 * Copyright (c) 2000-2004 Apple Computer, Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 * Modification History
27 * April 16, 2002 Allan Nathanson <ajn@apple.com>
28 * - updated to use _SCDPluginExecCommand()
30 * June 23, 2001 Allan Nathanson <ajn@apple.com>
31 * - updated to public SystemConfiguration.framework APIs
33 * June 4, 2001 Allan Nathanson <ajn@apple.com>
34 * - add changed keys as the arguments to the kicker script
36 * June 30, 2000 Allan Nathanson <ajn@apple.com>
44 #include <sys/types.h>
48 #include <SystemConfiguration/SystemConfiguration.h>
49 #include <SystemConfiguration/SCPrivate.h> // for SCLog()
50 #include <SystemConfiguration/SCDPlugin.h>
51 #include <SystemConfiguration/SCValidation.h>
54 * Information maintained for each to-be-kicked registration.
60 /* dictionary associated with this target */
63 /* SCDynamicStore session information for this target */
65 CFRunLoopSourceRef rls
;
66 SCDynamicStoreRef store
;
69 CFMutableArrayRef changedKeys
;
72 static CFURLRef myBundleURL
= NULL
;
73 static Boolean _verbose
= FALSE
;
75 static void booter(kickeeRef target
);
76 static void booterExit(pid_t pid
, int status
, struct rusage
*rusage
, void *context
);
80 cleanupKicker(kickeeRef target
)
82 CFStringRef name
= CFDictionaryGetValue(target
->dict
, CFSTR("name"));
84 SCLog(TRUE
, LOG_NOTICE
,
85 CFSTR(" target=%@: disabled"),
87 CFRunLoopRemoveSource(target
->rl
, target
->rls
, kCFRunLoopDefaultMode
);
88 CFRelease(target
->rls
);
89 CFRelease(target
->store
);
90 if (target
->dict
) CFRelease(target
->dict
);
91 if (target
->changedKeys
) CFRelease(target
->changedKeys
);
92 CFAllocatorDeallocate(NULL
, target
);
97 booter(kickeeRef target
)
101 CFStringRef execCommand
= CFDictionaryGetValue(target
->dict
, CFSTR("execCommand"));
103 CFArrayRef keys
= NULL
;
104 CFStringRef name
= CFDictionaryGetValue(target
->dict
, CFSTR("name"));
107 CFStringRef postName
= CFDictionaryGetValue(target
->dict
, CFSTR("postName"));
109 if (target
->active
) {
110 /* we need another kick! */
111 target
->needsKick
= TRUE
;
113 SCLog(_verbose
, LOG_DEBUG
, CFSTR("Kicker callback, target=%@ request queued"), name
);
117 SCLog(_verbose
, LOG_DEBUG
, CFSTR("Kicker callback, target=%@"), name
);
119 if (!isA_CFString(postName
) && !isA_CFString(execCommand
)) {
120 goto error
; /* if no notifications to post nor commands to execute */
123 if (isA_CFString(postName
)) {
127 * post a notification
129 cmd
= _SC_cfstring_to_cstring(postName
, NULL
, 0, kCFStringEncodingASCII
);
131 SCLog(TRUE
, LOG_DEBUG
, CFSTR(" could not convert post name to C string"));
135 SCLog(TRUE
, LOG_NOTICE
, CFSTR("posting notification %s"), cmd
);
136 status
= notify_post(cmd
);
137 if (status
!= NOTIFY_STATUS_OK
) {
138 SCLog(TRUE
, LOG_DEBUG
, CFSTR(" notify_post() failed: error=%ld"), status
);
142 CFAllocatorDeallocate(NULL
, cmd
); /* clean up */
147 * get the arguments for the kickee
149 keys
= target
->changedKeys
;
150 target
->changedKeys
= NULL
;
152 if (isA_CFString(execCommand
)) {
154 CFNumberRef execGID
= CFDictionaryGetValue(target
->dict
, CFSTR("execGID"));
155 CFNumberRef execUID
= CFDictionaryGetValue(target
->dict
, CFSTR("execUID"));
156 CFBooleanRef passKeys
= CFDictionaryGetValue(target
->dict
, CFSTR("changedKeysAsArguments"));
159 CFMutableStringRef str
;
162 * build the kickee command
164 str
= CFStringCreateMutableCopy(NULL
, 0, execCommand
);
165 bpr
= CFStringFind(str
, CFSTR("$BUNDLE"), 0);
166 if (bpr
.location
!= kCFNotFound
) {
167 CFStringRef bundlePath
;
169 bundlePath
= CFURLCopyFileSystemPath(myBundleURL
, kCFURLPOSIXPathStyle
);
170 CFStringReplace(str
, bpr
, bundlePath
);
171 CFRelease(bundlePath
);
174 cmd
= _SC_cfstring_to_cstring(str
, NULL
, 0, kCFStringEncodingASCII
);
177 SCLog(TRUE
, LOG_DEBUG
, CFSTR(" could not convert command to C string"));
182 * get the UID/GID for the kickee
184 if (isA_CFNumber(execUID
)) {
185 CFNumberGetValue(execUID
, kCFNumberIntType
, &reqUID
);
188 if (isA_CFNumber(execGID
)) {
189 CFNumberGetValue(execGID
, kCFNumberIntType
, &reqGID
);
192 nKeys
= CFArrayGetCount(keys
);
193 argv
= CFAllocatorAllocate(NULL
, (nKeys
+ 2) * sizeof(char *), 0);
194 for (i
= 0; i
< (nKeys
+ 2); i
++) {
198 /* create command name argument */
199 if ((argv
[0] = rindex(cmd
, '/')) != NULL
) {
205 /* create changed key arguments */
206 if (isA_CFBoolean(passKeys
) && CFBooleanGetValue(passKeys
)) {
207 for (i
= 0; i
< nKeys
; i
++) {
208 CFStringRef key
= CFArrayGetValueAtIndex(keys
, i
);
210 argv
[i
+1] = _SC_cfstring_to_cstring(key
, NULL
, 0, kCFStringEncodingASCII
);
212 SCLog(TRUE
, LOG_DEBUG
, CFSTR(" could not convert argument to C string"));
218 SCLog(TRUE
, LOG_NOTICE
, CFSTR("executing %s"), cmd
);
219 SCLog(_verbose
, LOG_DEBUG
, CFSTR(" current uid = %d, requested = %d"), geteuid(), reqUID
);
221 /* this kicker is now "running" */
222 target
->active
= TRUE
;
224 (void)_SCDPluginExecCommand(booterExit
,
231 // CFAllocatorDeallocate(NULL, cmd); /* clean up */
238 if (keys
) CFRelease(keys
);
239 if (cmd
) CFAllocatorDeallocate(NULL
, cmd
);
241 for (i
= 0; i
< nKeys
; i
++) {
243 CFAllocatorDeallocate(NULL
, argv
[i
+1]);
246 CFAllocatorDeallocate(NULL
, argv
);
251 * If the target action can't be performed this time then
252 * there's not much point in trying again. As such, I close
253 * the session and the kickee target released.
255 cleanupKicker(target
);
263 booterExit(pid_t pid
, int status
, struct rusage
*rusage
, void *context
)
267 kickeeRef target
= (kickeeRef
)context
;
269 name
= CFDictionaryGetValue(target
->dict
, CFSTR("name"));
270 target
->active
= FALSE
;
271 if (WIFEXITED(status
)) {
272 SCLog(TRUE
, LOG_DEBUG
,
273 CFSTR(" target=%@: exit status = %d"),
275 WEXITSTATUS(status
));
276 if (WEXITSTATUS(status
) != 0) {
279 } else if (WIFSIGNALED(status
)) {
280 SCLog(TRUE
, LOG_DEBUG
,
281 CFSTR(" target=%@: terminated w/signal = %d"),
286 SCLog(TRUE
, LOG_DEBUG
,
287 CFSTR(" target=%@: exit status = %d"),
294 if (CFDictionaryContainsKey(target
->dict
, CFSTR("postName"))) {
295 CFDictionaryRef oldDict
= target
->dict
;
296 CFMutableDictionaryRef newDict
= CFDictionaryCreateMutableCopy(NULL
, 0, oldDict
);
299 * if this target specifies both a BSD notification and
300 * a script to be executed then we want to continue to
301 * post the BSD notifications (and not execute the
302 * script). As such, remove the script reference from
305 CFDictionaryRemoveValue(newDict
, CFSTR("execCommand"));
306 CFDictionaryRemoveValue(newDict
, CFSTR("execGID"));
307 CFDictionaryRemoveValue(newDict
, CFSTR("execUID"));
308 CFDictionaryRemoveValue(newDict
, CFSTR("changedKeysAsArguments"));
309 target
->dict
= newDict
;
313 * If the target action can't be performed this time then
314 * there's not much point in trying again. As such, I close
315 * the session and the kickee target released.
317 cleanupKicker(target
);
321 if (target
!= NULL
&& target
->needsKick
) {
322 target
->needsKick
= FALSE
;
331 kicker(SCDynamicStoreRef store
, CFArrayRef changedKeys
, void *arg
)
334 CFIndex n
= CFArrayGetCount(changedKeys
);
335 kickeeRef target
= (kickeeRef
)arg
;
338 * Start a new kicker. If a kicker was already active then flag
339 * the need for a second kick after the active one completes.
342 /* create (or add to) the full list of keys that have changed */
343 if (!target
->changedKeys
) {
344 target
->changedKeys
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
346 for (i
= 0; i
< n
; i
++) {
347 CFStringRef key
= CFArrayGetValueAtIndex(changedKeys
, i
);
349 if (!CFArrayContainsValue(target
->changedKeys
,
350 CFRangeMake(0, CFArrayGetCount(target
->changedKeys
)),
352 CFArrayAppendValue(target
->changedKeys
, key
);
368 * The first argument is a dictionary representing the keys
369 * which need to be monitored for a given "target" and what
370 * action should be taken if a change in one of those keys
374 startKicker(const void *value
, void *context
)
376 CFMutableStringRef name
;
379 kickeeRef target
= CFAllocatorAllocate(NULL
, sizeof(kickee
), 0);
380 SCDynamicStoreContext targetContext
= { 0, (void *)target
, NULL
, NULL
, NULL
};
382 target
->active
= FALSE
;
383 target
->needsKick
= FALSE
;
384 target
->dict
= CFRetain((CFDictionaryRef
)value
);
385 target
->store
= NULL
;
388 target
->changedKeys
= NULL
;
390 name
= CFStringCreateMutableCopy(NULL
,
392 CFDictionaryGetValue(target
->dict
, CFSTR("name")));
393 SCLog(TRUE
, LOG_DEBUG
, CFSTR("Starting kicker for %@"), name
);
395 CFStringAppend(name
, CFSTR(" \"Kicker\""));
396 target
->store
= SCDynamicStoreCreate(NULL
, name
, kicker
, &targetContext
);
398 if (!target
->store
) {
401 CFSTR("SCDynamicStoreCreate() failed: %s"),
402 SCErrorString(SCError()));
406 keys
= isA_CFArray(CFDictionaryGetValue(target
->dict
, CFSTR("keys")));
407 patterns
= isA_CFArray(CFDictionaryGetValue(target
->dict
, CFSTR("regexKeys")));
408 if (!SCDynamicStoreSetNotificationKeys(target
->store
, keys
, patterns
)) {
411 CFSTR("SCDynamicStoreSetNotifications() failed: %s"),
412 SCErrorString(SCError()));
416 target
->rl
= CFRunLoopGetCurrent();
417 target
->rls
= SCDynamicStoreCreateRunLoopSource(NULL
, target
->store
, 0);
421 CFSTR("SCDynamicStoreCreateRunLoopSource() failed: %s"),
422 SCErrorString(SCError()));
426 CFRunLoopAddSource(target
->rl
, target
->rls
, kCFRunLoopDefaultMode
);
431 CFRelease(target
->dict
);
432 if (target
->store
) CFRelease(target
->store
);
433 CFAllocatorDeallocate(NULL
, target
);
439 getTargets(CFBundleRef bundle
)
442 CFArrayRef targets
; /* The array of dictionaries
443 representing targets with
444 a "kick me" sign posted on
447 CFStringRef xmlError
;
448 CFDataRef xmlTargets
= NULL
;
450 /* locate the Kicker targets */
451 url
= CFBundleCopyResourceURL(bundle
, CFSTR("Kicker"), CFSTR("xml"), NULL
);
456 /* read the resource data */
457 ok
= CFURLCreateDataAndPropertiesFromResource(NULL
, url
, &xmlTargets
, NULL
, NULL
, NULL
);
459 if (!ok
|| (xmlTargets
== NULL
)) {
463 /* convert the XML data into a property list */
464 targets
= CFPropertyListCreateFromXMLData(NULL
,
466 kCFPropertyListImmutable
,
468 CFRelease(xmlTargets
);
469 if (targets
== NULL
) {
470 if (xmlError
!= NULL
) {
471 SCLog(TRUE
, LOG_DEBUG
, CFSTR("getTargets(): %@"), xmlError
);
477 if (!isA_CFArray(targets
)) {
488 load_Kicker(CFBundleRef bundle
, Boolean bundleVerbose
)
490 CFArrayRef targets
; /* The array of dictionaries representing targets
491 * with a "kick me" sign posted on their backs.*/
497 SCLog(_verbose
, LOG_DEBUG
, CFSTR("load() called"));
498 SCLog(_verbose
, LOG_DEBUG
, CFSTR(" bundle ID = %@"), CFBundleGetIdentifier(bundle
));
500 /* get the bundle's URL */
501 myBundleURL
= CFBundleCopyBundleURL(bundle
);
502 if (myBundleURL
== NULL
) {
506 /* get the targets */
507 targets
= getTargets(bundle
);
508 if (targets
== NULL
) {
509 /* if nothing to do */
510 CFRelease(myBundleURL
);
514 /* start a kicker for each target */
515 CFArrayApplyFunction(targets
,
516 CFRangeMake(0, CFArrayGetCount(targets
)),
526 main(int argc
, char * const argv
[])
529 _sc_verbose
= (argc
> 1) ? TRUE
: FALSE
;
531 load_Kicker(CFBundleGetMainBundle(), (argc
> 1) ? TRUE
: FALSE
);