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 * February 16, 2004 Allan Nathanson <ajn@apple.com>
28 * - add preference notification APIs
30 * June 1, 2001 Allan Nathanson <ajn@apple.com>
31 * - public API conversion
33 * November 9, 2000 Allan Nathanson <ajn@apple.com>
37 #include <SystemConfiguration/SystemConfiguration.h>
38 #include <SystemConfiguration/SCValidation.h>
39 #include <SystemConfiguration/SCPrivate.h>
40 #include "SCPreferencesInternal.h"
45 #include <sys/errno.h>
49 __SCPreferencesCopyDescription(CFTypeRef cf
) {
50 CFAllocatorRef allocator
= CFGetAllocator(cf
);
51 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)cf
;
52 CFMutableStringRef result
;
54 result
= CFStringCreateMutable(allocator
, 0);
55 CFStringAppendFormat(result
, NULL
, CFSTR("<SCPreferences %p [%p]> {"), cf
, allocator
);
56 CFStringAppendFormat(result
, NULL
, CFSTR("name = %@"), prefsPrivate
->name
);
57 CFStringAppendFormat(result
, NULL
, CFSTR(", id = %@"), prefsPrivate
->prefsID
);
58 if (prefsPrivate
->perUser
) {
59 CFStringAppendFormat(result
, NULL
, CFSTR(" (for user %@)"), prefsPrivate
->user
);
61 CFStringAppendFormat(result
, NULL
, CFSTR(", path = %s"),
62 prefsPrivate
->newPath
? prefsPrivate
->newPath
: prefsPrivate
->path
);
63 if (prefsPrivate
->accessed
) {
64 CFStringAppendFormat(result
, NULL
, CFSTR(", accessed"));
66 if (prefsPrivate
->changed
) {
67 CFStringAppendFormat(result
, NULL
, CFSTR(", changed"));
69 if (prefsPrivate
->locked
) {
70 CFStringAppendFormat(result
, NULL
, CFSTR(", locked"));
72 CFStringAppendFormat(result
, NULL
, CFSTR("}"));
79 __SCPreferencesDeallocate(CFTypeRef cf
)
81 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)cf
;
83 /* release resources */
85 pthread_mutex_destroy(&prefsPrivate
->lock
);
87 if (prefsPrivate
->name
) CFRelease(prefsPrivate
->name
);
88 if (prefsPrivate
->prefsID
) CFRelease(prefsPrivate
->prefsID
);
89 if (prefsPrivate
->user
) CFRelease(prefsPrivate
->user
);
90 if (prefsPrivate
->path
) CFAllocatorDeallocate(NULL
, prefsPrivate
->path
);
91 if (prefsPrivate
->newPath
) CFAllocatorDeallocate(NULL
, prefsPrivate
->newPath
);
92 if (prefsPrivate
->signature
) CFRelease(prefsPrivate
->signature
);
93 if (prefsPrivate
->session
) CFRelease(prefsPrivate
->session
);
94 if (prefsPrivate
->sessionKeyLock
) CFRelease(prefsPrivate
->sessionKeyLock
);
95 if (prefsPrivate
->sessionKeyCommit
) CFRelease(prefsPrivate
->sessionKeyCommit
);
96 if (prefsPrivate
->sessionKeyApply
) CFRelease(prefsPrivate
->sessionKeyApply
);
97 if (prefsPrivate
->rlsContext
.release
!= NULL
) {
98 (*prefsPrivate
->rlsContext
.release
)(prefsPrivate
->rlsContext
.info
);
100 if (prefsPrivate
->prefs
) CFRelease(prefsPrivate
->prefs
);
106 static CFTypeID __kSCPreferencesTypeID
= _kCFRuntimeNotATypeID
;
109 static const CFRuntimeClass __SCPreferencesClass
= {
111 "SCPreferences", // className
114 __SCPreferencesDeallocate
, // dealloc
117 NULL
, // copyFormattingDesc
118 __SCPreferencesCopyDescription
// copyDebugDesc
122 static pthread_once_t initialized
= PTHREAD_ONCE_INIT
;
125 __SCPreferencesInitialize(void) {
126 __kSCPreferencesTypeID
= _CFRuntimeRegisterClass(&__SCPreferencesClass
);
131 static SCPreferencesPrivateRef
132 __SCPreferencesCreatePrivate(CFAllocatorRef allocator
)
134 SCPreferencesPrivateRef prefsPrivate
;
137 /* initialize runtime */
138 pthread_once(&initialized
, __SCPreferencesInitialize
);
140 /* allocate prefs session */
141 size
= sizeof(SCPreferencesPrivate
) - sizeof(CFRuntimeBase
);
142 prefsPrivate
= (SCPreferencesPrivateRef
)_CFRuntimeCreateInstance(allocator
,
143 __kSCPreferencesTypeID
,
146 if (prefsPrivate
== NULL
) {
150 pthread_mutex_init(&prefsPrivate
->lock
, NULL
);
152 prefsPrivate
->name
= NULL
;
153 prefsPrivate
->prefsID
= NULL
;
154 prefsPrivate
->perUser
= FALSE
;
155 prefsPrivate
->user
= NULL
;
156 prefsPrivate
->path
= NULL
;
157 prefsPrivate
->newPath
= NULL
; // new prefs path
158 prefsPrivate
->signature
= NULL
;
159 prefsPrivate
->session
= NULL
;
160 prefsPrivate
->sessionKeyLock
= NULL
;
161 prefsPrivate
->sessionKeyCommit
= NULL
;
162 prefsPrivate
->sessionKeyApply
= NULL
;
163 prefsPrivate
->rls
= NULL
;
164 prefsPrivate
->rlsFunction
= NULL
;
165 prefsPrivate
->rlsContext
.info
= NULL
;
166 prefsPrivate
->rlsContext
.retain
= NULL
;
167 prefsPrivate
->rlsContext
.release
= NULL
;
168 prefsPrivate
->rlsContext
.copyDescription
= NULL
;
169 prefsPrivate
->rlList
= NULL
;
170 prefsPrivate
->prefs
= NULL
;
171 prefsPrivate
->accessed
= FALSE
;
172 prefsPrivate
->changed
= FALSE
;
173 prefsPrivate
->locked
= FALSE
;
174 prefsPrivate
->isRoot
= (geteuid() == 0);
180 __private_extern__ SCPreferencesRef
181 __SCPreferencesCreate(CFAllocatorRef allocator
,
188 SCPreferencesPrivateRef prefsPrivate
;
189 int sc_status
= kSCStatusOK
;
192 * allocate and initialize a new prefs session
194 prefsPrivate
= __SCPreferencesCreatePrivate(allocator
);
195 if (prefsPrivate
== NULL
) {
202 * convert prefsID to path
204 prefsPrivate
->path
= __SCPreferencesPath(allocator
,
208 (prefsPrivate
->newPath
== NULL
));
209 if (prefsPrivate
->path
== NULL
) {
210 sc_status
= kSCStatusFailed
;
217 fd
= open(prefsPrivate
->path
, O_RDONLY
, 0644);
225 ((prefsID
== NULL
) || !CFStringHasPrefix(prefsID
, CFSTR("/")))) {
226 /* if default preference ID or relative path */
227 if (prefsPrivate
->newPath
== NULL
) {
229 * we've looked in the "new" prefs directory
230 * without success. Save the "new" path and
231 * look in the "old" prefs directory.
233 prefsPrivate
->newPath
= prefsPrivate
->path
;
237 * we've looked in both the "new" and "old"
238 * prefs directories without success. USe
241 CFAllocatorDeallocate(NULL
, prefsPrivate
->path
);
242 prefsPrivate
->path
= prefsPrivate
->newPath
;
243 prefsPrivate
->newPath
= NULL
;
247 /* no preference data, start fresh */
250 sc_status
= kSCStatusAccessError
;
253 sc_status
= kSCStatusFailed
;
256 SCLog(_sc_verbose
, LOG_DEBUG
, CFSTR("__SCPreferencesCreate open() failed: %s"), strerror(errno
));
265 prefsPrivate
->name
= CFStringCreateCopy(allocator
, name
);
266 if (prefsID
!= NULL
) prefsPrivate
->prefsID
= CFStringCreateCopy(allocator
, prefsID
);
267 prefsPrivate
->perUser
= perUser
;
268 if (user
!= NULL
) prefsPrivate
->user
= CFStringCreateCopy(allocator
, user
);
269 return (SCPreferencesRef
)prefsPrivate
;
273 if (fd
!= -1) (void) close(fd
);
274 CFRelease(prefsPrivate
);
275 _SCErrorSet(sc_status
);
280 __private_extern__ Boolean
281 __SCPreferencesAccess(SCPreferencesRef prefs
)
283 CFAllocatorRef allocator
= CFGetAllocator(prefs
);
285 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
286 int sc_status
= kSCStatusOK
;
289 if (prefsPrivate
->accessed
) {
290 // if preference data has already been accessed
294 fd
= open(prefsPrivate
->path
, O_RDONLY
, 0644);
297 if (fstat(fd
, &statBuf
) == -1) {
298 SCLog(_sc_verbose
, LOG_DEBUG
, CFSTR("__SCPreferencesAccess fstat() failed: %s"), strerror(errno
));
299 sc_status
= kSCStatusFailed
;
305 /* no preference data, start fresh */
306 bzero(&statBuf
, sizeof(statBuf
));
309 sc_status
= kSCStatusAccessError
;
312 sc_status
= kSCStatusFailed
;
315 SCLog(_sc_verbose
, LOG_DEBUG
, CFSTR("__SCPreferencesAccess open() failed: %s"), strerror(errno
));
321 if (prefsPrivate
->signature
!= NULL
) CFRelease(prefsPrivate
->signature
);
322 prefsPrivate
->signature
= __SCPSignatureFromStatbuf(&statBuf
);
324 if (statBuf
.st_size
> 0) {
325 CFDictionaryRef dict
;
326 CFMutableDataRef xmlData
;
327 CFStringRef xmlError
;
330 * extract property list
332 xmlData
= CFDataCreateMutable(allocator
, statBuf
.st_size
);
333 CFDataSetLength(xmlData
, statBuf
.st_size
);
334 if (read(fd
, (void *)CFDataGetBytePtr(xmlData
), statBuf
.st_size
) != statBuf
.st_size
) {
335 /* corrupt prefs file, start fresh */
336 SCLog(_sc_verbose
, LOG_DEBUG
, CFSTR("__SCPreferencesAccess read(): could not load preference data."));
345 dict
= CFPropertyListCreateFromXMLData(allocator
,
347 kCFPropertyListImmutable
,
351 /* corrupt prefs file, start fresh */
352 if (xmlError
!= NULL
) {
354 CFSTR("__SCPreferencesAccess CFPropertyListCreateFromXMLData(): %@"),
362 * make sure that we've got a dictionary
364 if (!isA_CFDictionary(dict
)) {
365 /* corrupt prefs file, start fresh */
366 SCLog(_sc_verbose
, LOG_DEBUG
, CFSTR("__SCPreferencesAccess CFGetTypeID(): not a dictionary."));
371 prefsPrivate
->prefs
= CFDictionaryCreateMutableCopy(allocator
, 0, dict
);
382 if (prefsPrivate
->prefs
== NULL
) {
384 * new file, create empty preferences
386 SCLog(_sc_verbose
, LOG_DEBUG
, CFSTR("__SCPreferencesAccess(): creating new dictionary."));
387 prefsPrivate
->prefs
= CFDictionaryCreateMutable(allocator
,
389 &kCFTypeDictionaryKeyCallBacks
,
390 &kCFTypeDictionaryValueCallBacks
);
391 prefsPrivate
->changed
= TRUE
;
394 prefsPrivate
->accessed
= TRUE
;
399 if (fd
!= -1) (void) close(fd
);
400 _SCErrorSet(sc_status
);
407 SCPreferencesCreate(CFAllocatorRef allocator
,
411 return __SCPreferencesCreate(allocator
, name
, prefsID
, FALSE
, NULL
);
416 SCUserPreferencesCreate(CFAllocatorRef allocator
,
421 return __SCPreferencesCreate(allocator
, name
, prefsID
, TRUE
, user
);
426 SCPreferencesGetTypeID(void) {
427 pthread_once(&initialized
, __SCPreferencesInitialize
); /* initialize runtime */
428 return __kSCPreferencesTypeID
;
433 prefsNotify(SCDynamicStoreRef store
, CFArrayRef changedKeys
, void *info
)
436 void (*context_release
)(const void *);
439 SCPreferencesNotification notify
= 0;
440 SCPreferencesRef prefs
= (SCPreferencesRef
)info
;
441 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
442 SCPreferencesCallBack rlsFunction
;
444 n
= (changedKeys
!= NULL
) ? CFArrayGetCount(changedKeys
) : 0;
445 for (i
= 0; i
< n
; i
++) {
448 key
= CFArrayGetValueAtIndex(changedKeys
, i
);
449 if (CFEqual(key
, prefsPrivate
->sessionKeyCommit
)) {
450 // if preferences have been saved
451 notify
|= kSCPreferencesNotificationCommit
;
453 if (CFEqual(key
, prefsPrivate
->sessionKeyApply
)) {
454 // if stored preferences should be applied to current configuration
455 notify
|= kSCPreferencesNotificationApply
;
464 pthread_mutex_lock(&prefsPrivate
->lock
);
467 rlsFunction
= prefsPrivate
->rlsFunction
;
468 if (prefsPrivate
->rlsContext
.retain
!= NULL
) {
469 context_info
= (void *)prefsPrivate
->rlsContext
.retain(prefsPrivate
->rlsContext
.info
);
470 context_release
= prefsPrivate
->rlsContext
.release
;
472 context_info
= prefsPrivate
->rlsContext
.info
;
473 context_release
= NULL
;
476 pthread_mutex_unlock(&prefsPrivate
->lock
);
478 if (rlsFunction
!= NULL
) {
479 (*rlsFunction
)(prefs
, notify
, context_info
);
482 if (context_release
!= NULL
) {
483 (*context_release
)(context_info
);
490 __private_extern__ Boolean
491 __SCPreferencesAddSession(SCPreferencesRef prefs
)
493 CFAllocatorRef allocator
= CFGetAllocator(prefs
);
494 SCDynamicStoreContext context
= { 0
500 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
502 /* establish a dynamic store session */
503 prefsPrivate
->session
= SCDynamicStoreCreate(allocator
,
504 CFSTR("SCPreferences"),
507 if (prefsPrivate
->session
== NULL
) {
508 SCLog(_sc_verbose
, LOG_INFO
, CFSTR("__SCPreferencesAddSession SCDynamicStoreCreate() failed"));
512 /* create the session "commit" key */
513 prefsPrivate
->sessionKeyCommit
= _SCPNotificationKey(NULL
,
514 prefsPrivate
->prefsID
,
515 prefsPrivate
->perUser
,
517 kSCPreferencesKeyCommit
);
519 /* create the session "apply" key */
520 prefsPrivate
->sessionKeyApply
= _SCPNotificationKey(NULL
,
521 prefsPrivate
->prefsID
,
522 prefsPrivate
->perUser
,
524 kSCPreferencesKeyApply
);
531 SCPreferencesSetCallback(SCPreferencesRef prefs
,
532 SCPreferencesCallBack callout
,
533 SCPreferencesContext
*context
)
535 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
538 /* sorry, you must provide a session */
539 _SCErrorSet(kSCStatusNoPrefsSession
);
543 pthread_mutex_lock(&prefsPrivate
->lock
);
545 if (prefsPrivate
->rlsContext
.release
!= NULL
) {
546 /* let go of the current context */
547 (*prefsPrivate
->rlsContext
.release
)(prefsPrivate
->rlsContext
.info
);
550 prefsPrivate
->rlsFunction
= callout
;
551 prefsPrivate
->rlsContext
.info
= NULL
;
552 prefsPrivate
->rlsContext
.retain
= NULL
;
553 prefsPrivate
->rlsContext
.release
= NULL
;
554 prefsPrivate
->rlsContext
.copyDescription
= NULL
;
555 if (context
!= NULL
) {
556 bcopy(context
, &prefsPrivate
->rlsContext
, sizeof(SCPreferencesContext
));
557 if (context
->retain
!= NULL
) {
558 prefsPrivate
->rlsContext
.info
= (void *)(*context
->retain
)(context
->info
);
562 pthread_mutex_unlock(&prefsPrivate
->lock
);
569 SCPreferencesScheduleWithRunLoop(SCPreferencesRef prefs
,
570 CFRunLoopRef runLoop
,
571 CFStringRef runLoopMode
)
573 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
576 /* sorry, you must provide a session */
577 _SCErrorSet(kSCStatusNoPrefsSession
);
581 pthread_mutex_lock(&prefsPrivate
->lock
);
583 if (prefsPrivate
->rls
== NULL
) {
584 CFMutableArrayRef keys
;
586 if (prefsPrivate
->session
== NULL
) {
587 __SCPreferencesAddSession(prefs
);
590 CFRetain(prefs
); // hold a reference to the prefs
592 keys
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
593 CFArrayAppendValue(keys
, prefsPrivate
->sessionKeyCommit
);
594 CFArrayAppendValue(keys
, prefsPrivate
->sessionKeyApply
);
595 (void)SCDynamicStoreSetNotificationKeys(prefsPrivate
->session
, keys
, NULL
);
598 prefsPrivate
->rls
= SCDynamicStoreCreateRunLoopSource(NULL
, prefsPrivate
->session
, 0);
599 prefsPrivate
->rlList
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
602 if (!_SC_isScheduled(NULL
, runLoop
, runLoopMode
, prefsPrivate
->rlList
)) {
604 * if we do not already have notifications scheduled with
605 * this runLoop / runLoopMode
607 CFRunLoopAddSource(runLoop
, prefsPrivate
->rls
, runLoopMode
);
610 _SC_schedule(prefs
, runLoop
, runLoopMode
, prefsPrivate
->rlList
);
612 pthread_mutex_unlock(&prefsPrivate
->lock
);
618 SCPreferencesUnscheduleFromRunLoop(SCPreferencesRef prefs
,
619 CFRunLoopRef runLoop
,
620 CFStringRef runLoopMode
)
622 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
627 /* sorry, you must provide a session */
628 _SCErrorSet(kSCStatusNoPrefsSession
);
632 pthread_mutex_lock(&prefsPrivate
->lock
);
634 if (prefsPrivate
->rls
== NULL
) {
635 /* if not currently scheduled */
639 if (!_SC_unschedule(NULL
, runLoop
, runLoopMode
, prefsPrivate
->rlList
, FALSE
)) {
640 /* if not currently scheduled */
644 n
= CFArrayGetCount(prefsPrivate
->rlList
);
645 if (n
== 0 || !_SC_isScheduled(NULL
, runLoop
, runLoopMode
, prefsPrivate
->rlList
)) {
647 * if we are no longer scheduled to receive notifications for
648 * this runLoop / runLoopMode
650 CFRunLoopRemoveSource(runLoop
, prefsPrivate
->rls
, runLoopMode
);
653 CFArrayRef changedKeys
;
656 * if *all* notifications have been unscheduled
658 CFRunLoopSourceInvalidate(prefsPrivate
->rls
);
659 CFRelease(prefsPrivate
->rls
);
660 prefsPrivate
->rls
= NULL
;
661 CFRelease(prefsPrivate
->rlList
);
662 prefsPrivate
->rlList
= NULL
;
664 CFRelease(prefs
); // release our reference to the prefs
666 // no need to track changes
667 (void)SCDynamicStoreSetNotificationKeys(prefsPrivate
->session
, NULL
, NULL
);
669 // clear out any pending notifications
670 changedKeys
= SCDynamicStoreCopyNotifiedKeys(prefsPrivate
->session
);
671 if (changedKeys
!= NULL
) {
672 CFRelease(changedKeys
);
681 pthread_mutex_unlock(&prefsPrivate
->lock
);
687 SCPreferencesSynchronize(SCPreferencesRef prefs
)
689 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
692 /* sorry, you must provide a session */
693 _SCErrorSet(kSCStatusNoPrefsSession
);
697 if (prefsPrivate
->prefs
!= NULL
) {
698 CFRelease(prefsPrivate
->prefs
);
699 prefsPrivate
->prefs
= NULL
;
701 if (prefsPrivate
->signature
!= NULL
) {
702 CFRelease(prefsPrivate
->signature
);
703 prefsPrivate
->signature
= NULL
;
705 prefsPrivate
->accessed
= FALSE
;
706 prefsPrivate
->changed
= FALSE
;