4 Copyright (C) 1995, 1996 Ovidiu Predescu and Mircea Oancea.
7 Author: Mircea Oancea <mircea@jupiter.elcom.pub.ro>
8 Ovidiu Predescu <ovidiu@bx.logicnet.ro>
9 Helge Hess <helge@mdlink.de>
11 This file is part of libFoundation.
13 Permission to use, copy, modify, and distribute this software and its
14 documentation for any purpose and without fee is hereby granted, provided
15 that the above copyright notice appear in all copies and that both that
16 copyright notice and this permission notice appear in supporting
19 We disclaim all warranties with regard to this software, including all
20 implied warranties of merchantability and fitness, in no event shall
21 we be liable for any special, indirect or consequential damages or any
22 damages whatsoever resulting from loss of use, data or profits, whether in
23 an action of contract, negligence or other tortious action, arising out of
24 or in connection with the use or performance of this software.
27 #include <Foundation/common.h>
28 #include <objc/objc.h>
32 /* determine directory reading files */
34 #if defined(HAVE_DIRENT_H)
36 #elif defined(HAVE_SYS_DIR_H)
38 #elif defined(HAVE_SYS_NDIR_H)
39 # include <sys/ndir.h>
40 #elif defined(HAVE_NDIR_H)
42 #elif defined(HAVE_DIR_H)
46 #if defined(HAVE_WINDOWS_H)
50 #if !defined(_POSIX_VERSION)
52 # define DIR_enum_item struct direct
56 #if !defined(DIR_enum_item)
57 # define DIR_enum_item struct dirent
60 #define DIR_enum_state DIR
62 /* determine filesystem max path length */
64 #if defined(_POSIX_VERSION) || defined(__WIN32__)
65 # include <limits.h> /* for PATH_MAX */
66 # if defined(__MINGW32__)
67 # include <sys/utime.h>
73 # include <sys/param.h> /* for MAXPATHLEN */
78 # ifdef _POSIX_VERSION
79 # define PATH_MAX _POSIX_PATH_MAX
82 # define PATH_MAX MAXPATHLEN
84 # define PATH_MAX 1024
89 /* determine if we have statfs struct and function */
91 #ifdef HAVE_SYS_STATFS_H
92 # include <sys/statfs.h>
95 #ifdef HAVE_SYS_STATVFS_H
96 # include <sys/statvfs.h>
100 # include <sys/vfs.h>
104 #include <sys/file.h>
108 # include <sys/stat.h>
117 /* include usual headers */
119 #include <Foundation/NSArray.h>
120 #include <Foundation/NSDictionary.h>
121 #include <Foundation/NSData.h>
122 #include <Foundation/NSDate.h>
123 #include <Foundation/NSString.h>
124 #include <Foundation/NSValue.h>
125 #include <Foundation/NSAutoreleasePool.h>
126 #include <Foundation/NSPathUtilities.h>
127 #include <Foundation/NSThread.h>
128 #include <Foundation/NSLock.h>
129 #include <Foundation/NSNotification.h>
130 #include <Foundation/NSFileManager.h>
131 #include <Foundation/NSException.h>
132 #include <Foundation/exceptions/GeneralExceptions.h>
134 @interface NSFileManager (PrivateMethods)
136 /* Copies the contents of source file to destination file. Assumes source
137 and destination are regular files or symbolic links. */
138 - (BOOL)_copyFile:(NSString*)source toFile:(NSString*)destination
141 /* Recursively copies the contents of source directory to destination. */
142 - (BOOL)_copyPath:(NSString*)source toPath:(NSString*)destination
145 @end /* NSFileManager (PrivateMethods) */
149 * NSFileManager implementation
152 @implementation NSFileManager
154 // Getting the default manager
156 static BOOL isMultithreaded = NO;
157 static NSFileManager* defaultManager = nil;
161 static BOOL initialized = NO;
164 defaultManager = [[self alloc] init];
165 [[NSNotificationCenter defaultCenter]
167 selector:@selector(taskNowMultiThreaded:)
168 name:NSWillBecomeMultiThreadedNotification
174 + (void)taskNowMultiThreaded:notification
176 NSThread* currentThread = [NSThread currentThread];
178 [[currentThread threadDictionary]
179 setObject:AUTORELEASE(defaultManager) forKey:@"DefaultNSFileManager"];
180 defaultManager = nil;
181 isMultithreaded = YES;
184 extern NSRecursiveLock* libFoundationLock;
186 + (NSFileManager*)defaultManager
188 if (isMultithreaded) {
189 NSThread* currentThread = [NSThread currentThread];
192 [libFoundationLock lock];
194 manager = [[currentThread threadDictionary]
195 objectForKey:@"DefaultNSFileManager"];
197 manager = AUTORELEASE([[self alloc] init]);
198 [[currentThread threadDictionary]
200 forKey:@"DefaultNSFileManager"];
203 [libFoundationLock unlock];
208 return defaultManager;
211 // Directory operations
213 - (BOOL)changeCurrentDirectoryPath:(NSString*)path
215 const char* cpath = [self fileSystemRepresentationWithPath:path];
217 #if defined(__MINGW32__)
218 return SetCurrentDirectory(cpath) == TRUE ? YES : NO;
220 return (chdir(cpath) == 0);
224 #if defined(__MINGW32__)
225 - (BOOL)createDirectoryAtPath:(NSString*)path
226 attributes:(NSDictionary*)attributes
228 NSEnumerator *paths = [[path pathComponents] objectEnumerator];
230 NSString *completePath = nil;
232 while ((subPath = [paths nextObject])) {
235 completePath = (completePath == nil)
237 : [completePath stringByAppendingPathComponent:subPath];
239 if ([self fileExistsAtPath:completePath isDirectory:&isDir]) {
242 "WARNING: during creation of directory %s:"
243 " sub path %s exists, but is not a directory !",
244 [path cString], [completePath cString]);
250 cpath = [self fileSystemRepresentationWithPath:completePath];
251 if (CreateDirectory(cpath, NULL) == FALSE)
257 // change attributes of last directory
258 return [self changeFileAttributes:attributes
264 - (BOOL)createDirectoryAtPath:(NSString*)path
265 attributes:(NSDictionary*)attributes
268 char dirpath[PATH_MAX+1];
272 cpath = [self fileSystemRepresentationWithPath:path];
278 if (Strcmp(cpath, "/") == 0 || len == 0)
279 // cannot use "/" or "" as a new dir path
282 strcpy(dirpath, cpath);
284 if (dirpath[len-1] == '/')
285 dirpath[len-1] = '\0';
289 // find next path separator
290 while (dirpath[cur] != '/' && cur < len)
293 // if first char is '/' then again; (cur == len) -> last component
298 // check if path from 0 to cur is valid
300 if (stat(dirpath, &statbuf) == 0) {
302 return NO; // already existing last path
305 // make new directory
306 #if MKDIR_HAS_TWO_ARGS || defined(__CYGWIN32__)
307 if (mkdir(dirpath, 0777) != 0)
309 if (mkdir(dirpath) != 0)
311 return NO; // could not create component
312 // if last directory and attributes then change
313 if (cur == len && attributes)
314 return [self changeFileAttributes:attributes
315 atPath:[self stringWithFileSystemRepresentation:dirpath
324 #endif /* __MINGW32__ */
326 - (NSString*)currentDirectoryPath
328 #if defined(__MINGW32__)
329 unsigned char *buf = objc_atomic_malloc(2048);
330 DWORD len = GetCurrentDirectory(2046, buf);
333 buf = objc_realloc(buf, len + 2);
334 len = GetCurrentDirectory(len, buf);
336 if (len == 0) return nil;
337 self = [NSString stringWithCString:buf length:len];
342 #if defined(HAVE_GETCWD)
343 if (getcwd(path, PATH_MAX-1) == NULL)
346 if (getwd(path) == NULL)
348 #endif /* HAVE_GETCWD */
349 return [self stringWithFileSystemRepresentation:path length:Strlen(path)];
350 #endif /* __MINGW32__ */
355 - (BOOL)copyPath:(NSString*)source toPath:(NSString*)destination
359 NSDictionary *attributes;
361 if (![self fileExistsAtPath:source isDirectory:&sourceIsDir])
365 if ([self fileExistsAtPath:destination])
366 // destination must not exist
369 #if defined(__MINGW32__)
370 if (!CopyFile([self fileSystemRepresentationWithPath:source],
371 [self fileSystemRepresentationWithPath:destination],
372 FALSE /* overwrite if dest exists */)) {
374 NSDictionary *errorInfo
375 = [NSDictionary dictionaryWithObjectsAndKeys:
377 destination, @"ToPath",
378 @"cannot copy file", @"Error",
380 if ([handler fileManager:self shouldProceedAfterError:errorInfo])
387 attributes = [self fileAttributesAtPath:source traverseLink:NO];
390 /* If destination directory is a descendant of source directory copying
392 if ([[destination stringByAppendingString:@"/"]
393 hasPrefix:[source stringByAppendingString:@"/"]])
396 [handler fileManager:self willProcessPath:destination];
397 if (![self createDirectoryAtPath:destination attributes:attributes]) {
399 NSDictionary* errorInfo
400 = [NSDictionary dictionaryWithObjectsAndKeys:
401 destination, @"Path",
402 @"cannot create directory", @"Error",
404 return [handler fileManager:self
405 shouldProceedAfterError:errorInfo];
413 if (![self _copyPath:source toPath:destination handler:handler])
416 [self changeFileAttributes:attributes atPath:destination];
421 [handler fileManager:self willProcessPath:source];
422 if (![self _copyFile:source toFile:destination handler:handler])
425 [self changeFileAttributes:attributes atPath:destination];
433 - (BOOL)movePath:(NSString*)source toPath:(NSString*)destination
437 const char *sourcePath;
438 const char *destPath;
439 #if !defined(__MINGW32__)
440 NSString* destinationParent;
441 unsigned int sourceDevice, destinationDevice;
444 sourcePath = [self fileSystemRepresentationWithPath:source];
445 destPath = [self fileSystemRepresentationWithPath:destination];
447 if (![self fileExistsAtPath:source isDirectory:&sourceIsDir])
448 // source does not exist
451 if ([self fileExistsAtPath:destination])
452 // destination does already exist
455 #if defined(__MINGW32__)
457 Special handling for directories is required !
458 (See MoveFile on msdn)
460 if (!MoveFile(sourcePath, destPath)) {
462 NSDictionary *errorInfo
463 = [NSDictionary dictionaryWithObjectsAndKeys:
465 destination, @"ToPath",
466 @"cannot move file", @"Error",
468 if ([handler fileManager:self shouldProceedAfterError:errorInfo])
475 /* Check to see if the source and destination's parent are on the same
476 physical device so we can perform a rename syscall directly. */
477 sourceDevice = [[[self fileSystemAttributesAtPath:source]
478 objectForKey:NSFileSystemNumber]
480 destinationParent = [destination stringByDeletingLastPathComponent];
481 if ([destinationParent isEqual:@""])
482 destinationParent = @".";
484 = [[[self fileSystemAttributesAtPath:destinationParent]
485 objectForKey:NSFileSystemNumber]
488 if (sourceDevice != destinationDevice) {
489 /* If destination directory is a descendant of source directory moving
491 if (sourceIsDir && [[destination stringByAppendingString:@"/"]
492 hasPrefix:[source stringByAppendingString:@"/"]])
495 if ([self copyPath:source toPath:destination handler:handler]) {
496 NSDictionary* attributes;
498 attributes = [self fileAttributesAtPath:source traverseLink:NO];
499 [self changeFileAttributes:attributes atPath:destination];
500 return [self removeFileAtPath:source handler:handler];
506 /* source and destination are on the same device so we can simply
507 invoke rename on source. */
508 [handler fileManager:self willProcessPath:source];
509 if (rename (sourcePath, destPath) == -1) {
511 NSDictionary* errorInfo
512 = [NSDictionary dictionaryWithObjectsAndKeys:
514 destination, @"ToPath",
515 @"cannot move file", @"Error",
517 if ([handler fileManager:self
518 shouldProceedAfterError:errorInfo])
529 - (BOOL)linkPath:(NSString*)source toPath:(NSString*)destination
533 [self notImplemented:_cmd];
537 - (BOOL)removeFileAtPath:(NSString *)path
540 // TODO: this method should be cleaned up !!!
541 NSDirectoryEnumerator *enumerator;
543 NSString *completeFilename;
545 NSDictionary *attributes;
547 BOOL pathIsDir, fileExists;
548 CREATE_AUTORELEASE_POOL(pool);
551 [[InvalidArgumentException new] raise];
553 if ([path isEqual:@"."] || [path isEqual:@".."])
554 [[InvalidArgumentException new] raise];
556 fileExists = [self fileExistsAtPath:path isDirectory:&pathIsDir];
560 [handler fileManager:self willProcessPath:path];
563 cpath = [self fileSystemRepresentationWithPath:path];
565 #if defined(__MINGW32__)
566 if (DeleteFile(cpath) == FALSE)
568 if (unlink(cpath)) /* use unlink, we know it's a file */
572 NSDictionary *errorInfo;
574 [NSDictionary dictionaryWithObjectsAndKeys:
575 path ? path : @"<nil>", @"Path",
576 @"cannot remove file", @"Error",
578 if (![handler fileManager:self
579 shouldProceedAfterError:errorInfo])
582 /* intended fall-through ? [is this really correct?] */
591 enumerator = [self enumeratorAtPath:path];
592 while ((dirEntry = [enumerator nextObject])) {
593 attributes = [enumerator fileAttributes];
594 fileType = [attributes objectForKey:NSFileType];
595 completeFilename = [path stringByAppendingPathComponent:dirEntry];
597 if ([fileType isEqual:NSFileTypeDirectory]) {
598 /* Skip the descendants of this directory so they will not be
599 present in further steps. */
600 [enumerator skipDescendents];
603 if (![self removeFileAtPath:completeFilename handler:handler])
608 if (rmdir([self fileSystemRepresentationWithPath:path])) {
610 NSDictionary *errorInfo;
613 [NSDictionary dictionaryWithObjectsAndKeys:
614 path ? path : @"<nil>", @"Path",
615 @"cannot remove directory", @"Error",
617 if (![handler fileManager:self
618 shouldProceedAfterError:errorInfo])
631 - (BOOL)createFileAtPath:(NSString*)path contents:(NSData*)contents
632 attributes:(NSDictionary*)attributes
634 #if defined(__MINGW32__)
637 fh = CreateFile([self fileSystemRepresentationWithPath:path],
640 NULL, // security attributes
642 FILE_ATTRIBUTE_NORMAL,
644 if (fh == INVALID_HANDLE_VALUE)
647 DWORD len = [contents length];
651 WriteFile(fh, [contents bytes], len, &written, NULL);
654 if (![self changeFileAttributes:attributes atPath:path])
657 return written == len ? YES : NO;
660 int fd, len, written;
662 fd = open ([self fileSystemRepresentationWithPath:path],
663 O_WRONLY|O_TRUNC|O_CREAT, 0644);
667 if (![self changeFileAttributes:attributes atPath:path]) {
672 len = [contents length];
674 written = write (fd, [contents bytes], len);
679 return written == len;
683 // Getting and comparing file contents
685 - (NSData*)contentsAtPath:(NSString*)path
687 return [NSData dataWithContentsOfFile:path];
690 - (BOOL)contentsEqualAtPath:(NSString*)path1 andPath:(NSString*)path2
693 [self notImplemented:_cmd];
697 // Detemining access to files
699 - (BOOL)fileExistsAtPath:(NSString*)path
701 return [self fileExistsAtPath:path isDirectory:NULL];
704 #if defined(__MINGW32__)
705 - (BOOL)fileExistsAtPath:(NSString*)path isDirectory:(BOOL*)isDirectory
708 if (path == NULL) return NO;
709 result = GetFileAttributes([self fileSystemRepresentationWithPath:path]);
714 *isDirectory = (result & FILE_ATTRIBUTE_DIRECTORY) ? YES : NO;
718 - (BOOL)isReadableFileAtPath:(NSString*)path
721 if (path == NULL) return NO;
722 result = GetFileAttributes([self fileSystemRepresentationWithPath:path]);
727 - (BOOL)isWritableFileAtPath:(NSString*)path
730 if (path == NULL) return NO;
731 result = GetFileAttributes([self fileSystemRepresentationWithPath:path]);
735 return (result & FILE_ATTRIBUTE_READONLY) ? NO : YES;
737 - (BOOL)isExecutableFileAtPath:(NSString*)path
739 // naive, is there a better way ?
740 if ([self isReadableFileAtPath:path]) {
741 return [[path pathExtension] isEqualToString:@"exe"];
746 - (BOOL)isDeletableFileAtPath:(NSString*)path
748 // TODO - handle directories
749 return [self isWritableFileAtPath:path];
753 - (BOOL)fileExistsAtPath:(NSString*)path isDirectory:(BOOL*)isDirectory
756 const char* cpath = [self fileSystemRepresentationWithPath:path];
758 if (stat(cpath, &statbuf) != 0)
762 *isDirectory = ((statbuf.st_mode & S_IFMT) == S_IFDIR);
766 - (BOOL)isReadableFileAtPath:(NSString*)path
768 const char* cpath = [self fileSystemRepresentationWithPath:path];
770 return (access(cpath, R_OK) == 0);
773 - (BOOL)isWritableFileAtPath:(NSString*)path
775 const char* cpath = [self fileSystemRepresentationWithPath:path];
777 return (access(cpath, W_OK) == 0);
780 - (BOOL)isExecutableFileAtPath:(NSString*)path
782 const char* cpath = [self fileSystemRepresentationWithPath:path];
784 return (access(cpath, X_OK) == 0);
787 - (BOOL)isDeletableFileAtPath:(NSString*)path
789 // TODO - handle directories
792 cpath = [self fileSystemRepresentationWithPath:
793 [path stringByDeletingLastPathComponent]];
795 if (access(cpath, X_OK | W_OK) != 0)
798 cpath = [self fileSystemRepresentationWithPath:path];
800 return (access(cpath, X_OK | W_OK) == 0);
804 - (NSDictionary*)fileAttributesAtPath:(NSString*)path traverseLink:(BOOL)flag
807 const char* cpath = [self fileSystemRepresentationWithPath:path];
817 NSFileModificationDate,
818 NSFileOwnerAccountNumber,
819 NSFileGroupOwnerAccountNumber,
820 NSFileReferenceCount,
822 NSFileDeviceIdentifier,
823 NSFilePosixPermissions,
825 NSFileOwnerAccountName
831 if (stat(cpath, &statbuf) != 0)
835 /* do not traverseLink */
836 if (lstat(cpath, &statbuf) != 0)
840 if (stat(cpath, &statbuf) != 0)
844 values[0] = [NSNumber numberWithUnsignedLongLong:statbuf.st_size];
845 values[1] = [NSDate dateWithTimeIntervalSince1970:statbuf.st_mtime];
846 values[2] = [NSNumber numberWithUnsignedInt:statbuf.st_uid];
847 values[3] = [NSNumber numberWithUnsignedInt:statbuf.st_gid];
848 values[4] = [NSNumber numberWithUnsignedInt:statbuf.st_nlink];
849 values[5] = [NSNumber numberWithUnsignedLong:statbuf.st_ino];
850 values[6] = [NSNumber numberWithUnsignedInt:statbuf.st_dev];
851 values[7] = [NSNumber numberWithUnsignedInt:statbuf.st_mode];
853 mode = statbuf.st_mode & S_IFMT;
855 if (mode == S_IFREG) values[8] = NSFileTypeRegular;
856 else if (mode == S_IFDIR) values[8] = NSFileTypeDirectory;
857 else if (mode == S_IFCHR) values[8] = NSFileTypeCharacterSpecial;
858 else if (mode == S_IFBLK) values[8] = NSFileTypeBlockSpecial;
860 else if (mode == S_IFLNK) values[8] = NSFileTypeSymbolicLink;
862 else if (mode == S_IFIFO) values[8] = NSFileTypeFifo;
864 else if (mode == S_IFSOCK) values[8] = NSFileTypeSocket;
866 else values[8] = NSFileTypeUnknown;
870 pw = getpwuid(statbuf.st_uid);
873 values[count] = [NSString stringWithCString:pw->pw_name];
878 return AUTORELEASE([[NSDictionary alloc]
879 initWithObjects:values forKeys:keys count:count]);
882 - (NSDictionary*)fileSystemAttributesAtPath:(NSString*)path
884 #if HAVE_SYS_VFS_H || HAVE_SYS_STATFS_H
887 struct statvfs statfsbuf;
889 struct statfs statfsbuf;
891 long long totalsize, freesize;
892 const char* cpath = [self fileSystemRepresentationWithPath:path];
897 NSFileSystemFreeSize,
899 NSFileSystemFreeNodes,
903 if (stat(cpath, &statbuf) != 0)
907 if (statvfs(cpath, &statfsbuf) != 0)
910 if (statfs(cpath, &statfsbuf) != 0)
914 totalsize = statfsbuf.f_bsize * statfsbuf.f_blocks;
915 freesize = statfsbuf.f_bsize * statfsbuf.f_bfree;
917 values[0] = [NSNumber numberWithLongLong:totalsize];
918 values[1] = [NSNumber numberWithLongLong:freesize];
919 values[2] = [NSNumber numberWithLong:statfsbuf.f_files];
920 values[3] = [NSNumber numberWithLong:statfsbuf.f_ffree];
921 values[4] = [NSNumber numberWithUnsignedInt:statbuf.st_dev];
923 return AUTORELEASE([[NSDictionary alloc]
924 initWithObjects:values forKeys:keys count:5]);
930 - (BOOL)changeFileAttributes:(NSDictionary*)attributes atPath:(NSString*)path
932 const char* cpath = [self fileSystemRepresentationWithPath:path];
938 num = [attributes objectForKey:NSFileOwnerAccountNumber];
940 allOk &= (chown(cpath, [num intValue], -1) == 0);
943 num = [attributes objectForKey:NSFileGroupOwnerAccountNumber];
945 allOk &= (chown(cpath, -1, [num intValue]) == 0);
949 num = [attributes objectForKey:NSFilePosixPermissions];
951 allOk &= (chmod(cpath, [num intValue]) == 0);
954 date = [attributes objectForKey:NSFileModificationDate];
957 #if defined(_POSIX_VERSION) || defined(__WIN32__)
963 if (stat(cpath, &sb) != 0)
966 #if defined(_POSIX_VERSION) || defined(__WIN32__)
967 ub.actime = sb.st_atime;
968 ub.modtime = [date timeIntervalSince1970];
969 allOk &= (utime(cpath, &ub) == 0);
972 ub[1] = [date timeIntervalSince1970];
973 allOk &= (utime((char*)cpath, ub) == 0);
981 // Discovering directory contents
983 - (NSArray *)directoryContentsAtPath:(NSString *)path
985 NSDirectoryEnumerator* direnum;
986 NSMutableArray* content;
989 if (![self fileExistsAtPath:path isDirectory:&isDir] || !isDir)
992 direnum = [[NSDirectoryEnumerator alloc]
993 initWithDirectoryPath:path
994 recurseIntoSubdirectories:NO
997 content = AUTORELEASE([[NSMutableArray alloc] init]);
999 while ((path = [direnum nextObject]))
1000 [content addObject:path];
1002 RELEASE(direnum); direnum = nil;
1007 - (NSDirectoryEnumerator*)enumeratorAtPath:(NSString*)path
1009 return AUTORELEASE([[NSDirectoryEnumerator alloc]
1010 initWithDirectoryPath:path
1011 recurseIntoSubdirectories:YES
1016 - (NSArray*)subpathsAtPath:(NSString*)path
1018 NSDirectoryEnumerator* direnum;
1019 NSMutableArray* content;
1022 if (![self fileExistsAtPath:path isDirectory:&isDir] || !isDir)
1025 direnum = [[NSDirectoryEnumerator alloc]
1026 initWithDirectoryPath:path
1027 recurseIntoSubdirectories:YES
1030 content = AUTORELEASE([[NSMutableArray alloc] init]);
1032 while ((path = [direnum nextObject]))
1033 [content addObject:path];
1035 RELEASE(direnum); direnum = nil;
1040 // Symbolic-link operations
1042 - (BOOL)createSymbolicLinkAtPath:(NSString*)path
1043 pathContent:(NSString*)otherPath
1046 const char* lpath = [self fileSystemRepresentationWithPath:path];
1047 const char* npath = [self fileSystemRepresentationWithPath:otherPath];
1049 return (symlink(lpath, npath) == 0);
1051 [[InvalidArgumentException new] raise];
1056 - (NSString*)pathContentOfSymbolicLinkAtPath:(NSString*)path
1059 char lpath[PATH_MAX];
1060 const char* cpath = [self fileSystemRepresentationWithPath:path];
1061 int llen = readlink(cpath, lpath, PATH_MAX-1);
1064 return [self stringWithFileSystemRepresentation:lpath length:llen];
1070 // Converting file-system representations
1072 - (const char*)fileSystemRepresentationWithPath:(NSString*)path
1074 return [path cString];
1077 - (NSString*)stringWithFileSystemRepresentation:(const char*)string
1078 length:(unsigned int)len
1080 return [NSString stringWithCString:string length:len];
1083 @end /* NSFileManager */
1086 * NSDirectoryEnumerator implementation
1089 @implementation NSDirectoryEnumerator
1091 #if defined(__MINGW32__)
1093 typedef struct _MingDIR {
1095 WIN32_FIND_DATA info;
1099 static inline MingDIR *ming_opendir(const char *cstr) {
1105 result = GetFileAttributes(cstr);
1106 if (result == 0xFFFFFFFF) {
1107 NSLog(@"ERROR: could not get file attributes of path '%s'", cstr);
1111 if (result & FILE_ATTRIBUTE_DIRECTORY) {
1112 MingDIR *dir = objc_atomic_malloc(sizeof(MingDIR));
1113 int len = strlen(cstr);
1114 char *buf = objc_atomic_malloc(len + 10);
1118 if (buf[len - 1] == '\\')
1127 dir->handle = FindFirstFile(buf, &(dir->info));
1128 objc_free(buf); buf = NULL;
1129 if (dir->handle == INVALID_HANDLE_VALUE) {
1130 objc_free(dir); dir = NULL;
1137 NSLog(@"ERROR: path '%s' is not a directory !", cstr);
1141 static inline void ming_closedir(MingDIR *dir) {
1143 if (dir->handle != INVALID_HANDLE_VALUE) {
1144 FindClose(dir->handle);
1145 dir->handle = INVALID_HANDLE_VALUE;
1151 static inline WIN32_FIND_DATA *ming_readdir(MingDIR *dir) {
1152 if (dir->dirCount == 0) {
1155 return &(dir->info);
1157 else if (dir->handle != INVALID_HANDLE_VALUE) {
1158 if (FindNextFile(dir->handle, &(dir->info)) == FALSE) {
1159 FindClose(dir->handle);
1160 dir->handle = INVALID_HANDLE_VALUE;
1164 return &(dir->info);
1166 else // directory closed
1172 // Implementation dependent methods
1175 recurses into directory `path'
1176 - pushes relative path (relative to root of search) on pathStack
1177 - pushes system dir enumerator on enumPath
1179 - (void)recurseIntoDirectory:(NSString*)path relativeName:(NSString*)name
1182 #if defined(__MINGW32__)
1188 cpath = [[NSFileManager defaultManager]
1189 fileSystemRepresentationWithPath:path];
1192 [[InvalidArgumentException new] raise];
1194 #if defined(__MINGW32__)
1195 if ((dir = ming_opendir(cpath))) {
1196 [pathStack addObject:name];
1197 [enumStack addObject:[NSValue valueWithPointer:dir]];
1200 if ((dir = opendir(cpath))) {
1201 [pathStack addObject:name];
1202 [enumStack addObject:[NSValue valueWithPointer:dir]];
1205 [[InvalidArgumentException new] raise];
1210 backtracks enumeration to the previous dir
1211 - pops current dir relative path from pathStack
1212 - pops system dir enumerator from enumStack
1213 - sets currentFile* to nil
1217 #if defined(__MINGW32__)
1218 ming_closedir((MingDIR *)[[enumStack lastObject] pointerValue]);
1220 closedir((DIR *)[[enumStack lastObject] pointerValue]);
1222 [[InvalidArgumentException new] raise];
1224 [enumStack removeLastObject];
1225 [pathStack removeLastObject];
1226 RELEASE(currentFileName); currentFileName = nil;
1227 RELEASE(currentFilePath); currentFilePath = nil;
1231 finds the next file according to the top enumerator
1232 - if there is a next file it is put in currentFile
1233 - if the current file is a directory and if isRecursive calls
1234 recurseIntoDirectory:currentFile
1235 - if the current file is a symlink to a directory and if isRecursive
1236 and isFollowing calls recurseIntoDirectory:currentFile
1237 - if at end of current directory pops stack and attempts to
1238 find the next entry in the parent
1239 - sets currentFile to nil if there are no more files to enumerate
1241 - (void)findNextFile
1243 NSFileManager *manager = [NSFileManager defaultManager];
1244 #if defined(__MINGW32__)
1246 WIN32_FIND_DATA *dirbuf;
1248 DIR_enum_state *dir;
1249 DIR_enum_item *dirbuf;
1251 #if defined(__MINGW32__)
1252 DWORD fileAttributes;
1254 struct stat statbuf;
1258 RELEASE(self->currentFileName); self->currentFileName = nil;
1259 RELEASE(self->currentFilePath); self->currentFilePath = nil;
1261 while ([self->pathStack count]) {
1262 #if defined(__MINGW32__)
1263 dir = (MingDIR *)[[self->enumStack lastObject] pointerValue];
1264 dirbuf = ming_readdir(dir);
1266 if (dirbuf == NULL) {
1267 // If we reached the end of this directory, go back to the upper one
1271 /* Skip "." and ".." directory entries */
1272 if (Strcmp(dirbuf->cFileName, ".") == 0 ||
1273 Strcmp(dirbuf->cFileName, "..") == 0)
1276 self->currentFileName = [manager
1277 stringWithFileSystemRepresentation:dirbuf->cFileName
1278 length:Strlen(dirbuf->cFileName)];
1280 dir = (DIR*)[[enumStack lastObject] pointerValue];
1281 dirbuf = readdir(dir);
1283 /* If we reached the end of this directory, go back to the upper one */
1284 if (dirbuf == NULL) {
1289 /* Skip "." and ".." directory entries */
1290 if (Strcmp(dirbuf->d_name, ".") == 0 ||
1291 Strcmp(dirbuf->d_name, "..") == 0)
1293 // Name of current file
1294 self->currentFileName = [manager
1295 stringWithFileSystemRepresentation:dirbuf->d_name
1296 length:Strlen(dirbuf->d_name)];
1298 [[InvalidArgumentException new] raise];
1300 self->currentFileName
1301 = RETAIN([[pathStack lastObject]
1302 stringByAppendingPathComponent:self->currentFileName]);
1304 // Full path of current file
1305 self->currentFilePath
1306 = RETAIN([self->topPath stringByAppendingPathComponent:
1307 self->currentFileName]);
1309 // Check if directory
1310 cpath = [manager fileSystemRepresentationWithPath:currentFilePath];
1312 // Do not follow links
1314 if (!flags.isFollowing) {
1315 if (lstat(cpath, &statbuf) < 0) {
1316 NSLog (@"cannot lstat file '%s'", cpath);
1319 // If link then return it as link
1320 if (S_IFLNK == (S_IFMT & statbuf.st_mode))
1325 #if defined(__MINGW32__)
1326 if ((fileAttributes = GetFileAttributes(cpath)) == 0xFFFFFFFF)
1327 // could not get file attributes
1330 if (self->flags.isRecursive) {
1331 if (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1332 [self recurseIntoDirectory:self->currentFilePath
1333 relativeName:self->currentFileName];
1337 // Follow links - check for directory
1338 if (stat(cpath, &statbuf) < 0)
1339 // could not stat file
1342 if (S_IFDIR == (S_IFMT & statbuf.st_mode) && self->flags.isRecursive) {
1343 [self recurseIntoDirectory:self->currentFilePath
1344 relativeName:self->currentFileName];
1353 - (id)initWithDirectoryPath:(NSString*)path
1354 recurseIntoSubdirectories:(BOOL)recurse
1355 followSymlinks:(BOOL)follow
1356 prefixFiles:(BOOL)prefix
1358 self->pathStack = [[NSMutableArray allocWithZone:[self zone]] init];
1359 self->enumStack = [[NSMutableArray allocWithZone:[self zone]] init];
1360 self->flags.isRecursive = recurse;
1361 self->flags.isFollowing = follow;
1363 self->topPath = [path copyWithZone:[self zone]];
1364 [self recurseIntoDirectory:path relativeName:@""];
1371 while ([self->pathStack count])
1374 RELEASE(self->pathStack);
1375 RELEASE(self->enumStack);
1376 RELEASE(self->currentFileName);
1377 RELEASE(self->currentFilePath);
1378 RELEASE(self->topPath);
1383 // Getting attributes
1385 - (NSDictionary *)directoryAttributes
1387 return [[NSFileManager defaultManager]
1388 fileAttributesAtPath:self->currentFilePath
1389 traverseLink:self->flags.isFollowing];
1392 - (NSDictionary*)fileAttributes
1394 return [[NSFileManager defaultManager]
1395 fileAttributesAtPath:self->currentFilePath
1396 traverseLink:self->flags.isFollowing];
1399 // Skipping subdirectories
1401 - (void)skipDescendents
1403 if ([self->pathStack count])
1411 [self findNextFile];
1412 return self->currentFileName;
1415 @end /* NSDirectoryEnumerator */
1418 * Attributes dictionary access
1421 @implementation NSDictionary(NSFileAttributes)
1423 - (NSNumber *)fileSize
1425 return [self objectForKey:NSFileSize];
1427 - (NSString *)fileType;
1429 return [self objectForKey:NSFileType];
1431 - (NSNumber *)fileOwnerAccountNumber;
1433 return [self objectForKey:NSFileOwnerAccountNumber];
1435 - (NSNumber *)fileGroupOwnerAccountNumber;
1437 return [self objectForKey:NSFileGroupOwnerAccountNumber];
1439 - (NSDate *)fileModificationDate;
1441 return [self objectForKey:NSFileModificationDate];
1443 - (NSNumber *)filePosixPermissions;
1445 return [self objectForKey:NSFilePosixPermissions];
1450 * File attributes names
1453 /* File Attributes */
1455 LF_DECLARE NSString *NSFileSize = @"NSFileSize";
1456 LF_DECLARE NSString *NSFileModificationDate = @"NSFileModificationDate";
1457 LF_DECLARE NSString *NSFileOwnerAccountNumber = @"NSFileOwnerAccountNumber";
1458 LF_DECLARE NSString *NSFileOwnerAccountName = @"NSFileOwnerAccountName";
1459 LF_DECLARE NSString *NSFileGroupOwnerAccountNumber = @"NSFileGroupOwnerAccountNumber";
1460 LF_DECLARE NSString *NSFileGroupOwnerAccountName = @"NSFileGroupOwnerAccountName";
1461 LF_DECLARE NSString *NSFileReferenceCount = @"NSFileReferenceCount";
1462 LF_DECLARE NSString *NSFileIdentifier = @"NSFileIdentifier";
1463 LF_DECLARE NSString *NSFileDeviceIdentifier = @"NSFileDeviceIdentifier";
1464 LF_DECLARE NSString *NSFilePosixPermissions = @"NSFilePosixPermissions";
1465 LF_DECLARE NSString *NSFileType = @"NSFileType";
1469 LF_DECLARE NSString *NSFileTypeDirectory = @"NSFileTypeDirectory";
1470 LF_DECLARE NSString *NSFileTypeRegular = @"NSFileTypeRegular";
1471 LF_DECLARE NSString *NSFileTypeSymbolicLink = @"NSFileTypeSymbolicLink";
1472 LF_DECLARE NSString *NSFileTypeSocket = @"NSFileTypeSocket";
1473 LF_DECLARE NSString *NSFileTypeFifo = @"NSFileTypeFifo";
1474 LF_DECLARE NSString *NSFileTypeCharacterSpecial = @"NSFileTypeCharacterSpecial";
1475 LF_DECLARE NSString *NSFileTypeBlockSpecial = @"NSFileTypeBlockSpecial";
1476 LF_DECLARE NSString *NSFileTypeUnknown = @"NSFileTypeUnknown";
1478 /* FileSystem Attributes */
1480 LF_DECLARE NSString *NSFileSystemFileNumber = @"NSFileSystemFileNumber";
1481 LF_DECLARE NSString *NSFileSystemSize = @"NSFileSystemSize";
1482 LF_DECLARE NSString *NSFileSystemFreeSize = @"NSFileSystemFreeSize";
1483 LF_DECLARE NSString *NSFileSystemNodes = @"NSFileSystemNodes";
1484 LF_DECLARE NSString *NSFileSystemFreeNodes = @"NSFileSystemFreeNodes";
1485 LF_DECLARE NSString *NSFileSystemNumber = @"NSFileSystemNumber";
1487 @implementation NSFileManager (PrivateMethods)
1489 - (BOOL)_copyFile:(NSString*)source toFile:(NSString*)destination
1492 NSDictionary* attributes;
1493 int i, bufsize = 8096;
1494 int sourceFd, destFd, fileSize, fileMode;
1496 char buffer[bufsize];
1498 /* Assumes source is a file and exists! */
1499 NSAssert1 ([self fileExistsAtPath:source],
1500 @"source file '%@' does not exist!", source);
1502 attributes = [self fileAttributesAtPath:source traverseLink:NO];
1503 NSAssert1 (attributes, @"could not get the attributes for file '%@'",
1506 fileSize = [[attributes objectForKey:NSFileSize] intValue];
1507 fileMode = [[attributes objectForKey:NSFilePosixPermissions] intValue];
1509 /* Open the source file. In case of error call the handler. */
1510 sourceFd = open([self fileSystemRepresentationWithPath:source], O_RDONLY, 0);
1513 NSDictionary* errorInfo
1514 = [NSDictionary dictionaryWithObjectsAndKeys:
1516 @"cannot open file for reading", @"Error",
1518 return [handler fileManager:self
1519 shouldProceedAfterError:errorInfo];
1525 /* Open the destination file. In case of error call the handler. */
1526 destFd = open([self fileSystemRepresentationWithPath:destination],
1527 O_WRONLY|O_CREAT|O_TRUNC, fileMode);
1530 NSDictionary* errorInfo
1531 = [NSDictionary dictionaryWithObjectsAndKeys:
1532 destination, @"ToPath",
1533 @"cannot open file for writing", @"Error",
1536 return [handler fileManager:self
1537 shouldProceedAfterError:errorInfo];
1543 /* Read bufsize bytes from source file and write them into the destination
1544 file. In case of errors call the handler and abort the operation. */
1545 for (i = 0; i < fileSize; i += rbytes) {
1546 rbytes = read (sourceFd, buffer, bufsize);
1549 NSDictionary* errorInfo
1550 = [NSDictionary dictionaryWithObjectsAndKeys:
1552 @"cannot read from file", @"Error",
1556 return [handler fileManager:self
1557 shouldProceedAfterError:errorInfo];
1563 wbytes = write (destFd, buffer, rbytes);
1564 if (wbytes != rbytes) {
1566 NSDictionary* errorInfo
1567 = [NSDictionary dictionaryWithObjectsAndKeys:
1569 destination, @"ToPath",
1570 @"cannot write to file", @"Error",
1574 return [handler fileManager:self
1575 shouldProceedAfterError:errorInfo];
1587 - (BOOL)_copyPath:(NSString*)source
1588 toPath:(NSString*)destination
1591 NSDirectoryEnumerator* enumerator;
1593 NSString* sourceFile;
1595 NSString* destinationFile;
1596 NSDictionary* attributes;
1597 CREATE_AUTORELEASE_POOL(pool);
1599 enumerator = [self enumeratorAtPath:source];
1600 while ((dirEntry = [enumerator nextObject])) {
1601 attributes = [enumerator fileAttributes];
1602 fileType = [attributes objectForKey:NSFileType];
1603 sourceFile = [source stringByAppendingPathComponent:dirEntry];
1605 = [destination stringByAppendingPathComponent:dirEntry];
1607 [handler fileManager:self willProcessPath:sourceFile];
1608 if ([fileType isEqual:NSFileTypeDirectory]) {
1609 if (![self createDirectoryAtPath:destinationFile
1610 attributes:attributes]) {
1612 NSDictionary* errorInfo
1613 = [NSDictionary dictionaryWithObjectsAndKeys:
1614 destinationFile, @"Path",
1615 @"cannot create directory", @"Error",
1617 if (![handler fileManager:self
1618 shouldProceedAfterError:errorInfo])
1625 [enumerator skipDescendents];
1626 if (![self _copyPath:sourceFile toPath:destinationFile
1631 else if ([fileType isEqual:NSFileTypeRegular]) {
1632 if (![self _copyFile:sourceFile toFile:destinationFile
1636 else if ([fileType isEqual:NSFileTypeSymbolicLink]) {
1637 if (![self createSymbolicLinkAtPath:destinationFile
1638 pathContent:sourceFile]) {
1640 NSDictionary* errorInfo
1641 = [NSDictionary dictionaryWithObjectsAndKeys:
1642 sourceFile, @"Path",
1643 destinationFile, @"ToPath",
1644 @"cannot create symbolic link", @"Error",
1646 if (![handler fileManager:self
1647 shouldProceedAfterError:errorInfo])
1655 NSLog(@"cannot copy file '%@' of type '%@'", sourceFile, fileType);
1657 [self changeFileAttributes:attributes atPath:destinationFile];
1664 @end /* NSFileManager (PrivateMethods) */