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