/* NSFileManager.m Copyright (C) 1997-1999 Free Software Foundation, Inc. Author: Mircea Oancea Author: Ovidiu Predescu Date: Feb 1997 Updates and fixes: Richard Frith-Macdonald This file is part of the GNUstep Base Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include /* determine directory reading files */ #if defined(HAVE_DIRENT_H) # include #elif defined(HAVE_SYS_DIR_H) # include #elif defined(HAVE_SYS_NDIR_H) # include #elif defined(HAVE_NDIR_H) # include #endif #include #if !defined(_POSIX_VERSION) # if defined(NeXT) # define DIR_enum_item struct direct # endif #endif #if !defined(DIR_enum_item) # define DIR_enum_item struct dirent #endif #define DIR_enum_state DIR #if defined(__WIN32__) #define WIN32ERR ((DWORD)0xFFFFFFFF) #endif /* determine filesystem max path length */ #ifdef _POSIX_VERSION # include /* for PATH_MAX */ # include #else #ifdef __WIN32__ # include # include #else # include /* for MAXPATHLEN */ #endif #endif #ifndef PATH_MAX # ifdef _POSIX_VERSION # define PATH_MAX _POSIX_PATH_MAX # else # ifdef MAXPATHLEN # define PATH_MAX MAXPATHLEN # else # define PATH_MAX 1024 # endif # endif #endif /* determine if we have statfs struct and function */ #ifdef HAVE_SYS_VFS_H # include # ifdef HAVE_SYS_STATVFS_H # include # endif #endif #ifdef HAVE_SYS_STATFS_H # include #endif #if HAVE_SYS_FILE_H #include #endif #if HAVE_SYS_MOUNT_H #include #endif #include #ifdef HAVE_SYS_STAT_H #include #endif #include #if HAVE_PWD_H #include /* For struct passwd */ #endif #if HAVE_GRP_H #include /* For struct group */ #endif #if HAVE_UTIME_H # include #endif /* include usual headers */ #include #include #include #include #include #include #include #include @interface NSFileManager (PrivateMethods) /* Copies the contents of source file to destination file. Assumes source and destination are regular files or symbolic links. */ - (BOOL) _copyFile: (NSString*)source toFile: (NSString*)destination handler: (id)handler; /* Recursively copies the contents of source directory to destination. */ - (BOOL) _copyPath: (NSString*)source toPath: (NSString*)destination handler: (id)handler; - (NSDictionary*) _attributesAtPath: (NSString*)path traverseLink: (BOOL)traverse forCopy: (BOOL)copy; @end /* NSFileManager (PrivateMethods) */ @interface NSDirectoryEnumerator (PrivateMethods) - (NSDictionary*) _attributesForCopy; @end /* * NSFileManager implementation */ @implementation NSFileManager // Getting the default manager static NSFileManager* defaultManager = nil; + (NSFileManager*) defaultManager { if (!defaultManager) { NS_DURING { [gnustep_global_lock lock]; defaultManager = [[self alloc] init]; [gnustep_global_lock unlock]; } NS_HANDLER { // unlock then re-raise the exception [gnustep_global_lock unlock]; [localException raise]; } NS_ENDHANDLER } return defaultManager; } - (void) dealloc { TEST_RELEASE(lastError); [super dealloc]; } // Directory operations - (BOOL) changeCurrentDirectoryPath: (NSString*)path { const char* cpath = [self fileSystemRepresentationWithPath: path]; #if defined(__WIN32__) return SetCurrentDirectory(cpath); #else return (chdir(cpath) == 0); #endif } - (BOOL) createDirectoryAtPath: (NSString*)path attributes: (NSDictionary*)attributes { #if defined(__WIN32__) BOOL ok; ok = CreateDirectory([self fileSystemRepresentationWithPath: path], NULL); if (ok == NO) ASSIGN(lastError, @"Could not create directory"); return ok; #else const char *cpath; char dirpath[PATH_MAX+1]; struct stat statbuf; int len, cur; NSDictionary *needChown = nil; /* * If there is no file owner specified, and we are running setuid to * root, then we assume we need to change ownership to correct user. */ if ([attributes objectForKey: NSFileOwnerAccountName] == nil && [attributes objectForKey: NSFileOwnerAccountNumber] == nil && geteuid() == 0 && [@"root" isEqualToString: NSUserName()] == NO) { needChown = [NSDictionary dictionaryWithObjectsAndKeys: NSFileOwnerAccountName, NSUserName(), nil]; } cpath = [self fileSystemRepresentationWithPath: path]; len = strlen(cpath); if (len > PATH_MAX) // name too long { ASSIGN(lastError, @"Could not create directory - name too long"); return NO; } if (strcmp(cpath, "/") == 0 || len == 0) // cannot use "/" or "" { ASSIGN(lastError, @"Could not create directory - no name given"); return NO; } strcpy(dirpath, cpath); dirpath[len] = '\0'; if (dirpath[len-1] == '/') dirpath[len-1] = '\0'; cur = 0; do { // find next '/' while (dirpath[cur] != '/' && cur < len) cur++; // if first char is '/' then again; (cur == len) -> last component if (cur == 0) { cur++; continue; } // check if path from 0 to cur is valid dirpath[cur] = '\0'; if (stat(dirpath, &statbuf) == 0) { if (cur == len) { ASSIGN(lastError, @"Could not create directory - already exists"); return NO; } } else { // make new directory if (mkdir(dirpath, 0777) != 0) { NSString *s; s = [NSString stringWithFormat: @"Could not create '%s' - '%s'", dirpath, strerror(errno)]; ASSIGN(lastError, s); return NO; } // if last directory and attributes then change if (cur == len && attributes) { if ([self changeFileAttributes: attributes atPath: [self stringWithFileSystemRepresentation: dirpath length: cur]] == NO) return NO; if (needChown) { if ([self changeFileAttributes: needChown atPath: [self stringWithFileSystemRepresentation: dirpath length: cur]] == NO) { NSLog(@"Failed to change ownership of '%s' to '%@'", dirpath, NSUserName()); } } return YES; } } dirpath[cur] = '/'; cur++; } while (cur < len); return YES; #endif /* WIN32 */ } - (NSString*) currentDirectoryPath { char path[PATH_MAX]; #if defined(__WIN32__) if (GetCurrentDirectory(PATH_MAX, path) > PATH_MAX) return nil; #else #ifdef HAVE_GETCWD if (getcwd(path, PATH_MAX-1) == NULL) return nil; #else if (getwd(path) == NULL) return nil; #endif /* HAVE_GETCWD */ #endif /* WIN32 */ return [self stringWithFileSystemRepresentation: path length: strlen(path)]; } // File operations - (BOOL) copyPath: (NSString*)source toPath: (NSString*)destination handler: handler { BOOL sourceIsDir; BOOL fileExists; NSDictionary *attributes; fileExists = [self fileExistsAtPath: source isDirectory: &sourceIsDir]; if (!fileExists) return NO; fileExists = [self fileExistsAtPath: destination]; if (fileExists) return NO; attributes = [self _attributesAtPath: source traverseLink: NO forCopy: YES]; if (sourceIsDir) { /* If destination directory is a descendant of source directory copying isn't possible. */ if ([[destination stringByAppendingString: @"/"] hasPrefix: [source stringByAppendingString: @"/"]]) return NO; [handler fileManager: self willProcessPath: destination]; if (![self createDirectoryAtPath: destination attributes: attributes]) { if (handler) { NSDictionary* errorInfo = [NSDictionary dictionaryWithObjectsAndKeys: destination, @"Path", lastError, @"Error", nil]; return [handler fileManager: self shouldProceedAfterError: errorInfo]; } else return NO; } } if (sourceIsDir) { if (![self _copyPath: source toPath: destination handler: handler]) return NO; else { [self changeFileAttributes: attributes atPath: destination]; return YES; } } else { [handler fileManager: self willProcessPath: source]; if (![self _copyFile: source toFile: destination handler: handler]) return NO; else { [self changeFileAttributes: attributes atPath: destination]; return YES; } } return NO; } - (BOOL) movePath: (NSString*)source toPath: (NSString*)destination handler: handler { BOOL sourceIsDir, fileExists; const char* sourcePath = [self fileSystemRepresentationWithPath: source]; const char* destPath = [self fileSystemRepresentationWithPath: destination]; NSString* destinationParent; unsigned int sourceDevice, destinationDevice; fileExists = [self fileExistsAtPath: source isDirectory: &sourceIsDir]; if (!fileExists) return NO; fileExists = [self fileExistsAtPath: destination]; if (fileExists) return NO; /* Check to see if the source and destination's parent are on the same physical device so we can perform a rename syscall directly. */ sourceDevice = [[[self fileSystemAttributesAtPath: source] objectForKey: NSFileSystemNumber] unsignedIntValue]; destinationParent = [destination stringByDeletingLastPathComponent]; if ([destinationParent isEqual: @""]) destinationParent = @"."; destinationDevice = [[[self fileSystemAttributesAtPath: destinationParent] objectForKey: NSFileSystemNumber] unsignedIntValue]; if (sourceDevice != destinationDevice) { /* If destination directory is a descendant of source directory moving isn't possible. */ if (sourceIsDir && [[destination stringByAppendingString: @"/"] hasPrefix: [source stringByAppendingString: @"/"]]) return NO; if ([self copyPath: source toPath: destination handler: handler]) { NSDictionary* attributes; attributes = [self _attributesAtPath: source traverseLink: NO forCopy: YES]; [self changeFileAttributes: attributes atPath: destination]; return [self removeFileAtPath: source handler: handler]; } else return NO; } else { /* source and destination are on the same device so we can simply invoke rename on source. */ [handler fileManager: self willProcessPath: source]; if (rename (sourcePath, destPath) == -1) { if (handler) { NSDictionary* errorInfo = [NSDictionary dictionaryWithObjectsAndKeys: source, @"Path", destination, @"ToPath", @"cannot move file", @"Error", nil]; if ([handler fileManager: self shouldProceedAfterError: errorInfo]) return YES; } return NO; } return YES; } return NO; } - (BOOL) linkPath: (NSString*)source toPath: (NSString*)destination handler: handler { // TODO [self notImplemented: _cmd]; return NO; } - (BOOL) removeFileAtPath: (NSString*)path handler: handler { NSArray *contents; if (handler) [handler fileManager: self willProcessPath: path]; contents = [self directoryContentsAtPath: path]; if (contents == nil) { if (unlink([path fileSystemRepresentation]) < 0) { BOOL result; if (handler) { NSMutableDictionary *info; info = [[NSMutableDictionary alloc] initWithCapacity: 3]; [info setObject: path forKey: @"Path"]; [info setObject: [NSString stringWithCString: strerror(errno)] forKey: @"Error"]; result = [handler fileManager: self shouldProceedAfterError: info]; RELEASE(info); } else result = NO; return result; } else return YES; } else { unsigned count = [contents count]; unsigned i; for (i = 0; i < count; i++) { CREATE_AUTORELEASE_POOL(arp); NSString *item; NSString *next; BOOL result; item = [contents objectAtIndex: i]; next = [path stringByAppendingPathComponent: item]; result = [self removeFileAtPath: next handler: handler]; RELEASE(arp); if (result == NO) return NO; } if (rmdir([path fileSystemRepresentation]) < 0) { BOOL result; if (handler) { NSMutableDictionary *info; info = [[NSMutableDictionary alloc] initWithCapacity: 3]; [info setObject: path forKey: @"Path"]; [info setObject: [NSString stringWithCString: strerror(errno)] forKey: @"Error"]; result = [handler fileManager: self shouldProceedAfterError: info]; RELEASE(info); } else result = NO; return result; } else return YES; } } - (BOOL) createFileAtPath: (NSString*)path contents: (NSData*)contents attributes: (NSDictionary*)attributes { int fd, len, written; fd = open ([self fileSystemRepresentationWithPath: path], O_WRONLY|O_TRUNC|O_CREAT, 0644); if (fd < 0) return NO; if (![self changeFileAttributes: attributes atPath: path]) { close (fd); return NO; } /* * If there is no file owner specified, and we are running setuid to * root, then we assume we need to change ownership to correct user. */ if ([attributes objectForKey: NSFileOwnerAccountName] == nil && [attributes objectForKey: NSFileOwnerAccountNumber] == nil && geteuid() == 0 && [@"root" isEqualToString: NSUserName()] == NO) { attributes = [NSDictionary dictionaryWithObjectsAndKeys: NSFileOwnerAccountName, NSUserName(), nil]; if (![self changeFileAttributes: attributes atPath: path]) { NSLog(@"Failed to change ownership of '%@' to '%@'", path, NSUserName()); } } len = [contents length]; if (len) written = write (fd, [contents bytes], len); else written = 0; close (fd); return written == len; } // Getting and comparing file contents - (NSData*) contentsAtPath: (NSString*)path { return [NSData dataWithContentsOfFile: path]; } - (BOOL) contentsEqualAtPath: (NSString*)path1 andPath: (NSString*)path2 { NSDictionary *d1; NSDictionary *d2; NSString *t; if ([path1 isEqual: path2]) return YES; d1 = [self fileAttributesAtPath: path1 traverseLink: NO]; d2 = [self fileAttributesAtPath: path2 traverseLink: NO]; t = [d1 objectForKey: NSFileType]; if ([t isEqual: [d2 objectForKey: NSFileType]] == NO) return NO; if ([t isEqual: NSFileTypeRegular]) { id s1 = [d1 objectForKey: NSFileSize]; id s2 = [d2 objectForKey: NSFileSize]; if ([s1 isEqual: s2] == YES) { NSData *c1 = [NSData dataWithContentsOfFile: path1]; NSData *c2 = [NSData dataWithContentsOfFile: path2]; if ([c1 isEqual: c2]) return YES; } return NO; } else if ([t isEqual: NSFileTypeDirectory]) { NSArray *a1 = [self directoryContentsAtPath: path1]; NSArray *a2 = [self directoryContentsAtPath: path2]; unsigned index; BOOL ok = YES; if ([a1 isEqual: a2] == NO) return NO; for (index = 0; ok == YES && index < [a1 count]; index++) { CREATE_AUTORELEASE_POOL(pool); NSString *n = [a1 objectAtIndex: index]; NSString *p1; NSString *p2; p1 = [path1 stringByAppendingPathComponent: n]; p2 = [path2 stringByAppendingPathComponent: n]; d1 = [self fileAttributesAtPath: p1 traverseLink: NO]; d2 = [self fileAttributesAtPath: p2 traverseLink: NO]; t = [d1 objectForKey: NSFileType]; if ([t isEqual: [d2 objectForKey: NSFileType]] == NO) { ok = NO; } else if ([t isEqual: NSFileTypeDirectory]) { ok = [self contentsEqualAtPath: p1 andPath: p2]; } RELEASE(pool); } return ok; } else return YES; } // Determining access to files - (BOOL) fileExistsAtPath: (NSString*)path { return [self fileExistsAtPath: path isDirectory: NULL]; } - (BOOL) fileExistsAtPath: (NSString*)path isDirectory: (BOOL*)isDirectory { const char* cpath = [self fileSystemRepresentationWithPath: path]; if (cpath == 0 || *cpath == '\0') { return NO; } else { #if defined(__WIN32__) DWORD res; res = GetFileAttributes(cpath); if (res == WIN32ERR) return NO; if (isDirectory) { if (res & FILE_ATTRIBUTE_DIRECTORY) *isDirectory = YES; else *isDirectory = NO; } return YES; #else struct stat statbuf; if (stat(cpath, &statbuf) != 0) return NO; if (isDirectory) { *isDirectory = ((statbuf.st_mode & S_IFMT) == S_IFDIR); } return YES; #endif /* WIN32 */ } } - (BOOL) isReadableFileAtPath: (NSString*)path { const char* cpath = [self fileSystemRepresentationWithPath: path]; if (cpath == 0 || *cpath == '\0') return NO; else { #if defined(__WIN32__) DWORD res= GetFileAttributes(cpath); if (res == WIN32ERR) return NO; return YES; #else return (access(cpath, R_OK) == 0); #endif } } - (BOOL) isWritableFileAtPath: (NSString*)path { const char* cpath = [self fileSystemRepresentationWithPath: path]; if (cpath == 0 || *cpath == '\0') return NO; else { #if defined(__WIN32__) DWORD res= GetFileAttributes(cpath); if (res == WIN32ERR) return NO; return (res & FILE_ATTRIBUTE_READONLY) ? NO : YES; #else return (access(cpath, W_OK) == 0); #endif } } - (BOOL) isExecutableFileAtPath: (NSString*)path { const char* cpath = [self fileSystemRepresentationWithPath: path]; if (cpath == 0 || *cpath == '\0') return NO; else { #if defined(__WIN32__) DWORD res= GetFileAttributes(cpath); int len = strlen(cpath); if (res == WIN32ERR) return NO; if (len > 4 && strcmp(&path[len-4], ".exe") == 0) return YES; return NO; #else return (access(cpath, X_OK) == 0); #endif } } - (BOOL) isDeletableFileAtPath: (NSString*)path { if (path == nil) return NO; else { const char *cpath; // TODO - handle directories cpath = [self fileSystemRepresentationWithPath: [path stringByDeletingLastPathComponent]]; if (access(cpath, X_OK || W_OK) != 0) return NO; cpath = [self fileSystemRepresentationWithPath: [path stringByDeletingLastPathComponent]]; return (access(cpath, X_OK || W_OK) != 0); } } - (NSDictionary*) fileAttributesAtPath: (NSString*)path traverseLink: (BOOL)flag { return [self _attributesAtPath: path traverseLink: flag forCopy: NO]; } - (NSDictionary*) fileSystemAttributesAtPath: (NSString*)path { #if defined(__WIN32__) long long totalsize, freesize; id values[5]; id keys[5] = { NSFileSystemSize, NSFileSystemFreeSize, NSFileSystemNodes, NSFileSystemFreeNodes, NSFileSystemNumber }; DWORD SectorsPerCluster, BytesPerSector, NumberFreeClusters; DWORD TotalNumberClusters; const char *cpath = [self fileSystemRepresentationWithPath: path]; if (!GetDiskFreeSpace(cpath, &SectorsPerCluster, &BytesPerSector, &NumberFreeClusters, &TotalNumberClusters)) return nil; totalsize = TotalNumberClusters * SectorsPerCluster * BytesPerSector; freesize = NumberFreeClusters * SectorsPerCluster * BytesPerSector; values[0] = [NSNumber numberWithLongLong: totalsize]; values[1] = [NSNumber numberWithLongLong: freesize]; values[2] = [NSNumber numberWithLong: LONG_MAX]; values[3] = [NSNumber numberWithLong: LONG_MAX]; values[4] = [NSNumber numberWithUnsignedInt: 0]; return [NSDictionary dictionaryWithObjects: values forKeys: keys count: 5]; #else #if HAVE_SYS_VFS_H || HAVE_SYS_STATFS_H struct stat statbuf; #if HAVE_STATVFS struct statvfs statfsbuf; #else struct statfs statfsbuf; #endif long long totalsize, freesize; const char* cpath = [self fileSystemRepresentationWithPath: path]; id values[5]; id keys[5] = { NSFileSystemSize, NSFileSystemFreeSize, NSFileSystemNodes, NSFileSystemFreeNodes, NSFileSystemNumber }; if (stat(cpath, &statbuf) != 0) return nil; #if HAVE_STATVFS if (statvfs(cpath, &statfsbuf) != 0) return nil; #else if (statfs(cpath, &statfsbuf) != 0) return nil; #endif totalsize = statfsbuf.f_bsize * statfsbuf.f_blocks; freesize = statfsbuf.f_bsize * statfsbuf.f_bfree; values[0] = [NSNumber numberWithLongLong: totalsize]; values[1] = [NSNumber numberWithLongLong: freesize]; values[2] = [NSNumber numberWithLong: statfsbuf.f_files]; values[3] = [NSNumber numberWithLong: statfsbuf.f_ffree]; values[4] = [NSNumber numberWithUnsignedLong: statbuf.st_dev]; return [NSDictionary dictionaryWithObjects: values forKeys: keys count: 5]; #else return nil; #endif #endif /* WIN32 */ } - (BOOL) changeFileAttributes: (NSDictionary*)attributes atPath: (NSString*)path { const char *cpath = [self fileSystemRepresentationWithPath: path]; NSNumber *num; NSString *str; NSDate *date; BOOL allOk = YES; #ifndef __WIN32__ num = [attributes objectForKey: NSFileOwnerAccountNumber]; if (num) { if (chown(cpath, [num intValue], -1) != 0) { allOk = NO; str = [NSString stringWithFormat: @"Unable to change NSFileOwnerAccountNumber to '%@'", num]; ASSIGN(lastError, str); } } else { if ((str = [attributes objectForKey: NSFileOwnerAccountName]) != nil) { BOOL ok = NO; #if HAVE_PWD_H struct passwd *pw = getpwnam([str cString]); if (pw) { ok = (chown(cpath, pw->pw_uid, -1) == 0); chown(cpath, -1, pw->pw_gid); } #endif if (ok == NO) { allOk = NO; str = [NSString stringWithFormat: @"Unable to change NSFileOwnerAccountName to '%@'", str]; ASSIGN(lastError, str); } } } num = [attributes objectForKey: NSFileGroupOwnerAccountNumber]; if (num) { if (chown(cpath, -1, [num intValue]) != 0) { allOk = NO; str = [NSString stringWithFormat: @"Unable to change NSFileGroupOwnerAccountNumber to '%@'", num]; ASSIGN(lastError, str); } } else if ((str=[attributes objectForKey: NSFileGroupOwnerAccountName]) != nil) { BOOL ok = NO; #if HAVE_GRP_H struct group *gp = getgrnam([str cString]); if (gp) { if (chown(cpath, -1, gp->gr_gid) == 0) ok = YES; } #endif if (ok == NO) { allOk = NO; str = [NSString stringWithFormat: @"Unable to change NSFileGroupOwnerAccountName to '%@'", str]; ASSIGN(lastError, str); } } #endif /* __WIN32__ */ num = [attributes objectForKey: NSFilePosixPermissions]; if (num) { if (chmod(cpath, [num intValue]) != 0) { allOk = NO; str = [NSString stringWithFormat: @"Unable to change NSFilePosixPermissions to '%o'", [num intValue]]; ASSIGN(lastError, str); } } date = [attributes objectForKey: NSFileModificationDate]; if (date) { BOOL ok = NO; struct stat sb; #if defined(__WIN32__) || defined(_POSIX_VERSION) struct utimbuf ub; #else time_t ub[2]; #endif if (stat(cpath, &sb) != 0) ok = NO; else { #if defined(__WIN32__) || defined(_POSIX_VERSION) ub.actime = sb.st_atime; ub.modtime = [date timeIntervalSince1970]; ok = (utime(cpath, &ub) == 0); #else ub[0] = sb.st_atime; ub[1] = [date timeIntervalSince1970]; ok = (utime((char*)cpath, ub) == 0); #endif } if (ok == NO) { allOk = NO; str = [NSString stringWithFormat: @"Unable to change NSFileModificationDate to '%@'", date]; ASSIGN(lastError, str); } } return allOk; } // Discovering directory contents - (NSArray*) directoryContentsAtPath: (NSString*)path { NSDirectoryEnumerator *direnum; NSMutableArray *content; BOOL isDir; IMP nxtImp; IMP addImp; if (![self fileExistsAtPath: path isDirectory: &isDir] || !isDir) return nil; direnum = [[NSDirectoryEnumerator alloc] initWithDirectoryPath: path recurseIntoSubdirectories: NO followSymlinks: NO prefixFiles: NO]; content = [NSMutableArray arrayWithCapacity: 128]; nxtImp = [direnum methodForSelector: @selector(nextObject)]; addImp = [content methodForSelector: @selector(addObject: )]; while ((path = (*nxtImp)(direnum, @selector(nextObject))) != nil) (*addImp)(content, @selector(addObject: ), path); RELEASE(direnum); return content; } - (NSDirectoryEnumerator*) enumeratorAtPath: (NSString*)path { return AUTORELEASE([[NSDirectoryEnumerator alloc] initWithDirectoryPath: path recurseIntoSubdirectories: YES followSymlinks: NO prefixFiles: YES]); } - (NSArray*) subpathsAtPath: (NSString*)path { NSDirectoryEnumerator *direnum; NSMutableArray *content; BOOL isDir; IMP nxtImp; IMP addImp; if (![self fileExistsAtPath: path isDirectory: &isDir] || !isDir) return nil; direnum = [[NSDirectoryEnumerator alloc] initWithDirectoryPath: path recurseIntoSubdirectories: YES followSymlinks: NO prefixFiles: YES]; content = [NSMutableArray arrayWithCapacity: 128]; nxtImp = [direnum methodForSelector: @selector(nextObject)]; addImp = [content methodForSelector: @selector(addObject: )]; while ((path = (*nxtImp)(direnum, @selector(nextObject))) != nil) (*addImp)(content, @selector(addObject: ), path); RELEASE(direnum); return content; } // Symbolic-link operations - (BOOL) createSymbolicLinkAtPath: (NSString*)path pathContent: (NSString*)otherPath { const char* lpath = [self fileSystemRepresentationWithPath: path]; const char* npath = [self fileSystemRepresentationWithPath: otherPath]; #ifdef __WIN32__ return NO; #else return (symlink(lpath, npath) == 0); #endif } - (NSString*) pathContentOfSymbolicLinkAtPath: (NSString*)path { char lpath[PATH_MAX]; const char* cpath = [self fileSystemRepresentationWithPath: path]; int llen = readlink(cpath, lpath, PATH_MAX-1); if (llen > 0) return [self stringWithFileSystemRepresentation: lpath length: llen]; else return nil; } // Converting file-system representations - (const char*) fileSystemRepresentationWithPath: (NSString*)path { #if 0 && defined(__WIN32__) unsigned len = [path length]; NSMutableData *d = [NSMutableData dataWithLength: len + 5]; char *fspath = (char*)[d mutableBytes]; [path getCString: &fspath[4]]; fspath[len+4] = '\0'; // Check if path specifies drive number or is current drive if (fspath[4] && (fspath[5] == ': ')) { fspath[0] = fspath[4]; fspath[1] = fspath[5]; fspath[2] = '\\'; fspath[3] = '\0'; } else { fspath = &fspath[2]; fspath[0] = '\\'; fspath[1] = '\0'; } return fspath; #else return [path cString]; #endif } - (NSString*) stringWithFileSystemRepresentation: (const char*)string length: (unsigned int)len { return [NSString stringWithCString: string length: len]; } @end /* NSFileManager */ /* * NSDirectoryEnumerator implementation */ @implementation NSDirectoryEnumerator // Implementation dependent methods /* recurses into directory `path' - pushes relative path (relative to root of search) on pathStack - pushes system dir enumerator on enumPath */ - (void) recurseIntoDirectory: (NSString*)path relativeName: (NSString*)name { const char* cpath; DIR* dir; cpath = [[NSFileManager defaultManager] fileSystemRepresentationWithPath: path]; dir = opendir(cpath); if (dir) { [pathStack addObject: name]; [enumStack addObject: [NSValue valueWithPointer: dir]]; } else NSLog(@"Failed to recurse into directory '%@' - %s", path, strerror(errno)); } /* backtracks enumeration to the previous dir - pops current dir relative path from pathStack - pops system dir enumerator from enumStack - sets currentFile* to nil */ - (void) backtrack { closedir((DIR*)[[enumStack lastObject] pointerValue]); [enumStack removeLastObject]; [pathStack removeLastObject]; DESTROY(currentFileName); DESTROY(currentFilePath); DESTROY(fileAttributes); } /* finds the next file according to the top enumerator - if there is a next file it is put in currentFile - if the current file is a directory and if isRecursive calls recurseIntoDirectory: currentFile - if the current file is a symlink to a directory and if isRecursive and isFollowing calls recurseIntoDirectory: currentFile - if at end of current directory pops stack and attempts to find the next entry in the parent - sets currentFile to nil if there are no more files to enumerate */ - (void) findNextFile { NSFileManager* manager = [NSFileManager defaultManager]; DIR_enum_state* dir; DIR_enum_item* dirbuf; struct stat statbuf; const char* cpath; DESTROY(currentFileName); DESTROY(currentFilePath); DESTROY(fileAttributes); while ([pathStack count]) { dir = (DIR*)[[enumStack lastObject] pointerValue]; dirbuf = readdir(dir); if (dirbuf) { /* Skip "." and ".." directory entries */ if (strcmp(dirbuf->d_name, ".") == 0 || strcmp(dirbuf->d_name, "..") == 0) continue; // Name of current file currentFileName = [manager stringWithFileSystemRepresentation: dirbuf->d_name length: strlen(dirbuf->d_name)]; currentFileName = RETAIN([[pathStack lastObject] stringByAppendingPathComponent: currentFileName]); // Full path of current file currentFilePath = RETAIN([topPath stringByAppendingPathComponent: currentFileName]); // Check if directory cpath = [manager fileSystemRepresentationWithPath: currentFilePath]; if (flags.isRecursive == YES) { // Do not follow links if (!flags.isFollowing) { if (lstat(cpath, &statbuf) != 0) break; // If link then return it as link if (S_IFLNK == (S_IFMT & statbuf.st_mode)) break; } else { if (stat(cpath, &statbuf) != 0) break; } if (S_IFDIR == (S_IFMT & statbuf.st_mode)) { [self recurseIntoDirectory: currentFilePath relativeName: currentFileName]; } } break; // Got a file name - break out of loop } else { [self backtrack]; } } } // Initializing - (id) initWithDirectoryPath: (NSString*)path recurseIntoSubdirectories: (BOOL)recurse followSymlinks: (BOOL)follow prefixFiles: (BOOL)prefix { pathStack = [NSMutableArray new]; enumStack = [NSMutableArray new]; flags.isRecursive = recurse; flags.isFollowing = follow; topPath = RETAIN(path); [self recurseIntoDirectory: path relativeName: @""]; return self; } - (void) dealloc { while ([pathStack count]) [self backtrack]; RELEASE(pathStack); RELEASE(enumStack); RELEASE(topPath); TEST_RELEASE(currentFileName); TEST_RELEASE(currentFilePath); TEST_RELEASE(fileAttributes); TEST_RELEASE(directoryAttributes); [super dealloc]; } // Getting attributes - (NSDictionary*) directoryAttributes { if (directoryAttributes == nil) { directoryAttributes = [[NSFileManager defaultManager] fileAttributesAtPath: topPath traverseLink: flags.isFollowing]; RETAIN(directoryAttributes); } return directoryAttributes; } - (NSDictionary*) fileAttributes { if (fileAttributes == nil) { fileAttributes = [[NSFileManager defaultManager] fileAttributesAtPath: currentFilePath traverseLink: flags.isFollowing]; RETAIN(fileAttributes); } return fileAttributes; } // Skipping subdirectories - (void) skipDescendents { if ([pathStack count]) [self backtrack]; } // Enumerate next - (id) nextObject { [self findNextFile]; return currentFileName; } @end /* NSDirectoryEnumerator */ @implementation NSDirectoryEnumerator (PrivateMethods) - (NSDictionary*) _attributesForCopy { return [[NSFileManager defaultManager] _attributesAtPath: currentFilePath traverseLink: flags.isFollowing forCopy: YES]; } @end /* * Attributes dictionary access */ @implementation NSDictionary(NSFileAttributes) - (unsigned long long) fileSize { return [[self objectForKey: NSFileSize] unsignedLongLongValue]; } - (NSString*) fileType { return [self objectForKey: NSFileType]; } - (NSString*) fileOwnerAccountName { return [self objectForKey: NSFileOwnerAccountName]; } - (unsigned long) fileOwnerAccountNumber { return [[self objectForKey: NSFileOwnerAccountNumber] unsignedIntValue]; } - (NSString*) fileGroupOwnerAccountName { return [self objectForKey: NSFileGroupOwnerAccountName]; } - (unsigned long) fileGroupOwnerAccountNumber { return [[self objectForKey: NSFileGroupOwnerAccountNumber] unsignedIntValue]; } - (NSDate*) fileModificationDate { return [self objectForKey: NSFileModificationDate]; } - (unsigned long) filePosixPermissions { return [[self objectForKey: NSFilePosixPermissions] unsignedLongValue]; } - (unsigned long) fileSystemNumber { return [[self objectForKey: NSFileSystemNumber] unsignedLongValue]; } - (unsigned long) fileSystemFileNumber { return [[self objectForKey: NSFileSystemFileNumber] unsignedLongValue]; } @end @implementation NSFileManager (PrivateMethods) - (BOOL) _copyFile: (NSString*)source toFile: (NSString*)destination handler: handler { NSDictionary* attributes; int i, bufsize = 8096; int sourceFd, destFd, fileSize, fileMode; int rbytes, wbytes; char buffer[bufsize]; /* Assumes source is a file and exists! */ NSAssert1 ([self fileExistsAtPath: source], @"source file '%@' does not exist!", source); attributes = [self _attributesAtPath: source traverseLink: NO forCopy: YES]; NSAssert1 (attributes, @"could not get the attributes for file '%@'", source); fileSize = [[attributes objectForKey: NSFileSize] intValue]; fileMode = [[attributes objectForKey: NSFilePosixPermissions] intValue]; /* Open the source file. In case of error call the handler. */ sourceFd = open ([self fileSystemRepresentationWithPath: source], O_RDONLY); if (sourceFd < 0) { if (handler) { NSDictionary* errorInfo = [NSDictionary dictionaryWithObjectsAndKeys: source, @"Path", @"cannot open file for reading", @"Error", nil]; return [handler fileManager: self shouldProceedAfterError: errorInfo]; } else return NO; } /* Open the destination file. In case of error call the handler. */ destFd = open ([self fileSystemRepresentationWithPath: destination], O_WRONLY|O_CREAT|O_TRUNC, fileMode); if (destFd < 0) { if (handler) { NSDictionary* errorInfo = [NSDictionary dictionaryWithObjectsAndKeys: destination, @"ToPath", @"cannot open file for writing", @"Error", nil]; close (sourceFd); return [handler fileManager: self shouldProceedAfterError: errorInfo]; } else return NO; } /* Read bufsize bytes from source file and write them into the destination file. In case of errors call the handler and abort the operation. */ for (i = 0; i < fileSize; i += rbytes) { rbytes = read (sourceFd, buffer, bufsize); if (rbytes < 0) { if (handler) { NSDictionary* errorInfo = [NSDictionary dictionaryWithObjectsAndKeys: source, @"Path", @"cannot read from file", @"Error", nil]; close (sourceFd); close (destFd); return [handler fileManager: self shouldProceedAfterError: errorInfo]; } else return NO; } wbytes = write (destFd, buffer, rbytes); if (wbytes != rbytes) { if (handler) { NSDictionary* errorInfo = [NSDictionary dictionaryWithObjectsAndKeys: source, @"Path", destination, @"ToPath", @"cannot write to file", @"Error", nil]; close (sourceFd); close (destFd); return [handler fileManager: self shouldProceedAfterError: errorInfo]; } else return NO; } } close (sourceFd); close (destFd); return YES; } - (BOOL) _copyPath: (NSString*)source toPath: (NSString*)destination handler: handler { NSDirectoryEnumerator *enumerator; NSString *dirEntry; CREATE_AUTORELEASE_POOL(pool); enumerator = [self enumeratorAtPath: source]; while ((dirEntry = [enumerator nextObject])) { NSString *sourceFile; NSString *fileType; NSString *destinationFile; NSDictionary *attributes; attributes = [enumerator _attributesForCopy]; fileType = [attributes objectForKey: NSFileType]; sourceFile = [source stringByAppendingPathComponent: dirEntry]; destinationFile = [destination stringByAppendingPathComponent: dirEntry]; [handler fileManager: self willProcessPath: sourceFile]; if ([fileType isEqual: NSFileTypeDirectory]) { if (![self createDirectoryAtPath: destinationFile attributes: attributes]) { if (handler) { NSDictionary *errorInfo; errorInfo = [NSDictionary dictionaryWithObjectsAndKeys: destinationFile, @"Path", lastError, @"Error", nil]; if (![handler fileManager: self shouldProceedAfterError: errorInfo]) return NO; } else return NO; } else { [enumerator skipDescendents]; if (![self _copyPath: sourceFile toPath: destinationFile handler: handler]) return NO; } } else if ([fileType isEqual: NSFileTypeRegular]) { if (![self _copyFile: sourceFile toFile: destinationFile handler: handler]) return NO; } else if ([fileType isEqual: NSFileTypeSymbolicLink]) { if (![self createSymbolicLinkAtPath: destinationFile pathContent: sourceFile]) { if (handler) { NSDictionary *errorInfo = [NSDictionary dictionaryWithObjectsAndKeys: sourceFile, @"Path", destinationFile, @"ToPath", @"cannot create symbolic link", @"Error", nil]; if (![handler fileManager: self shouldProceedAfterError: errorInfo]) return NO; } else return NO; } } else { NSString *s; s = [NSString stringWithFormat: @"cannot copy file type '%@'", fileType]; ASSIGN(lastError, s); NSLog(@"%@: %@", sourceFile, s); continue; } [self changeFileAttributes: attributes atPath: destinationFile]; } RELEASE(pool); return YES; } - (NSDictionary*) _attributesAtPath: (NSString*)path traverseLink: (BOOL)traverse forCopy: (BOOL)copy { struct stat statbuf; const char* cpath = [self fileSystemRepresentationWithPath: path]; int mode; int count; id values[12]; id keys[12] = { NSFileSize, NSFileModificationDate, NSFileReferenceCount, NSFileSystemNumber, NSFileSystemFileNumber, NSFileDeviceIdentifier, NSFilePosixPermissions, NSFileType, NSFileOwnerAccountName, NSFileGroupOwnerAccountName, NSFileOwnerAccountNumber, NSFileGroupOwnerAccountNumber }; if (traverse) { if (stat(cpath, &statbuf) != 0) return nil; } else { if (lstat(cpath, &statbuf) != 0) return nil; } values[0] = [NSNumber numberWithUnsignedLongLong: statbuf.st_size]; values[1] = [NSDate dateWithTimeIntervalSince1970: statbuf.st_mtime]; values[2] = [NSNumber numberWithUnsignedInt: statbuf.st_nlink]; values[3] = [NSNumber numberWithUnsignedLong: statbuf.st_dev]; values[4] = [NSNumber numberWithUnsignedLong: statbuf.st_ino]; values[5] = [NSNumber numberWithUnsignedInt: statbuf.st_dev]; values[6] = [NSNumber numberWithUnsignedInt: statbuf.st_mode]; mode = statbuf.st_mode & S_IFMT; if (mode == S_IFREG) values[7] = NSFileTypeRegular; else if (mode == S_IFDIR) values[7] = NSFileTypeDirectory; else if (mode == S_IFCHR) values[7] = NSFileTypeCharacterSpecial; else if (mode == S_IFBLK) values[7] = NSFileTypeBlockSpecial; #ifdef S_IFLNK else if (mode == S_IFLNK) values[7] = NSFileTypeSymbolicLink; #endif else if (mode == S_IFIFO) values[7] = NSFileTypeFifo; #ifdef S_IFSOCK else if (mode == S_IFSOCK) values[7] = NSFileTypeSocket; #endif else values[7] = NSFileTypeUnknown; if (copy == NO) { #if HAVE_PWD_H { struct passwd *pw; pw = getpwuid(statbuf.st_uid); if (pw) { values[8] = [NSString stringWithCString: pw->pw_name]; } else { values[8] = @"UnknownUser"; } } #else values[8] = @"UnknownUser"; #endif /* HAVE_PWD_H */ #if HAVE_GRP_H { struct group *gp; setgrent(); while ((gp = getgrent()) != 0) { if (gp->gr_gid == statbuf.st_gid) { break; } } if (gp) { values[9] = [NSString stringWithCString: gp->gr_name]; } else { values[9] = @"UnknownGroup"; } endgrent(); } #else values[9] = @"UnknownGroup"; #endif values[10] = [NSNumber numberWithUnsignedInt: statbuf.st_uid]; values[11] = [NSNumber numberWithUnsignedInt: statbuf.st_gid]; count = 12; } else { NSString *u = NSUserName(); count = 8; /* No ownership details needed. */ /* * If we are running setuid to root - we need to specify the user * to be the owner of copied files. */ if (geteuid() == 0 && [@"root" isEqualToString: u] == NO) { values[count++] = u; } } return [NSDictionary dictionaryWithObjects: values forKeys: keys count: count]; } @end /* NSFileManager (PrivateMethods) */