1/*
2 * Copyright (c) 2014 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
65CF_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
73CF_INLINE void closeAutoFSNoWait(int fd) {
74    if (-1 != fd) close(fd);
75}
76
77CF_PRIVATE CFStringRef _CFCopyExtensionForAbstractType(CFStringRef abstractType) {
78    return (abstractType ? (CFStringRef)CFRetain(abstractType) : NULL);
79}
80
81
82CF_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
89CF_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
96CF_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
103CF_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
158CF_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}
166CF_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
176CF_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
217If 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. */
219CF_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
509CF_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
597CF_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
625static 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
640static 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
659CF_PRIVATE UniChar _CFGetSlash() {
660    return CFPreferredSlash;
661}
662
663CF_PRIVATE CFStringRef _CFGetSlashStr() {
664    return CFPreferredSlashStr;
665}
666
667CF_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
698CF_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
707static 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
731CF_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
750CF_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
758CF_PRIVATE void _CFAppendPathComponent2(CFMutableStringRef path, CFStringRef component) {
759    _CFAppendTrailingPathSlash2(path);
760    CFStringAppend(path, component);
761}
762
763CF_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
776CF_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
830CF_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
876CF_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
892CF_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
919CF_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
935CF_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
951CF_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
970CF_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
991CF_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
1011CF_PRIVATE CFIndex _CFLengthAfterDeletingPathExtension2(CFStringRef path) {
1012    CFIndex start = _CFStartOfPathExtension2(path);
1013    return ((0 < start) ? start : CFStringGetLength(path));
1014}
1015
1016CF_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
1028CF_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
1105