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