/ 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