]>
Commit | Line | Data |
---|---|---|
d8925383 | 1 | /* |
bd5b749c | 2 | * Copyright (c) 2008 Apple Inc. All rights reserved. |
d8925383 A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
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 | |
11 | * file. | |
12 | * | |
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. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | /* CFXMLPreferencesDomain.c | |
24 | Copyright 1998-2002, Apple, Inc. All rights reserved. | |
25 | Responsibility: Chris Parker | |
26 | */ | |
27 | ||
d8925383 A |
28 | #include <CoreFoundation/CFPreferences.h> |
29 | #include <CoreFoundation/CFURLAccess.h> | |
30 | #include <CoreFoundation/CFPropertyList.h> | |
31 | #include <CoreFoundation/CFNumber.h> | |
32 | #include <CoreFoundation/CFDate.h> | |
33 | #include "CFInternal.h" | |
34 | #include <time.h> | |
bd5b749c | 35 | #if DEPLOYMENT_TARGET_MACOSX |
d8925383 A |
36 | #include <unistd.h> |
37 | #include <stdio.h> | |
38 | #include <sys/stat.h> | |
39 | #include <mach/mach.h> | |
40 | #include <mach/mach_syscalls.h> | |
bd5b749c | 41 | #endif |
d8925383 A |
42 | |
43 | Boolean __CFPreferencesShouldWriteXML(void); | |
44 | ||
45 | typedef struct { | |
46 | CFMutableDictionaryRef _domainDict; // Current value of the domain dictionary | |
47 | CFMutableArrayRef _dirtyKeys; // The array of keys which must be synchronized | |
48 | CFAbsoluteTime _lastReadTime; // The last time we synchronized with the disk | |
49 | CFSpinLock_t _lock; // Lock for accessing fields in the domain | |
50 | Boolean _isWorldReadable; // HACK - this is because we have no good way to propogate the kCFPreferencesAnyUser information from the upper level CFPreferences routines REW, 1/13/00 | |
51 | char _padding[3]; | |
52 | } _CFXMLPreferencesDomain; | |
53 | ||
54 | static void *createXMLDomain(CFAllocatorRef allocator, CFTypeRef context); | |
55 | static void freeXMLDomain(CFAllocatorRef allocator, CFTypeRef context, void *tDomain); | |
56 | static CFTypeRef fetchXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key); | |
57 | static void writeXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key, CFTypeRef value); | |
58 | static Boolean synchronizeXMLDomain(CFTypeRef context, void *xmlDomain); | |
59 | static void getXMLKeysAndValues(CFAllocatorRef alloc, CFTypeRef context, void *xmlDomain, void **buf[], CFIndex *numKeyValuePairs); | |
60 | static CFDictionaryRef copyXMLDomainDictionary(CFTypeRef context, void *domain); | |
61 | static void setXMLDomainIsWorldReadable(CFTypeRef context, void *domain, Boolean isWorldReadable); | |
62 | ||
63 | __private_extern__ const _CFPreferencesDomainCallBacks __kCFXMLPropertyListDomainCallBacks = {createXMLDomain, freeXMLDomain, fetchXMLValue, writeXMLValue, synchronizeXMLDomain, getXMLKeysAndValues, copyXMLDomainDictionary, setXMLDomainIsWorldReadable}; | |
64 | ||
65 | // Directly ripped from Foundation.... | |
66 | static void __CFMilliSleep(uint32_t msecs) { | |
bd5b749c | 67 | #if defined(__svr4__) || defined(__hpux__) |
d8925383 | 68 | sleep((msecs + 900) / 1000); |
bd5b749c | 69 | #elif DEPLOYMENT_TARGET_MACOSX |
d8925383 A |
70 | struct timespec input; |
71 | input.tv_sec = msecs / 1000; | |
72 | input.tv_nsec = (msecs - input.tv_sec * 1000) * 1000000; | |
73 | nanosleep(&input, NULL); | |
74 | #else | |
75 | #error Dont know how to define sleep for this platform | |
76 | #endif | |
77 | } | |
78 | ||
bd5b749c | 79 | static CFSpinLock_t _propDictLock = CFSpinLockInit; // Annoying that we need this, but otherwise we have a multithreading risk |
d8925383 A |
80 | |
81 | CF_INLINE CFDictionaryRef URLPropertyDictForPOSIXMode(SInt32 mode) { | |
82 | static CFMutableDictionaryRef _propertyDict = NULL; | |
83 | CFNumberRef num = CFNumberCreate(__CFPreferencesAllocator(), kCFNumberSInt32Type, &mode); | |
84 | __CFSpinLock(&_propDictLock); | |
85 | if (!_propertyDict) { | |
86 | _propertyDict = CFDictionaryCreateMutable(__CFPreferencesAllocator(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); | |
87 | } | |
88 | CFDictionarySetValue(_propertyDict, kCFURLFilePOSIXMode, num); | |
89 | CFRelease(num); | |
90 | return _propertyDict; | |
91 | } | |
92 | ||
93 | CF_INLINE void URLPropertyDictRelease(void) { | |
94 | __CFSpinUnlock(&_propDictLock); | |
95 | } | |
96 | ||
97 | // Asssumes caller already knows the directory doesn't exist. | |
98 | static Boolean _createDirectory(CFURLRef dirURL, Boolean worldReadable) { | |
99 | CFAllocatorRef alloc = __CFPreferencesAllocator(); | |
100 | CFURLRef parentURL = CFURLCreateCopyDeletingLastPathComponent(alloc, dirURL); | |
bd5b749c | 101 | CFBooleanRef val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, parentURL, kCFURLFileExists, NULL); |
d8925383 A |
102 | Boolean parentExists = (val && CFBooleanGetValue(val)); |
103 | SInt32 mode; | |
104 | Boolean result; | |
105 | if (val) CFRelease(val); | |
106 | if (!parentExists) { | |
107 | CFStringRef path = CFURLCopyPath(parentURL); | |
108 | if (!CFEqual(path, CFSTR("/"))) { | |
109 | _createDirectory(parentURL, worldReadable); | |
bd5b749c | 110 | val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, parentURL, kCFURLFileExists, NULL); |
d8925383 A |
111 | parentExists = (val && CFBooleanGetValue(val)); |
112 | if (val) CFRelease(val); | |
113 | } | |
114 | CFRelease(path); | |
115 | } | |
116 | if (parentURL) CFRelease(parentURL); | |
117 | if (!parentExists) return false; | |
118 | ||
bd5b749c | 119 | #if DEPLOYMENT_TARGET_MACOSX |
d8925383 | 120 | mode = worldReadable ? S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH : S_IRWXU; |
bd5b749c A |
121 | #else |
122 | mode = 0666; | |
123 | #endif | |
d8925383 A |
124 | |
125 | result = CFURLWriteDataAndPropertiesToResource(dirURL, (CFDataRef)dirURL, URLPropertyDictForPOSIXMode(mode), NULL); | |
126 | URLPropertyDictRelease(); | |
127 | return result; | |
128 | } | |
129 | ||
130 | ||
131 | /* XML - context is the CFURL where the property list is stored on disk; domain is an _CFXMLPreferencesDomain */ | |
132 | static void *createXMLDomain(CFAllocatorRef allocator, CFTypeRef context) { | |
bd5b749c | 133 | _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain*) CFAllocatorAllocate(allocator, sizeof(_CFXMLPreferencesDomain), 0); |
d8925383 A |
134 | domain->_lastReadTime = 0.0; |
135 | domain->_domainDict = NULL; | |
136 | domain->_dirtyKeys = CFArrayCreateMutable(allocator, 0, & kCFTypeArrayCallBacks); | |
bd5b749c A |
137 | const CFSpinLock_t lock = CFSpinLockInit; |
138 | domain->_lock = lock; | |
d8925383 A |
139 | domain->_isWorldReadable = false; |
140 | return domain; | |
141 | } | |
142 | ||
143 | static void freeXMLDomain(CFAllocatorRef allocator, CFTypeRef context, void *tDomain) { | |
144 | _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)tDomain; | |
145 | if (domain->_domainDict) CFRelease(domain->_domainDict); | |
146 | if (domain->_dirtyKeys) CFRelease(domain->_dirtyKeys); | |
147 | CFAllocatorDeallocate(allocator, domain); | |
148 | } | |
149 | ||
150 | // Assumes the domain has already been locked | |
151 | static void _loadXMLDomainIfStale(CFURLRef url, _CFXMLPreferencesDomain *domain) { | |
152 | CFAllocatorRef alloc = __CFPreferencesAllocator(); | |
153 | int idx; | |
154 | if (domain->_domainDict) { | |
155 | CFDateRef modDate; | |
156 | CFAbsoluteTime modTime; | |
157 | CFURLRef testURL = url; | |
158 | ||
159 | if (CFDictionaryGetCount(domain->_domainDict) == 0) { | |
160 | // domain never existed; check the parent directory, not the child | |
161 | testURL = CFURLCreateWithFileSystemPathRelativeToBase(alloc, CFSTR(".."), kCFURLPOSIXPathStyle, true, url); | |
162 | } | |
163 | ||
164 | modDate = (CFDateRef )CFURLCreatePropertyFromResource(alloc, testURL, kCFURLFileLastModificationTime, NULL); | |
165 | modTime = modDate ? CFDateGetAbsoluteTime(modDate) : 0.0; | |
166 | ||
167 | // free before possible return. we can test non-NULL of modDate but don't depend on contents after this. | |
168 | if (testURL != url) CFRelease(testURL); | |
169 | if (modDate) CFRelease(modDate); | |
170 | ||
171 | if (modDate != NULL && modTime < domain->_lastReadTime) { // We're up-to-date | |
172 | return; | |
173 | } | |
174 | } | |
175 | ||
176 | ||
177 | // We're out-of-date; destroy domainDict and reload | |
178 | if (domain->_domainDict) { | |
179 | CFRelease(domain->_domainDict); | |
180 | domain->_domainDict = NULL; | |
181 | } | |
182 | ||
183 | // We no longer lock on read; instead, we assume parse failures are because someone else is writing the file, and just try to parse again. If we fail 3 times in a row, we assume the file is corrupted. REW, 7/13/99 | |
184 | ||
185 | for (idx = 0; idx < 3; idx ++) { | |
186 | CFDataRef data; | |
187 | if (!CFURLCreateDataAndPropertiesFromResource(alloc, url, &data, NULL, NULL, NULL) || !data) { | |
188 | // Either a file system error (so we can't read the file), or an empty (or perhaps non-existant) file | |
189 | domain->_domainDict = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); | |
190 | break; | |
191 | } else { | |
192 | CFTypeRef pList = CFPropertyListCreateFromXMLData(alloc, data, kCFPropertyListImmutable, NULL); | |
193 | CFRelease(data); | |
194 | if (pList && CFGetTypeID(pList) == CFDictionaryGetTypeID()) { | |
195 | domain->_domainDict = CFDictionaryCreateMutableCopy(alloc, 0, (CFDictionaryRef)pList); | |
196 | CFRelease(pList); | |
197 | break; | |
198 | } else if (pList) { | |
199 | CFRelease(pList); | |
200 | } | |
201 | // Assume the file is being written; sleep for a short time (to allow the write to complete) then re-read | |
202 | __CFMilliSleep(150); | |
203 | } | |
204 | } | |
205 | if (!domain->_domainDict) { | |
206 | // Failed to ever load | |
207 | domain->_domainDict = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); | |
208 | } | |
209 | domain->_lastReadTime = CFAbsoluteTimeGetCurrent(); | |
210 | } | |
211 | ||
212 | static CFTypeRef fetchXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key) { | |
213 | _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; | |
214 | CFTypeRef result; | |
215 | ||
216 | // Never reload if we've looked at the file system within the last 5 seconds. | |
217 | __CFSpinLock(&domain->_lock); | |
218 | if (domain->_domainDict == NULL) _loadXMLDomainIfStale((CFURLRef )context, domain); | |
219 | result = CFDictionaryGetValue(domain->_domainDict, key); | |
220 | if (result) CFRetain(result); | |
221 | __CFSpinUnlock(&domain->_lock); | |
222 | ||
223 | return result; | |
224 | } | |
225 | ||
226 | ||
bd5b749c | 227 | #if DEPLOYMENT_TARGET_MACOSX |
d8925383 | 228 | #include <sys/fcntl.h> |
d8925383 A |
229 | |
230 | /* __CFWriteBytesToFileWithAtomicity is a "safe save" facility. Write the bytes using the specified mode on the file to the provided URL. If the atomic flag is true, try to do it in a fashion that will enable a safe save. | |
231 | */ | |
232 | static Boolean __CFWriteBytesToFileWithAtomicity(CFURLRef url, const void *bytes, int length, SInt32 mode, Boolean atomic) { | |
233 | int fd = -1; | |
234 | char auxPath[CFMaxPathSize + 16]; | |
235 | char cpath[CFMaxPathSize]; | |
4c91a73d A |
236 | uid_t owner = getuid(); |
237 | gid_t group = getgid(); | |
238 | Boolean writingFileAsRoot = ((getuid() != geteuid()) && (geteuid() == 0)); | |
bd5b749c A |
239 | |
240 | if (!CFURLGetFileSystemRepresentation(url, true, (uint8_t *)cpath, CFMaxPathSize)) { | |
d8925383 A |
241 | return false; |
242 | } | |
4c91a73d A |
243 | |
244 | if (-1 == mode || writingFileAsRoot) { | |
bd5b749c | 245 | struct stat statBuf; |
4c91a73d A |
246 | if (0 == stat(cpath, &statBuf)) { |
247 | mode = statBuf.st_mode; | |
248 | owner = statBuf.st_uid; | |
249 | group = statBuf.st_gid; | |
250 | } else { | |
251 | mode = 0664; | |
252 | if (writingFileAsRoot && (0 == strncmp(cpath, "/Library/Preferences", 20))) { | |
253 | owner = geteuid(); | |
254 | group = 80; | |
255 | } | |
256 | } | |
d8925383 | 257 | } |
4c91a73d | 258 | |
d8925383 | 259 | if (atomic) { |
bd5b749c A |
260 | CFURLRef dir = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorSystemDefault, url); |
261 | CFURLRef tempFile = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, dir, CFSTR("cf#XXXXX"), false); | |
d8925383 | 262 | CFRelease(dir); |
bd5b749c | 263 | if (!CFURLGetFileSystemRepresentation(tempFile, true, (uint8_t *)auxPath, CFMaxPathSize)) { |
d8925383 A |
264 | CFRelease(tempFile); |
265 | return false; | |
266 | } | |
267 | CFRelease(tempFile); | |
268 | fd = mkstemp(auxPath); | |
269 | } else { | |
270 | fd = open(cpath, O_WRONLY|O_CREAT|O_TRUNC, mode); | |
271 | } | |
4c91a73d | 272 | |
d8925383 | 273 | if (fd < 0) return false; |
4c91a73d | 274 | |
d8925383 A |
275 | if (length && (write(fd, bytes, length) != length || fsync(fd) < 0)) { |
276 | int saveerr = thread_errno(); | |
277 | close(fd); | |
278 | if (atomic) | |
279 | unlink(auxPath); | |
280 | thread_set_errno(saveerr); | |
281 | return false; | |
282 | } | |
4c91a73d | 283 | |
d8925383 | 284 | close(fd); |
4c91a73d | 285 | |
d8925383 A |
286 | if (atomic) { |
287 | // preserve the mode as passed in originally | |
288 | chmod(auxPath, mode); | |
bd5b749c | 289 | |
d8925383 A |
290 | if (0 != rename(auxPath, cpath)) { |
291 | unlink(auxPath); | |
292 | return false; | |
293 | } | |
4c91a73d A |
294 | |
295 | // If the file was renamed successfully and we wrote it as root we need to reset the owner & group as they were. | |
296 | if (writingFileAsRoot) { | |
297 | chown(cpath, owner, group); | |
298 | } | |
d8925383 A |
299 | } |
300 | return true; | |
301 | } | |
302 | #endif | |
303 | ||
304 | // domain should already be locked. | |
305 | static Boolean _writeXMLFile(CFURLRef url, CFMutableDictionaryRef dict, Boolean isWorldReadable, Boolean *tryAgain) { | |
306 | Boolean success = false; | |
307 | CFAllocatorRef alloc = __CFPreferencesAllocator(); | |
308 | *tryAgain = false; | |
309 | if (CFDictionaryGetCount(dict) == 0) { | |
310 | // Destroy the file | |
bd5b749c | 311 | CFBooleanRef val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, url, kCFURLFileExists, NULL); |
d8925383 A |
312 | if (val && CFBooleanGetValue(val)) { |
313 | success = CFURLDestroyResource(url, NULL); | |
314 | } else { | |
315 | success = true; | |
316 | } | |
317 | if (val) CFRelease(val); | |
318 | } else { | |
319 | CFPropertyListFormat desiredFormat = __CFPreferencesShouldWriteXML() ? kCFPropertyListXMLFormat_v1_0 : kCFPropertyListBinaryFormat_v1_0; | |
320 | CFWriteStreamRef binStream = CFWriteStreamCreateWithAllocatedBuffers(alloc, alloc); | |
321 | CFWriteStreamOpen(binStream); | |
322 | CFPropertyListWriteToStream(dict, binStream, desiredFormat, NULL); | |
323 | CFWriteStreamClose(binStream); | |
bd5b749c | 324 | CFDataRef data = (CFDataRef) CFWriteStreamCopyProperty(binStream, kCFStreamPropertyDataWritten); |
d8925383 A |
325 | CFRelease(binStream); |
326 | if (data) { | |
327 | SInt32 mode; | |
bd5b749c | 328 | #if DEPLOYMENT_TARGET_MACOSX |
d8925383 | 329 | mode = isWorldReadable ? S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH : S_IRUSR|S_IWUSR; |
bd5b749c A |
330 | #else |
331 | mode = 0666; | |
332 | #endif | |
333 | #if DEPLOYMENT_TARGET_MACOSX | |
334 | { // Try quick atomic way first, then fallback to slower ways and error cases | |
335 | CFStringRef scheme = CFURLCopyScheme(url); | |
336 | if (!scheme) { | |
337 | *tryAgain = false; | |
338 | CFRelease(data); | |
339 | return false; | |
340 | } else if (CFStringCompare(scheme, CFSTR("file"), 0) == kCFCompareEqualTo) { | |
341 | SInt32 length = CFDataGetLength(data); | |
342 | const void *bytes = (0 == length) ? (const void *)"" : CFDataGetBytePtr(data); | |
343 | Boolean atomicWriteSuccess = __CFWriteBytesToFileWithAtomicity(url, bytes, length, mode, true); | |
344 | if (atomicWriteSuccess) { | |
345 | CFRelease(scheme); | |
346 | *tryAgain = false; | |
347 | CFRelease(data); | |
348 | return true; | |
349 | } | |
350 | if (!atomicWriteSuccess && thread_errno() == ENOSPC) { | |
351 | CFRelease(scheme); | |
352 | *tryAgain = false; | |
353 | CFRelease(data); | |
354 | return false; | |
355 | } | |
356 | } | |
357 | CFRelease(scheme); | |
358 | } | |
d8925383 A |
359 | #endif |
360 | success = CFURLWriteDataAndPropertiesToResource(url, data, URLPropertyDictForPOSIXMode(mode), NULL); | |
361 | URLPropertyDictRelease(); | |
362 | if (success) { | |
363 | CFDataRef readData; | |
364 | if (!CFURLCreateDataAndPropertiesFromResource(alloc, url, &readData, NULL, NULL, NULL) || !CFEqual(readData, data)) { | |
365 | success = false; | |
366 | *tryAgain = true; | |
367 | } | |
368 | if (readData) CFRelease(readData); | |
369 | } else { | |
bd5b749c | 370 | CFBooleanRef val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, url, kCFURLFileExists, NULL); |
d8925383 A |
371 | if (!val || !CFBooleanGetValue(val)) { |
372 | CFURLRef tmpURL = CFURLCreateWithFileSystemPathRelativeToBase(alloc, CFSTR("."), kCFURLPOSIXPathStyle, true, url); // Just "." because url is not a directory URL | |
373 | CFURLRef parentURL = tmpURL ? CFURLCopyAbsoluteURL(tmpURL) : NULL; | |
374 | if (tmpURL) CFRelease(tmpURL); | |
375 | if (val) CFRelease(val); | |
bd5b749c | 376 | val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, parentURL, kCFURLFileExists, NULL); |
d8925383 A |
377 | if ((!val || !CFBooleanGetValue(val)) && _createDirectory(parentURL, isWorldReadable)) { |
378 | // parent directory didn't exist; now it does; try again to write | |
379 | success = CFURLWriteDataAndPropertiesToResource(url, data, URLPropertyDictForPOSIXMode(mode), NULL); | |
380 | URLPropertyDictRelease(); | |
381 | if (success) { | |
382 | CFDataRef rdData; | |
383 | if (!CFURLCreateDataAndPropertiesFromResource(alloc, url, &rdData, NULL, NULL, NULL) || !CFEqual(rdData, data)) { | |
384 | success = false; | |
385 | *tryAgain = true; | |
386 | } | |
387 | if (rdData) CFRelease(rdData); | |
388 | } | |
389 | ||
390 | } | |
391 | if (parentURL) CFRelease(parentURL); | |
392 | } | |
393 | if (val) CFRelease(val); | |
394 | } | |
395 | CFRelease(data); | |
396 | } else { | |
397 | // ??? This should never happen | |
398 | CFLog(__kCFLogAssertion, CFSTR("Could not generate XML data for property list")); | |
399 | success = false; | |
400 | } | |
401 | } | |
402 | return success; | |
403 | } | |
404 | ||
405 | static void writeXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key, CFTypeRef value) { | |
406 | _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; | |
407 | const void *existing = NULL; | |
408 | ||
409 | __CFSpinLock(&domain->_lock); | |
410 | if (domain->_domainDict == NULL) { | |
411 | _loadXMLDomainIfStale((CFURLRef )context, domain); | |
412 | } | |
413 | ||
414 | // check to see if the value is the same | |
415 | // if (1) the key is present AND value is !NULL and equal to existing, do nothing, or | |
416 | // if (2) the key is not present AND value is NULL, do nothing | |
417 | // these things are no-ops, and should not dirty the domain | |
418 | if (CFDictionaryGetValueIfPresent(domain->_domainDict, key, &existing)) { | |
419 | if (NULL != value && (existing == value || CFEqual(existing, value))) { | |
420 | __CFSpinUnlock(&domain->_lock); | |
421 | return; | |
422 | } | |
423 | } else { | |
424 | if (NULL == value) { | |
425 | __CFSpinUnlock(&domain->_lock); | |
426 | return; | |
427 | } | |
428 | } | |
429 | ||
430 | // We must append first so key gets another retain (in case we're | |
431 | // about to remove it from the dictionary, and that's the sole reference) | |
432 | // This should be a set not an array. | |
433 | if (!CFArrayContainsValue(domain->_dirtyKeys, CFRangeMake(0, CFArrayGetCount(domain->_dirtyKeys)), key)) { | |
434 | CFArrayAppendValue(domain->_dirtyKeys, key); | |
435 | } | |
436 | if (value) { | |
437 | // Must copy for two reasons - we don't want mutable objects in the cache, and we don't want objects allocated from a different allocator in the cache. | |
438 | CFTypeRef newValue = CFPropertyListCreateDeepCopy(__CFPreferencesAllocator(), value, kCFPropertyListImmutable); | |
439 | CFDictionarySetValue(domain->_domainDict, key, newValue); | |
440 | CFRelease(newValue); | |
441 | } else { | |
442 | CFDictionaryRemoveValue(domain->_domainDict, key); | |
443 | } | |
444 | __CFSpinUnlock(&domain->_lock); | |
445 | } | |
446 | ||
447 | static void getXMLKeysAndValues(CFAllocatorRef alloc, CFTypeRef context, void *xmlDomain, void **buf[], CFIndex *numKeyValuePairs) { | |
448 | _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; | |
449 | CFIndex count; | |
450 | __CFSpinLock(&domain->_lock); | |
451 | if (!domain->_domainDict) { | |
452 | _loadXMLDomainIfStale((CFURLRef )context, domain); | |
453 | } | |
454 | count = CFDictionaryGetCount(domain->_domainDict); | |
455 | if (buf) { | |
456 | void **values; | |
457 | if (count <= *numKeyValuePairs) { | |
458 | values = *buf + count; | |
459 | CFDictionaryGetKeysAndValues(domain->_domainDict, (const void **)*buf, (const void **)values); | |
460 | } else if (alloc != kCFAllocatorNull) { | |
bd5b749c | 461 | *buf = (void**) CFAllocatorReallocate(alloc, (*buf ? *buf : NULL), count * 2 * sizeof(void *), 0); |
d8925383 A |
462 | if (*buf) { |
463 | values = *buf + count; | |
464 | CFDictionaryGetKeysAndValues(domain->_domainDict, (const void **)*buf, (const void **)values); | |
465 | } | |
466 | } | |
467 | } | |
468 | *numKeyValuePairs = count; | |
469 | __CFSpinUnlock(&domain->_lock); | |
470 | } | |
471 | ||
472 | static CFDictionaryRef copyXMLDomainDictionary(CFTypeRef context, void *xmlDomain) { | |
473 | _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; | |
474 | CFDictionaryRef result; | |
475 | ||
476 | __CFSpinLock(&domain->_lock); | |
477 | if(!domain->_domainDict) { | |
478 | _loadXMLDomainIfStale((CFURLRef)context, domain); | |
479 | } | |
480 | ||
481 | result = (CFDictionaryRef)CFPropertyListCreateDeepCopy(__CFPreferencesAllocator(), domain->_domainDict, kCFPropertyListImmutable); | |
482 | ||
483 | __CFSpinUnlock(&domain->_lock); | |
484 | return result; | |
485 | } | |
486 | ||
487 | ||
488 | static void setXMLDomainIsWorldReadable(CFTypeRef context, void *domain, Boolean isWorldReadable) { | |
489 | ((_CFXMLPreferencesDomain *)domain)->_isWorldReadable = isWorldReadable; | |
490 | } | |
491 | ||
492 | static Boolean synchronizeXMLDomain(CFTypeRef context, void *xmlDomain) { | |
493 | _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; | |
494 | CFMutableDictionaryRef cachedDict; | |
495 | CFMutableArrayRef changedKeys; | |
496 | SInt32 idx, count; | |
497 | Boolean success, tryAgain; | |
498 | ||
499 | __CFSpinLock(&domain->_lock); | |
500 | cachedDict = domain->_domainDict; | |
501 | changedKeys = domain->_dirtyKeys; | |
502 | count = CFArrayGetCount(changedKeys); | |
503 | ||
504 | if (count == 0) { | |
505 | // no changes were made to this domain; just remove it from the cache to guarantee it will be taken from disk next access | |
506 | if (cachedDict) { | |
507 | CFRelease(cachedDict); | |
508 | domain->_domainDict = NULL; | |
509 | } | |
510 | __CFSpinUnlock(&domain->_lock); | |
511 | return true; | |
512 | } | |
513 | ||
514 | domain->_domainDict = NULL; // This forces a reload. Note that we now have a retain on cachedDict | |
515 | do { | |
516 | _loadXMLDomainIfStale((CFURLRef )context, domain); | |
517 | // now cachedDict holds our changes; domain->_domainDict has the latest version from the disk | |
518 | for (idx = 0; idx < count; idx ++) { | |
bd5b749c | 519 | CFStringRef key = (CFStringRef) CFArrayGetValueAtIndex(changedKeys, idx); |
d8925383 A |
520 | CFTypeRef value = CFDictionaryGetValue(cachedDict, key); |
521 | if (value) | |
522 | CFDictionarySetValue(domain->_domainDict, key, value); | |
523 | else | |
524 | CFDictionaryRemoveValue(domain->_domainDict, key); | |
525 | } | |
526 | success = _writeXMLFile((CFURLRef )context, domain->_domainDict, domain->_isWorldReadable, &tryAgain); | |
527 | if (tryAgain) { | |
bd5b749c | 528 | __CFMilliSleep((((uint32_t)__CFReadTSR() & 0xf) + 1) * 50); |
d8925383 A |
529 | } |
530 | } while (tryAgain); | |
531 | CFRelease(cachedDict); | |
532 | if (success) { | |
533 | CFArrayRemoveAllValues(domain->_dirtyKeys); | |
534 | } | |
535 | domain->_lastReadTime = CFAbsoluteTimeGetCurrent(); | |
536 | __CFSpinUnlock(&domain->_lock); | |
537 | return success; | |
538 | } | |
539 | ||
d8925383 | 540 |