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