/ CFFileUtilities.c
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