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