]> git.saurik.com Git - apple/cf.git/blob - CFFileUtilities.c
CF-855.11.tar.gz
[apple/cf.git] / CFFileUtilities.c
1 /*
2 * Copyright (c) 2013 Apple 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
24 /* CFFileUtilities.c
25 Copyright (c) 1999-2013, Apple Inc. All rights reserved.
26 Responsibility: Tony Parker
27 */
28
29 #include "CFInternal.h"
30 #include <CoreFoundation/CFPriv.h>
31
32 #include <sys/stat.h>
33 #include <errno.h>
34 #include <string.h>
35 #include <stdio.h>
36
37 #if DEPLOYMENT_TARGET_WINDOWS
38 #include <io.h>
39 #include <fcntl.h>
40
41 #define close _close
42 #define write _write
43 #define read _read
44 #define open _NS_open
45 #define stat _NS_stat
46 #define fstat _fstat
47 #define mkdir(a,b) _NS_mkdir(a)
48 #define rmdir _NS_rmdir
49 #define unlink _NS_unlink
50
51 #define statinfo _stat
52
53 #else
54
55 #include <unistd.h>
56 #include <dirent.h>
57 #include <sys/types.h>
58 #include <pwd.h>
59 #include <fcntl.h>
60
61 #define statinfo stat
62
63 #endif
64
65 CF_INLINE int openAutoFSNoWait() {
66 #if DEPLOYMENT_TARGET_WINDOWS
67 return -1;
68 #else
69 return (__CFProphylacticAutofsAccess ? open("/dev/autofs_nowait", 0) : -1);
70 #endif
71 }
72
73 CF_INLINE void closeAutoFSNoWait(int fd) {
74 if (-1 != fd) close(fd);
75 }
76
77 CF_PRIVATE CFStringRef _CFCopyExtensionForAbstractType(CFStringRef abstractType) {
78 return (abstractType ? (CFStringRef)CFRetain(abstractType) : NULL);
79 }
80
81
82 CF_PRIVATE Boolean _CFCreateDirectory(const char *path) {
83 int no_hang_fd = openAutoFSNoWait();
84 int ret = ((mkdir(path, 0777) == 0) ? true : false);
85 closeAutoFSNoWait(no_hang_fd);
86 return ret;
87 }
88
89 CF_PRIVATE Boolean _CFRemoveDirectory(const char *path) {
90 int no_hang_fd = openAutoFSNoWait();
91 int ret = ((rmdir(path) == 0) ? true : false);
92 closeAutoFSNoWait(no_hang_fd);
93 return ret;
94 }
95
96 CF_PRIVATE Boolean _CFDeleteFile(const char *path) {
97 int no_hang_fd = openAutoFSNoWait();
98 int ret = unlink(path) == 0;
99 closeAutoFSNoWait(no_hang_fd);
100 return ret;
101 }
102
103 CF_PRIVATE Boolean _CFReadBytesFromPathAndGetFD(CFAllocatorRef alloc, const char *path, void **bytes, CFIndex *length, CFIndex maxLength, int extraOpenFlags, int *fd) { // maxLength is the number of bytes desired, or 0 if the whole file is desired regardless of length.
104 struct statinfo statBuf;
105
106 *bytes = NULL;
107
108
109 int no_hang_fd = openAutoFSNoWait();
110 *fd = open(path, O_RDONLY|extraOpenFlags|CF_OPENFLGS, 0666);
111
112 if (*fd < 0) {
113 closeAutoFSNoWait(no_hang_fd);
114 return false;
115 }
116 if (fstat(*fd, &statBuf) < 0) {
117 int saveerr = thread_errno();
118 close(*fd);
119 *fd = -1;
120 closeAutoFSNoWait(no_hang_fd);
121 thread_set_errno(saveerr);
122 return false;
123 }
124 if ((statBuf.st_mode & S_IFMT) != S_IFREG) {
125 close(*fd);
126 *fd = -1;
127 closeAutoFSNoWait(no_hang_fd);
128 thread_set_errno(EACCES);
129 return false;
130 }
131 if (statBuf.st_size == 0) {
132 *bytes = CFAllocatorAllocate(alloc, 4, 0); // don't return constant string -- it's freed!
133 if (__CFOASafe) __CFSetLastAllocationEventName(*bytes, "CFUtilities (file-bytes)");
134 *length = 0;
135 } else {
136 CFIndex desiredLength;
137 if ((maxLength >= statBuf.st_size) || (maxLength == 0)) {
138 desiredLength = statBuf.st_size;
139 } else {
140 desiredLength = maxLength;
141 }
142 *bytes = CFAllocatorAllocate(alloc, desiredLength, 0);
143 if (__CFOASafe) __CFSetLastAllocationEventName(*bytes, "CFUtilities (file-bytes)");
144 // fcntl(fd, F_NOCACHE, 1);
145 if (read(*fd, *bytes, desiredLength) < 0) {
146 CFAllocatorDeallocate(alloc, *bytes);
147 close(*fd);
148 *fd = -1;
149 closeAutoFSNoWait(no_hang_fd);
150 return false;
151 }
152 *length = desiredLength;
153 }
154 closeAutoFSNoWait(no_hang_fd);
155 return true;
156 }
157
158 CF_PRIVATE Boolean _CFReadBytesFromPath(CFAllocatorRef alloc, const char *path, void **bytes, CFIndex *length, CFIndex maxLength, int extraOpenFlags) {
159 int fd = -1;
160 Boolean result = _CFReadBytesFromPathAndGetFD(alloc, path, bytes, length, maxLength, extraOpenFlags, &fd);
161 if (fd >= 0) {
162 close(fd);
163 }
164 return result;
165 }
166 CF_PRIVATE Boolean _CFReadBytesFromFile(CFAllocatorRef alloc, CFURLRef url, void **bytes, CFIndex *length, CFIndex maxLength, int extraOpenFlags) {
167 // maxLength is the number of bytes desired, or 0 if the whole file is desired regardless of length.
168
169 char path[CFMaxPathSize];
170 if (!CFURLGetFileSystemRepresentation(url, true, (uint8_t *)path, CFMaxPathSize)) {
171 return false;
172 }
173 return _CFReadBytesFromPath(alloc, (const char *)path, bytes, length, maxLength, extraOpenFlags);
174 }
175
176 CF_PRIVATE Boolean _CFWriteBytesToFile(CFURLRef url, const void *bytes, CFIndex length) {
177 int fd = -1;
178 int mode;
179 struct statinfo statBuf;
180 char path[CFMaxPathSize];
181 if (!CFURLGetFileSystemRepresentation(url, true, (uint8_t *)path, CFMaxPathSize)) {
182 return false;
183 }
184
185 int no_hang_fd = openAutoFSNoWait();
186 mode = 0666;
187 if (0 == stat(path, &statBuf)) {
188 mode = statBuf.st_mode;
189 } else if (thread_errno() != ENOENT) {
190 closeAutoFSNoWait(no_hang_fd);
191 return false;
192 }
193 fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|CF_OPENFLGS, 0666);
194 if (fd < 0) {
195 closeAutoFSNoWait(no_hang_fd);
196 return false;
197 }
198 if (length && write(fd, bytes, length) != length) {
199 int saveerr = thread_errno();
200 close(fd);
201 closeAutoFSNoWait(no_hang_fd);
202 thread_set_errno(saveerr);
203 return false;
204 }
205 #if DEPLOYMENT_TARGET_WINDOWS
206 FlushFileBuffers((HANDLE)_get_osfhandle(fd));
207 #else
208 fsync(fd);
209 #endif
210 close(fd);
211 closeAutoFSNoWait(no_hang_fd);
212 return true;
213 }
214
215
216 /* On Mac OS 8/9, one of dirSpec and dirURL must be non-NULL. On all other platforms, one of path and dirURL must be non-NULL
217 If both are present, they are assumed to be in-synch; that is, they both refer to the same directory. */
218 /* Lately, dirSpec appears to be (rightfully) unused. */
219 CF_PRIVATE CFMutableArrayRef _CFCreateContentsOfDirectory(CFAllocatorRef alloc, char *dirPath, void *dirSpec, CFURLRef dirURL, CFStringRef matchingAbstractType) {
220 CFMutableArrayRef files = NULL;
221 Boolean releaseBase = false;
222 CFIndex pathLength = dirPath ? strlen(dirPath) : 0;
223 // MF:!!! Need to use four-letter type codes where appropriate.
224 CFStringRef extension = (matchingAbstractType ? _CFCopyExtensionForAbstractType(matchingAbstractType) : NULL);
225 CFIndex targetExtLen = (extension ? CFStringGetLength(extension) : 0);
226
227 #if DEPLOYMENT_TARGET_WINDOWS
228 // This is a replacement for 'dirent' below, and also uses wchar_t to support unicode paths
229 wchar_t extBuff[CFMaxPathSize];
230 int extBuffInteriorDotCount = 0; //people insist on using extensions like ".trace.plist", so we need to know how many dots back to look :(
231
232 if (targetExtLen > 0) {
233 CFIndex usedBytes = 0;
234 CFStringGetBytes(extension, CFRangeMake(0, targetExtLen), kCFStringEncodingUTF16, 0, false, (uint8_t *)extBuff, CFMaxPathLength, &usedBytes);
235 targetExtLen = usedBytes / sizeof(wchar_t);
236 extBuff[targetExtLen] = '\0';
237 wchar_t *extBuffStr = (wchar_t *)extBuff;
238 if (extBuffStr[0] == '.')
239 extBuffStr++; //skip the first dot, it's legitimate to have ".plist" for example
240
241 wchar_t *extBuffDotPtr = extBuffStr;
242 while ((extBuffDotPtr = wcschr(extBuffStr, '.'))) { //find the next . in the extension...
243 extBuffInteriorDotCount++;
244 extBuffStr = extBuffDotPtr + 1;
245 }
246 }
247
248 wchar_t pathBuf[CFMaxPathSize];
249
250 if (!dirPath) {
251 if (!_CFURLGetWideFileSystemRepresentation(dirURL, true, pathBuf, CFMaxPathLength)) {
252 if (extension) CFRelease(extension);
253 return NULL;
254 }
255
256 pathLength = wcslen(pathBuf);
257
258 } else {
259 // Convert dirPath to a wide representation and put it into our pathBuf
260 // Get the real length of the string in UTF16 characters
261 CFStringRef dirPathStr = CFStringCreateWithCString(kCFAllocatorSystemDefault, dirPath, kCFStringEncodingUTF8);
262 CFIndex strLen = CFStringGetLength(dirPathStr);
263
264 // Copy the string into the buffer and terminate
265 CFStringGetCharacters(dirPathStr, CFRangeMake(0, strLen), (UniChar *)pathBuf);
266 pathBuf[strLen] = 0;
267
268 CFRelease(dirPathStr);
269 }
270
271 WIN32_FIND_DATAW file;
272 HANDLE handle;
273
274 if (pathLength + 2 >= CFMaxPathLength) {
275 if (extension) {
276 CFRelease(extension);
277 }
278 return NULL;
279 }
280
281 pathBuf[pathLength] = '\\';
282 pathBuf[pathLength + 1] = '*';
283 pathBuf[pathLength + 2] = '\0';
284 handle = FindFirstFileW(pathBuf, (LPWIN32_FIND_DATAW)&file);
285 if (INVALID_HANDLE_VALUE == handle) {
286 pathBuf[pathLength] = '\0';
287 if (extension) {
288 CFRelease(extension);
289 }
290 return NULL;
291 }
292
293 files = CFArrayCreateMutable(alloc, 0, &kCFTypeArrayCallBacks);
294
295 do {
296 CFURLRef fileURL;
297 CFIndex namelen = wcslen(file.cFileName);
298 if (file.cFileName[0] == '.' && (namelen == 1 || (namelen == 2 && file.cFileName[1] == '.'))) {
299 continue;
300 }
301
302 if (targetExtLen > namelen) continue; // if the extension is the same length or longer than the name, it can't possibly match.
303
304 if (targetExtLen > 0) {
305 if (file.cFileName[namelen - 1] == '.') continue; //filename ends with a dot, no extension
306
307 wchar_t *fileExt = NULL;
308
309 if (extBuffInteriorDotCount == 0) {
310 fileExt = wcsrchr(file.cFileName, '.');
311 } else { //find the Nth occurrence of . from the end of the string, to handle ".foo.bar"
312 wchar_t *save = file.cFileName;
313 while ((save = wcschr(save, '.')) && !fileExt) {
314 wchar_t *temp = save;
315 int moreDots = 0;
316 while ((temp = wcschr(temp, '.'))) {
317 if (++moreDots == extBuffInteriorDotCount) break;
318 }
319 if (moreDots == extBuffInteriorDotCount) {
320 fileExt = save;
321 }
322 }
323 }
324
325 if (!fileExt) continue; //no extension
326
327 if (((const wchar_t *)extBuff)[0] != '.')
328 fileExt++; //omit the dot if the target file extension omits the dot
329
330 CFIndex fileExtLen = wcslen(fileExt);
331
332 //if the extensions are different lengths, they can't possibly match
333 if (fileExtLen != targetExtLen) continue;
334
335 // Check to see if it matches the extension we're looking for.
336 if (_wcsicmp(fileExt, (const wchar_t *)extBuff) != 0) {
337 continue;
338 }
339 }
340 if (dirURL == NULL) {
341 CFStringRef dirURLStr = CFStringCreateWithBytes(alloc, (const uint8_t *)pathBuf, pathLength * sizeof(wchar_t), kCFStringEncodingUTF16, NO);
342 dirURL = CFURLCreateWithFileSystemPath(alloc, dirURLStr, kCFURLWindowsPathStyle, true);
343 CFRelease(dirURLStr);
344 releaseBase = true;
345 }
346 // MF:!!! What about the trailing slash?
347 CFStringRef fileURLStr = CFStringCreateWithBytes(alloc, (const uint8_t *)file.cFileName, namelen * sizeof(wchar_t), kCFStringEncodingUTF16, NO);
348 fileURL = CFURLCreateWithFileSystemPathRelativeToBase(alloc, fileURLStr, kCFURLWindowsPathStyle, (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false, dirURL);
349 CFArrayAppendValue(files, fileURL);
350 CFRelease(fileURL);
351 CFRelease(fileURLStr);
352 } while (FindNextFileW(handle, &file));
353 FindClose(handle);
354 pathBuf[pathLength] = '\0';
355
356 #elif DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI || DEPLOYMENT_TARGET_LINUX || DEPLOYMENT_TARGET_FREEBSD
357 uint8_t extBuff[CFMaxPathSize];
358 int extBuffInteriorDotCount = 0; //people insist on using extensions like ".trace.plist", so we need to know how many dots back to look :(
359
360 if (targetExtLen > 0) {
361 CFStringGetBytes(extension, CFRangeMake(0, targetExtLen), CFStringFileSystemEncoding(), 0, false, extBuff, CFMaxPathLength, &targetExtLen);
362 extBuff[targetExtLen] = '\0';
363 char *extBuffStr = (char *)extBuff;
364 if (extBuffStr[0] == '.')
365 extBuffStr++; //skip the first dot, it's legitimate to have ".plist" for example
366
367 char *extBuffDotPtr = extBuffStr;
368 while ((extBuffDotPtr = strchr(extBuffStr, '.'))) { //find the next . in the extension...
369 extBuffInteriorDotCount++;
370 extBuffStr = extBuffDotPtr + 1;
371 }
372 }
373
374 uint8_t pathBuf[CFMaxPathSize];
375
376 if (!dirPath) {
377 if (!CFURLGetFileSystemRepresentation(dirURL, true, pathBuf, CFMaxPathLength)) {
378 if (extension) CFRelease(extension);
379 return NULL;
380 } else {
381 dirPath = (char *)pathBuf;
382 pathLength = strlen(dirPath);
383 }
384 }
385
386 struct dirent buffer;
387 struct dirent *dp;
388 int err;
389
390 int no_hang_fd = __CFProphylacticAutofsAccess ? open("/dev/autofs_nowait", 0) : -1;
391
392 DIR *dirp = opendir(dirPath);
393 if (!dirp) {
394 if (extension) {
395 CFRelease(extension);
396 }
397 if (-1 != no_hang_fd) close(no_hang_fd);
398 return NULL;
399 // raiseErrno("opendir", path);
400 }
401 files = CFArrayCreateMutable(alloc, 0, & kCFTypeArrayCallBacks);
402
403 while((0 == readdir_r(dirp, &buffer, &dp)) && dp) {
404 CFURLRef fileURL;
405 unsigned namelen = strlen(dp->d_name);
406
407 // skip . & ..; they cause descenders to go berserk
408 if (dp->d_name[0] == '.' && (namelen == 1 || (namelen == 2 && dp->d_name[1] == '.'))) {
409 continue;
410 }
411
412 if (targetExtLen > namelen) continue; // if the extension is the same length or longer than the name, it can't possibly match.
413
414 if (targetExtLen > 0) {
415 if (dp->d_name[namelen - 1] == '.') continue; //filename ends with a dot, no extension
416
417 char *fileExt = NULL;
418 if (extBuffInteriorDotCount == 0) {
419 fileExt = strrchr(dp->d_name, '.');
420 } else { //find the Nth occurrence of . from the end of the string, to handle ".foo.bar"
421 char *save = dp->d_name;
422 while ((save = strchr(save, '.')) && !fileExt) {
423 char *temp = save;
424 int moreDots = 0;
425 while ((temp = strchr(temp, '.'))) {
426 if (++moreDots == extBuffInteriorDotCount) break;
427 }
428 if (moreDots == extBuffInteriorDotCount) {
429 fileExt = save;
430 }
431 }
432 }
433
434 if (!fileExt) continue; //no extension
435
436 if (((char *)extBuff)[0] != '.')
437 fileExt++; //omit the dot if the target extension omits the dot; safe, because we checked to make sure it isn't the last character just before
438
439 size_t fileExtLen = strlen(fileExt);
440
441 //if the extensions are different lengths, they can't possibly match
442 if (fileExtLen != targetExtLen) continue;
443
444 // Check to see if it matches the extension we're looking for.
445 if (strncmp(fileExt, (char *)extBuff, fileExtLen) != 0) {
446 continue;
447 }
448 }
449 if (dirURL == NULL) {
450 dirURL = CFURLCreateFromFileSystemRepresentation(alloc, (uint8_t *)dirPath, pathLength, true);
451 releaseBase = true;
452 }
453 if (dp->d_type == DT_DIR || dp->d_type == DT_UNKNOWN || dp->d_type == DT_LNK || dp->d_type == DT_WHT) {
454 Boolean isDir = (dp->d_type == DT_DIR);
455 if (!isDir) {
456 // Ugh; must stat.
457 char subdirPath[CFMaxPathLength];
458 struct statinfo statBuf;
459 strlcpy(subdirPath, dirPath, sizeof(subdirPath));
460 strlcat(subdirPath, "/", sizeof(subdirPath));
461 strlcat(subdirPath, dp->d_name, sizeof(subdirPath));
462 if (stat(subdirPath, &statBuf) == 0) {
463 isDir = ((statBuf.st_mode & S_IFMT) == S_IFDIR);
464 }
465 }
466 #if DEPLOYMENT_TARGET_LINUX
467 fileURL = CFURLCreateFromFileSystemRepresentationRelativeToBase(alloc, (uint8_t *)dp->d_name, namelen, isDir, dirURL);
468 #else
469 fileURL = CFURLCreateFromFileSystemRepresentationRelativeToBase(alloc, (uint8_t *)dp->d_name, dp->d_namlen, isDir, dirURL);
470 #endif
471 } else {
472 #if DEPLOYMENT_TARGET_LINUX
473 fileURL = CFURLCreateFromFileSystemRepresentationRelativeToBase (alloc, (uint8_t *)dp->d_name, namelen, false, dirURL);
474 #else
475 fileURL = CFURLCreateFromFileSystemRepresentationRelativeToBase (alloc, (uint8_t *)dp->d_name, dp->d_namlen, false, dirURL);
476 #endif
477 }
478 CFArrayAppendValue(files, fileURL);
479 CFRelease(fileURL);
480 }
481 err = closedir(dirp);
482 if (-1 != no_hang_fd) close(no_hang_fd);
483 if (err != 0) {
484 CFRelease(files);
485 if (releaseBase) {
486 CFRelease(dirURL);
487 }
488 if (extension) {
489 CFRelease(extension);
490 }
491 return NULL;
492 }
493
494 #else
495
496 #error _CFCreateContentsOfDirectory() unknown architecture, not implemented
497
498 #endif
499
500 if (extension) {
501 CFRelease(extension);
502 }
503 if (releaseBase) {
504 CFRelease(dirURL);
505 }
506 return files;
507 }
508
509 CF_PRIVATE SInt32 _CFGetPathProperties(CFAllocatorRef alloc, char *path, Boolean *exists, SInt32 *posixMode, int64_t *size, CFDateRef *modTime, SInt32 *ownerID, CFArrayRef *dirContents) {
510 Boolean fileExists;
511 Boolean isDirectory = false;
512
513 if ((exists == NULL) && (posixMode == NULL) && (size == NULL) && (modTime == NULL) && (ownerID == NULL) && (dirContents == NULL)) {
514 // Nothing to do.
515 return 0;
516 }
517
518 struct statinfo statBuf;
519
520 if (stat(path, &statBuf) != 0) {
521 // stat failed, but why?
522 if (thread_errno() == ENOENT) {
523 fileExists = false;
524 } else {
525 return thread_errno();
526 }
527 } else {
528 fileExists = true;
529 isDirectory = ((statBuf.st_mode & S_IFMT) == S_IFDIR);
530 }
531
532
533 if (exists != NULL) {
534 *exists = fileExists;
535 }
536
537 if (posixMode != NULL) {
538 if (fileExists) {
539
540 *posixMode = statBuf.st_mode;
541
542 } else {
543 *posixMode = 0;
544 }
545 }
546
547 if (size != NULL) {
548 if (fileExists) {
549
550 *size = statBuf.st_size;
551
552 } else {
553 *size = 0;
554 }
555 }
556
557 if (modTime != NULL) {
558 if (fileExists) {
559 #if DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_LINUX
560 struct timespec ts = {statBuf.st_mtime, 0};
561 #else
562 struct timespec ts = statBuf.st_mtimespec;
563 #endif
564 *modTime = CFDateCreate(alloc, _CFAbsoluteTimeFromFileTimeSpec(ts));
565 } else {
566 *modTime = NULL;
567 }
568 }
569
570 if (ownerID != NULL) {
571 if (fileExists) {
572
573 *ownerID = statBuf.st_uid;
574
575 } else {
576 *ownerID = -1;
577 }
578 }
579
580 if (dirContents != NULL) {
581 if (fileExists && isDirectory) {
582
583 CFMutableArrayRef contents = _CFCreateContentsOfDirectory(alloc, (char *)path, NULL, NULL, NULL);
584
585 if (contents) {
586 *dirContents = contents;
587 } else {
588 *dirContents = NULL;
589 }
590 } else {
591 *dirContents = NULL;
592 }
593 }
594 return 0;
595 }
596
597 CF_PRIVATE SInt32 _CFGetFileProperties(CFAllocatorRef alloc, CFURLRef pathURL, Boolean *exists, SInt32 *posixMode, int64_t *size, CFDateRef *modTime, SInt32 *ownerID, CFArrayRef *dirContents) {
598
599 char path[CFMaxPathSize];
600
601 if (!CFURLGetFileSystemRepresentation(pathURL, true, (uint8_t *)path, CFMaxPathLength)) {
602 return -1;
603 }
604
605 return _CFGetPathProperties(alloc, path, exists, posixMode, size, modTime, ownerID, dirContents);
606 }
607
608
609 #if DEPLOYMENT_TARGET_WINDOWS
610 #define WINDOWS_PATH_SEMANTICS
611 #else
612 #define UNIX_PATH_SEMANTICS
613 #endif
614
615 #if defined(WINDOWS_PATH_SEMANTICS)
616 #define CFPreferredSlash ((UniChar)'\\')
617 #define CFPreferredSlashStr CFSTR("\\")
618 #elif defined(UNIX_PATH_SEMANTICS)
619 #define CFPreferredSlash ((UniChar)'/')
620 #define CFPreferredSlashStr CFSTR("/")
621 #else
622 #error Cannot define NSPreferredSlash on this platform
623 #endif
624
625 static Boolean _hasDrive(CFStringRef path) {
626 if (CFStringGetLength(path) >= 2) {
627 UniChar firstCharacters[2];
628 firstCharacters[0] = CFStringGetCharacterAtIndex(path, 0);
629 firstCharacters[1] = CFStringGetCharacterAtIndex(path, 1);
630 if (firstCharacters[1] == ':' &&
631 (('A' <= (firstCharacters)[0] && (firstCharacters)[0] <= 'Z') ||
632 ('a' <= (firstCharacters)[0] && (firstCharacters)[0] <= 'z'))
633 ) {
634 return true;
635 }
636 }
637 return false;
638 }
639
640 static Boolean _hasNet(CFStringRef path) {
641 if (CFStringGetLength(path) >= 2) {
642 UniChar firstCharacters[2];
643 firstCharacters[0] = CFStringGetCharacterAtIndex(path, 0);
644 firstCharacters[1] = CFStringGetCharacterAtIndex(path, 1);
645 if (firstCharacters[0] == '\\' && firstCharacters[1] == '\\') return true;
646 }
647 return false;
648 }
649
650 #define HAS_DRIVE(S) ((S)[1] == ':' && (('A' <= (S)[0] && (S)[0] <= 'Z') || ('a' <= (S)[0] && (S)[0] <= 'z')))
651 #define HAS_NET(S) ((S)[0] == '\\' && (S)[1] == '\\')
652
653 #if defined(WINDOWS_PATH_SEMANTICS)
654 #define IS_SLASH(C) ((C) == '\\' || (C) == '/')
655 #elif defined(UNIX_PATH_SEMANTICS)
656 #define IS_SLASH(C) ((C) == '/')
657 #endif
658
659 CF_PRIVATE UniChar _CFGetSlash() {
660 return CFPreferredSlash;
661 }
662
663 CF_PRIVATE CFStringRef _CFGetSlashStr() {
664 return CFPreferredSlashStr;
665 }
666
667 CF_PRIVATE Boolean _CFIsAbsolutePath(UniChar *unichars, CFIndex length) {
668 if (length < 1) {
669 return false;
670 }
671 #if defined(WINDOWS_PATH_SEMANTICS)
672 if (unichars[0] == '~') {
673 return true;
674 }
675 if (length < 2) {
676 return false;
677 }
678 if (HAS_NET(unichars)) {
679 return true;
680 }
681 if (length < 3) {
682 return false;
683 }
684 if (IS_SLASH(unichars[2]) && HAS_DRIVE(unichars)) {
685 return true;
686 }
687 #else
688 if (unichars[0] == '~') {
689 return true;
690 }
691 if (IS_SLASH(unichars[0])) {
692 return true;
693 }
694 #endif
695 return false;
696 }
697
698 CF_PRIVATE Boolean _CFStripTrailingPathSlashes(UniChar *unichars, CFIndex *length) {
699 Boolean destHasDrive = (1 < *length) && HAS_DRIVE(unichars);
700 CFIndex oldLength = *length;
701 while (((destHasDrive && 3 < *length) || (!destHasDrive && 1 < *length)) && IS_SLASH(unichars[*length - 1])) {
702 (*length)--;
703 }
704 return (oldLength != *length);
705 }
706
707 static Boolean _CFAppendTrailingPathSlash(UniChar *unichars, CFIndex *length, CFIndex maxLength) {
708 if (maxLength < *length + 1) {
709 return false;
710 }
711 switch (*length) {
712 case 0:
713 break;
714 case 1:
715 if (!IS_SLASH(unichars[0])) {
716 unichars[(*length)++] = CFPreferredSlash;
717 }
718 break;
719 case 2:
720 if (!HAS_DRIVE(unichars) && !HAS_NET(unichars)) {
721 unichars[(*length)++] = CFPreferredSlash;
722 }
723 break;
724 default:
725 unichars[(*length)++] = CFPreferredSlash;
726 break;
727 }
728 return true;
729 }
730
731 CF_PRIVATE void _CFAppendTrailingPathSlash2(CFMutableStringRef path) {
732 static const UniChar slash[1] = { CFPreferredSlash };
733 CFIndex len = CFStringGetLength(path);
734 if (len == 0) {
735 // Do nothing for this case
736 } else if (len == 1) {
737 UniChar character = CFStringGetCharacterAtIndex((CFStringRef)path, 0);
738 if (!IS_SLASH(character)) {
739 CFStringAppendCharacters(path, slash, 1);
740 }
741 } else if (len == 2) {
742 if (!_hasDrive(path) && !_hasNet(path)) {
743 CFStringAppendCharacters(path, slash, 1);
744 }
745 } else {
746 CFStringAppendCharacters(path, slash, 1);
747 }
748 }
749
750 CF_PRIVATE void _CFAppendConditionalTrailingPathSlash2(CFMutableStringRef path) {
751 static const UniChar slash[1] = { CFPreferredSlash };
752 UniChar character = CFStringGetCharacterAtIndex((CFStringRef)path, CFStringGetLength(path) - 1);
753 if (!IS_SLASH(character)) {
754 CFStringAppendCharacters(path, slash, 1);
755 }
756 }
757
758 CF_PRIVATE void _CFAppendPathComponent2(CFMutableStringRef path, CFStringRef component) {
759 _CFAppendTrailingPathSlash2(path);
760 CFStringAppend(path, component);
761 }
762
763 CF_PRIVATE Boolean _CFAppendPathComponent(UniChar *unichars, CFIndex *length, CFIndex maxLength, UniChar *component, CFIndex componentLength) {
764 if (0 == componentLength) {
765 return true;
766 }
767 if (maxLength < *length + 1 + componentLength) {
768 return false;
769 }
770 _CFAppendTrailingPathSlash(unichars, length, maxLength);
771 memmove(unichars + *length, component, componentLength * sizeof(UniChar));
772 *length += componentLength;
773 return true;
774 }
775
776 CF_PRIVATE Boolean _CFAppendPathExtension2(CFMutableStringRef path, CFStringRef extension) {
777 if (!path) {
778 return false;
779 }
780
781 if (0 < CFStringGetLength(extension) && IS_SLASH(CFStringGetCharacterAtIndex(extension, 0))) {
782 return false;
783 }
784 if (1 < CFStringGetLength(extension)) {
785 if (_hasDrive(extension)) return false;
786 }
787
788 Boolean destHasDrive = (1 < CFStringGetLength(path)) && _hasDrive(path);
789 while (((destHasDrive && 3 < CFStringGetLength(path)) || (!destHasDrive && 1 < CFStringGetLength(path))) && IS_SLASH(CFStringGetCharacterAtIndex(path, CFStringGetLength(path) - 1))) {
790 CFStringDelete(path, CFRangeMake(CFStringGetLength(path) - 1, 1));
791 }
792
793 if (CFStringGetLength(path) == 0) {
794 return false;
795 }
796
797 UniChar firstChar = CFStringGetCharacterAtIndex(path, 0);
798 CFIndex newLength = CFStringGetLength(path);
799 switch (newLength) {
800 case 0:
801 return false;
802 case 1:
803 if (IS_SLASH(firstChar) || firstChar == '~') {
804 return false;
805 }
806 break;
807 case 2:
808 if (_hasDrive(path) || _hasNet(path)) {
809 return false;
810 }
811 break;
812 case 3:
813 if (IS_SLASH(CFStringGetCharacterAtIndex(path, 2)) && _hasDrive(path)) {
814 return false;
815 }
816 break;
817 }
818 if (0 < newLength && firstChar == '~') {
819 // Make sure we have a slash in the string
820 if (!CFStringFindWithOptions(path, CFPreferredSlashStr, CFRangeMake(1, newLength - 1), 0, NULL)) {
821 return false;
822 }
823 }
824 static const UniChar dotChar = '.';
825 CFStringAppendCharacters(path, &dotChar, 1);
826 CFStringAppend(path, extension);
827 return true;
828 }
829
830 CF_PRIVATE Boolean _CFAppendPathExtension(UniChar *unichars, CFIndex *length, CFIndex maxLength, UniChar *extension, CFIndex extensionLength) {
831 if (maxLength < *length + 1 + extensionLength) {
832 return false;
833 }
834 if ((0 < extensionLength && IS_SLASH(extension[0])) || (1 < extensionLength && HAS_DRIVE(extension))) {
835 return false;
836 }
837 _CFStripTrailingPathSlashes(unichars, length);
838 switch (*length) {
839 case 0:
840 return false;
841 case 1:
842 if (IS_SLASH(unichars[0]) || unichars[0] == '~') {
843 return false;
844 }
845 break;
846 case 2:
847 if (HAS_DRIVE(unichars) || HAS_NET(unichars)) {
848 return false;
849 }
850 break;
851 case 3:
852 if (IS_SLASH(unichars[2]) && HAS_DRIVE(unichars)) {
853 return false;
854 }
855 break;
856 }
857 if (0 < *length && unichars[0] == '~') {
858 CFIndex idx;
859 Boolean hasSlash = false;
860 for (idx = 1; idx < *length; idx++) {
861 if (IS_SLASH(unichars[idx])) {
862 hasSlash = true;
863 break;
864 }
865 }
866 if (!hasSlash) {
867 return false;
868 }
869 }
870 unichars[(*length)++] = '.';
871 memmove(unichars + *length, extension, extensionLength * sizeof(UniChar));
872 *length += extensionLength;
873 return true;
874 }
875
876 CF_PRIVATE Boolean _CFTransmutePathSlashes(UniChar *unichars, CFIndex *length, UniChar replSlash) {
877 CFIndex didx, sidx, scnt = *length;
878 sidx = (1 < *length && HAS_NET(unichars)) ? 2 : 0;
879 didx = sidx;
880 while (sidx < scnt) {
881 if (IS_SLASH(unichars[sidx])) {
882 unichars[didx++] = replSlash;
883 for (sidx++; sidx < scnt && IS_SLASH(unichars[sidx]); sidx++);
884 } else {
885 unichars[didx++] = unichars[sidx++];
886 }
887 }
888 *length = didx;
889 return (scnt != didx);
890 }
891
892 CF_PRIVATE CFStringRef _CFCreateLastPathComponent(CFAllocatorRef alloc, CFStringRef path, CFIndex *slashIndex) {
893 CFIndex len = CFStringGetLength(path);
894 if (len < 2) {
895 // Can't be any path components in a string this short
896 if (slashIndex) *slashIndex = -1;
897 return (CFStringRef)CFRetain(path);
898 }
899
900 // Find the last slash
901 for (CFIndex i = len - 1; i >= 0; i--) {
902 if (IS_SLASH(CFStringGetCharacterAtIndex(path, i))) {
903 if (slashIndex) *slashIndex = i;
904 return CFStringCreateWithSubstring(alloc, path, CFRangeMake(i + 1, len - i - 1));
905 }
906 }
907
908 // Strip any drive if we have one
909 if (len > 2 && _hasDrive(path)) {
910 if (slashIndex) *slashIndex = -1;
911 return CFStringCreateWithSubstring(alloc, path, CFRangeMake(2, len - 2));
912 }
913
914 // No slash, so just return the same string
915 if (slashIndex) *slashIndex = -1;
916 return (CFStringRef)CFRetain(path);
917 }
918
919 CF_PRIVATE CFIndex _CFStartOfLastPathComponent(UniChar *unichars, CFIndex length) {
920 CFIndex idx;
921 if (length < 2) {
922 return 0;
923 }
924 for (idx = length - 1; idx; idx--) {
925 if (IS_SLASH(unichars[idx - 1])) {
926 return idx;
927 }
928 }
929 if ((2 < length) && HAS_DRIVE(unichars)) {
930 return 2;
931 }
932 return 0;
933 }
934
935 CF_PRIVATE CFIndex _CFStartOfLastPathComponent2(CFStringRef path) {
936 CFIndex length = CFStringGetLength(path);
937 if (length < 2) {
938 return 0;
939 }
940 for (CFIndex idx = length - 1; idx; idx--) {
941 if (IS_SLASH(CFStringGetCharacterAtIndex(path, idx - 1))) {
942 return idx;
943 }
944 }
945 if ((2 < length && _hasDrive(path))) {
946 return 2;
947 }
948 return 0;
949 }
950
951 CF_PRIVATE CFIndex _CFLengthAfterDeletingLastPathComponent(UniChar *unichars, CFIndex length) {
952 CFIndex idx;
953 if (length < 2) {
954 return 0;
955 }
956 for (idx = length - 1; idx; idx--) {
957 if (IS_SLASH(unichars[idx - 1])) {
958 if ((idx != 1) && (!HAS_DRIVE(unichars) || idx != 3)) {
959 return idx - 1;
960 }
961 return idx;
962 }
963 }
964 if ((2 < length) && HAS_DRIVE(unichars)) {
965 return 2;
966 }
967 return 0;
968 }
969
970 CF_PRIVATE CFIndex _CFStartOfPathExtension2(CFStringRef path) {
971 if (CFStringGetLength(path) < 2) {
972 return 0;
973 }
974 Boolean hasDrive = _hasDrive(path);
975 for (CFIndex idx = CFStringGetLength(path) - 1; idx; idx--) {
976 UniChar thisCharacter = CFStringGetCharacterAtIndex(path, idx);
977 if (IS_SLASH(thisCharacter)) {
978 return 0;
979 }
980 if (thisCharacter != '.') {
981 continue;
982 }
983 if (idx == 2 && hasDrive) {
984 return 0;
985 }
986 return idx;
987 }
988 return 0;
989 }
990
991 CF_PRIVATE CFIndex _CFStartOfPathExtension(UniChar *unichars, CFIndex length) {
992 CFIndex idx;
993 if (length < 2) {
994 return 0;
995 }
996 for (idx = length - 1; idx; idx--) {
997 if (IS_SLASH(unichars[idx - 1])) {
998 return 0;
999 }
1000 if (unichars[idx] != '.') {
1001 continue;
1002 }
1003 if (idx == 2 && HAS_DRIVE(unichars)) {
1004 return 0;
1005 }
1006 return idx;
1007 }
1008 return 0;
1009 }
1010
1011 CF_PRIVATE CFIndex _CFLengthAfterDeletingPathExtension2(CFStringRef path) {
1012 CFIndex start = _CFStartOfPathExtension2(path);
1013 return ((0 < start) ? start : CFStringGetLength(path));
1014 }
1015
1016 CF_PRIVATE CFIndex _CFLengthAfterDeletingPathExtension(UniChar *unichars, CFIndex length) {
1017 CFIndex start = _CFStartOfPathExtension(unichars, length);
1018 return ((0 < start) ? start : length);
1019 }
1020
1021 #if DEPLOYMENT_TARGET_WINDOWS
1022 #define DT_DIR 4
1023 #define DT_REG 8
1024 #define DT_LNK 10
1025 #endif
1026
1027 // NOTE: on Windows the filename is UTF16-encoded, the fileNameLen is result of wcslen. This function automatically skips '.' and '..', and '._' files
1028 CF_PRIVATE void _CFIterateDirectory(CFStringRef directoryPath, Boolean (^fileHandler)(CFStringRef fileName, uint8_t fileType)) {
1029 char directoryPathBuf[CFMaxPathSize];
1030 if (!CFStringGetFileSystemRepresentation(directoryPath, directoryPathBuf, CFMaxPathSize)) return;
1031
1032 #if DEPLOYMENT_TARGET_WINDOWS
1033 CFIndex cpathLen = strlen(directoryPathBuf);
1034 // Make sure there is room for the additional space we need in the win32 api
1035 if (cpathLen + 2 < CFMaxPathSize) {
1036 WIN32_FIND_DATAW file;
1037 HANDLE handle;
1038
1039 directoryPathBuf[cpathLen++] = '\\';
1040 directoryPathBuf[cpathLen++] = '*';
1041 directoryPathBuf[cpathLen] = '\0';
1042
1043 // Convert UTF8 buffer to windows appropriate UTF-16LE
1044 // Get the real length of the string in UTF16 characters
1045 CFStringRef cfStr = CFStringCreateWithCString(kCFAllocatorSystemDefault, directoryPathBuf, kCFStringEncodingUTF8);
1046 cpathLen = CFStringGetLength(cfStr);
1047 // Allocate a wide buffer to hold the converted string, including space for a NULL terminator
1048 wchar_t *wideBuf = (wchar_t *)malloc((cpathLen + 1) * sizeof(wchar_t));
1049 // Copy the string into the buffer and terminate
1050 CFStringGetCharacters(cfStr, CFRangeMake(0, cpathLen), (UniChar *)wideBuf);
1051 wideBuf[cpathLen] = 0;
1052 CFRelease(cfStr);
1053
1054 handle = FindFirstFileW(wideBuf, (LPWIN32_FIND_DATAW)&file);
1055 if (handle != INVALID_HANDLE_VALUE) {
1056 do {
1057 CFIndex nameLen = wcslen(file.cFileName);
1058 if (file.cFileName[0] == '.' && (nameLen == 1 || (nameLen == 2 && file.cFileName[1] == '.'))) {
1059 continue;
1060 }
1061
1062 CFStringRef fileName = CFStringCreateWithBytes(kCFAllocatorSystemDefault, (const uint8_t *)file.cFileName, nameLen * sizeof(wchar_t), kCFStringEncodingUTF16, NO);
1063 if (!fileName) {
1064 continue;
1065 }
1066
1067 Boolean isDirectory = file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
1068 Boolean result = fileHandler(fileName, isDirectory ? DT_DIR : DT_REG);
1069 CFRelease(fileName);
1070 if (!result) break;
1071 } while (FindNextFileW(handle, &file));
1072
1073 FindClose(handle);
1074 }
1075 free(wideBuf);
1076 }
1077 #else
1078 DIR *dirp;
1079 struct dirent *dent;
1080 if ((dirp = opendir(directoryPathBuf))) {
1081 while ((dent = readdir(dirp))) {
1082 #if DEPLOYMENT_TARGET_LINUX
1083 CFIndex nameLen = strlen(dent->d_name);
1084 #else
1085 CFIndex nameLen = dent->d_namlen;
1086 #endif
1087 if (0 == nameLen || 0 == dent->d_fileno || ('.' == dent->d_name[0] && (1 == nameLen || (2 == nameLen && '.' == dent->d_name[1]) || '_' == dent->d_name[1]))) {
1088 continue;
1089 }
1090
1091 CFStringRef fileName = CFStringCreateWithFileSystemRepresentation(kCFAllocatorSystemDefault, dent->d_name);
1092 if (!fileName) {
1093 continue;
1094 }
1095
1096 Boolean result = fileHandler(fileName, dent->d_type);
1097 CFRelease(fileName);
1098 if (!result) break;
1099 }
1100 (void)closedir(dirp);
1101 }
1102 #endif
1103 }
1104