/* 
   NSFileManager.m

   Copyright (C) 1997 Free Software Foundation, Inc.

   Author: Mircea Oancea <mircea@jupiter.elcom.pub.ro>
   Author: Ovidiu Predescu <ovidiu@net-community.com>
   Date: Feb 1997

   This file is part of the mySTEP Library and is provided
   under the terms of the GNU Library General Public License.
*/

#import <Foundation/NSFileManager.h>
#import <Foundation/NSException.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSLock.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSData.h>
#import <Foundation/NSDate.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSPathUtilities.h>
#import <Foundation/NSProcessInfo.h>

#import "NSPrivate.h"

#include <unistd.h>

#ifndef NO_DIRENT_H
# include <dirent.h>
#elif defined(HAVE_SYS_DIR_H)
# include <sys/dir.h>
#elif defined(HAVE_SYS_NDIR_H)
# include <sys/ndir.h>
#elif defined(HAVE_NDIR_H)
# include <ndir.h>
#endif

#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
										// determine filesystem max path length
#ifdef _POSIX_VERSION
#include <limits.h>						// for PATH_MAX
#include <utime.h>
#else
#ifndef __WIN32__
#include <sys/param.h>					// 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
#ifndef __APPLE__
#define HAVE_SYS_STATFS_H 1
#define HAVE_SYS_VFS_H 1
#endif

#ifdef HAVE_SYS_VFS_H
# include <sys/vfs.h>
# ifdef HAVE_SYS_STATVFS_H
#  include <sys/statvfs.h>
# endif
#endif

#ifdef HAVE_SYS_STATFS_H
# include <sys/statfs.h>
#endif

#include <sys/stat.h>

#include <fcntl.h>
#if HAVE_PWD_H
#include <pwd.h>								// For struct passwd
#endif
#if HAVE_UTIME_H
# include <utime.h>
#endif

typedef struct _NSDirectoryEnumerator_t {
	@defs(NSDirectoryEnumerator)
} NSDirectoryEnumerator_t;

// Class variables
static NSFileManager *__fm = nil;


@interface NSFileManager (PrivateMethods)
												// Copies contents of src file 
- (BOOL) _copyFile:(NSString*)source 			// to dest file. Assumes source
			toFile:(NSString*)destination		// and dest are regular files
			handler:handler;					// or symbolic links.

- (BOOL) _copyPath:(NSString*)source 			// Recursively copies contents
			toPath:(NSString*)destination		// of src directory to dst.
			handler:handler;

@end /* NSFileManager (PrivateMethods) */

//*****************************************************************************
//
// 		NSFileManager 
//
//*****************************************************************************

@implementation NSFileManager

+ (NSFileManager*) defaultManager
{
	return (__fm) ? __fm : [self new];
}

- (id) init
{
	return (__fm) ? __fm : (__fm = self);
}

- (BOOL) changeCurrentDirectoryPath:(NSString*)path	
{														// Directory operations
	const char *cpath = [self fileSystemRepresentationWithPath:path];
    if(!cpath)
		return NO;
#if defined(__WIN32__) || defined(_WIN32)
    return SetCurrentDirectory(cpath);
#else
    return (chdir(cpath) == 0);
#endif
}

- (BOOL) _createDirectoryDownToPath:(NSString *) path;
{ // create all directories on this path (if possible)
	if([self fileExistsAtPath:path])
		return YES;	// ok.
	if(![self _createDirectoryDownToPath:[path stringByDeletingLastPathComponent]])	// create super-directory first
		return NO;	// was not able to create superdirectory
	return [self createDirectoryAtPath:path attributes:nil];	// default attributes
}

- (BOOL) createDirectoryAtPath:(NSString*)path
					attributes:(NSDictionary*)attributes
{ // superdirectory must exist!
	struct stat statbuf;
	const char *cpath = [self fileSystemRepresentationWithPath:path];
	if(!cpath)
		return NO;
	if(stat(cpath, &statbuf) == 0) 		// file or directory already exists!
		return NO;
	if(mkdir(cpath, 0777) != 0)			// will be reduced by umask
		return NO;							// was not able to create file
	if(attributes)
		return [self changeFileAttributes:attributes atPath:path];
	return YES;
}

- (NSString*) currentDirectoryPath
{
char path[PATH_MAX];

#if defined(__WIN32__) || defined(_WIN32)
	if(GetCurrentDirectory(PATH_MAX, path) > PATH_MAX)
		return nil;
#else
    if(getcwd(path, PATH_MAX-1) == NULL)
		return nil;
#endif /* WIN32 */

    return [self stringWithFileSystemRepresentation:path length:strlen(path)];
}

- (BOOL) copyPath:(NSString*)source 						// File operations
		   toPath:(NSString*)destination
		   handler:handler
{
BOOL sourceIsDir;
NSDictionary *attributes;

    if (![self fileExistsAtPath:source isDirectory:&sourceIsDir]
			|| [self fileExistsAtPath:destination])
		return NO;

    attributes = [self fileAttributesAtPath:source traverseLink:NO];

    if (sourceIsDir) 					// If dest directory is a descendant of
		{								// src 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 *e = [NSDictionary dictionaryWithObjectsAndKeys:
									destination, @"Path",
									@"cannot create directory", @"Error", nil];
	
				return [handler fileManager:self shouldProceedAfterError:e];
				}

			return NO;
			}

		if (![self _copyPath:source toPath:destination handler:handler])
	    	return NO;

	    [self changeFileAttributes:attributes atPath:destination];

	    return YES;
		}

	[handler fileManager:self willProcessPath:source];
	if (![self _copyFile:source toFile:destination handler:handler])
	    return NO;

	[self changeFileAttributes:attributes atPath:destination];

	return YES;
}

- (BOOL) movePath:(NSString*)source 
		   toPath:(NSString*)destination 
		   handler:handler
{
BOOL sourceIsDir;
NSString *destinationParent;
unsigned int sourceDevice, destinationDevice;
const char *s, *d;

    if (![self fileExistsAtPath:source isDirectory:&sourceIsDir])
		{
		NSLog(@"NSFileManager movePath: source %@ does not exist", source);
		return NO;
		}

    if ([self fileExistsAtPath:destination])
		{
		NSLog(@"NSFileManager movePath: destination %@ exists", destination);
		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 *a=[self fileAttributesAtPath:source traverseLink:NO];

	    	[self changeFileAttributes:a atPath:destination];

	    	return [self removeFileAtPath:source handler:handler];
			}

		return NO;
		}						// src and dest are on the same device so we
								// can simply invoke rename on source. 
	s = [self fileSystemRepresentationWithPath:source];
	d = [self fileSystemRepresentationWithPath:destination];

	[handler fileManager:self willProcessPath:source];
	if (rename (s, d) == -1) 
		{
		if (handler) 
			{
			NSDictionary *e = [NSDictionary dictionaryWithObjectsAndKeys:
									source, @"Path", destination, @"ToPath",
									@"cannot move file", @"Error", nil];

			if ([handler fileManager:self shouldProceedAfterError:e])
				return YES;
			}

		return NO;
		}

	return YES;
}

- (BOOL) linkPath:(NSString*)source 
		   toPath:(NSString*)destination
					  handler:handler								{ NIMP ; return NO; }

- (BOOL) removeFileAtPath:(NSString*)path handler:handler
{
	NSArray	*contents;
	int	i;

	if (handler)
		[handler fileManager: self willProcessPath: path];

	if ((contents = [self directoryContentsAtPath: path]) == nil)
		{
		if (unlink([path fileSystemRepresentation]) < 0)
			{
			BOOL result;
	
			if (handler)
				{
				NSMutableDictionary	*d;
		
				d = [[NSMutableDictionary alloc] initWithCapacity: 3];
				[d setObject: path forKey: @"Path"];
				[d setObject: [NSString stringWithCString: strerror(errno)]
				   forKey: @"Error"];
				result = [handler fileManager:self shouldProceedAfterError:d];
				[d release];
				}
			else
				result = NO;

			return result;
			}

		return YES;
		}

	for (i = 0; i < [contents count]; i++)
		{
		NSAutoreleasePool *arp = [NSAutoreleasePool new];
		NSString *item = [contents objectAtIndex: i];
		NSString *next = [path stringByAppendingPathComponent: item];
		BOOL result = [self removeFileAtPath: next handler: handler];
	
		[arp release];
		if (result == NO)
			return NO;
		}

	if (rmdir([path fileSystemRepresentation]) < 0)
		{
		BOOL result;
	
		if (handler)
			{
			NSMutableDictionary	*d;
	
			d = [[NSMutableDictionary alloc] initWithCapacity: 3];
			[d setObject: path forKey: @"Path"];
			[d setObject: [NSString stringWithCString: strerror(errno)]
			   forKey: @"Error"];
			result = [handler fileManager:self shouldProceedAfterError:d];
			[d release];
			}
		else
			result = NO;

		return result;
		}

	return YES;
}

- (BOOL) createFileAtPath:(NSString*)path 
				 contents:(NSData*)contents
				 attributes:(NSDictionary*)attributes
{
	int fd, len, written;
	const char *cpath = [self fileSystemRepresentationWithPath:path];
	if(!cpath)
		return NO;
    if ((fd = open (cpath, O_WRONLY|O_TRUNC|O_CREAT, 0644)) < 0)
        return NO;

    if (![self changeFileAttributes:attributes atPath:path]) 
		{
        close (fd);
        return NO;
		}

	written = (len = [contents length]) ? write(fd, [contents bytes], len) : 0;
	close (fd);

    return (written == len);
}

- (NSData*) contentsAtPath:(NSString*)path
{
	return [NSData dataWithContentsOfFile:path];
}

- (BOOL) contentsEqualAtPath:(NSString*)path1 
					 andPath:(NSString*)path2
{
	NSData *d1 = [NSData dataWithContentsOfFile:path1];
	return [d1 isEqualToData: [NSData dataWithContentsOfFile:path2]];
}

- (BOOL) fileExistsAtPath:(NSString*)path
{
    return [self fileExistsAtPath:path isDirectory:NULL];
}

- (BOOL) fileExistsAtPath:(NSString*)path isDirectory:(BOOL*)isDirectory
{
	const char *cpath = [self fileSystemRepresentationWithPath:path];
	struct stat statbuf;
	if(!cpath)
		return NO;
    if (stat(cpath, &statbuf) != 0)
		return NO;
    if (isDirectory)
		*isDirectory = ((statbuf.st_mode & S_IFMT) == S_IFDIR);
	return YES;
}

- (BOOL) isReadableFileAtPath:(NSString*)path
{
	const char *cpath = [self fileSystemRepresentationWithPath:path];
	if(!cpath)
		return NO;
    return (access(cpath, R_OK) == 0);
}

- (BOOL) isWritableFileAtPath:(NSString*)path
{
	const char *cpath = [self fileSystemRepresentationWithPath: path];
	if(!cpath)
		return NO;
    return (access(cpath, W_OK) == 0);
}

- (BOOL) isExecutableFileAtPath:(NSString*)path
{
	const char *cpath = [self fileSystemRepresentationWithPath:path];
	struct stat s;
	if(!cpath)
		return NO;
    										// test the exec permission bits
	if (stat(cpath, &s) == 0)				// return YES if any are set
		if(s.st_mode & S_IXUSR || s.st_mode & S_IXGRP || s.st_mode & S_IXOTH)
			return YES;

	return NO;
}

- (BOOL) isDeletableFileAtPath:(NSString*)path
{
	const char *cpath = [self fileSystemRepresentationWithPath: path];
	if(!cpath)
		return NO;
    return (access(cpath, X_OK || W_OK) == 0);
}

- (NSDictionary*) fileAttributesAtPath:(NSString*)path 
						  traverseLink:(BOOL)flag   // FIXME: this is ignored!?!
{
	struct stat statbuf;
	const char *cpath = [self fileSystemRepresentationWithPath: path];
#if HAVE_PWD_H
	struct passwd *pw;
#endif
	id keys[] = {
		NSFileSize,
		NSFileModificationDate,
		NSFileCreationDate,
		NSFileOwnerAccountID,
		NSFileGroupOwnerAccountID,
		NSFileReferenceCount,
		NSFileSystemFileNumber,
		NSFileDeviceIdentifier,
		NSFilePosixPermissions,
		NSFileType,
		NSFileOwnerAccountName
	};
	int mode, count = 0;
	
	id values[sizeof(keys)/sizeof(keys[0])];
#if 0
	NSLog(@"fileAttributesAtPath:%s", cpath);
#endif
	
    if (!cpath || stat(cpath, &statbuf) != 0)
		{
#if 1
		NSLog(@"fileAttributesAtPath: can't stat %s", cpath);
#endif
		return nil;
		}   
    values[count++] = [NSNumber numberWithUnsignedLongLong:statbuf.st_size];
    values[count++] = [NSDate dateWithTimeIntervalSince1970:statbuf.st_mtime];
	values[count++] = [NSDate dateWithTimeIntervalSince1970:statbuf.st_ctime];
    values[count++] = [NSNumber numberWithUnsignedInt:statbuf.st_uid];
    values[count++] = [NSNumber numberWithUnsignedInt:statbuf.st_gid];
    values[count++] = [NSNumber numberWithUnsignedInt:statbuf.st_nlink];
    values[count++] = [NSNumber numberWithUnsignedLong:statbuf.st_ino];
    values[count++] = [NSNumber numberWithUnsignedInt:statbuf.st_dev];
    values[count++] = [NSNumber numberWithUnsignedInt:statbuf.st_mode];
    
    mode = statbuf.st_mode & S_IFMT;
	
    if (mode == S_IFREG)
		values[count] = NSFileTypeRegular;
    else if (mode == S_IFDIR)
		values[count] = NSFileTypeDirectory;
	else if (mode == S_IFCHR)
		values[count] = NSFileTypeCharacterSpecial;
	else if (mode == S_IFBLK)
		values[count] = NSFileTypeBlockSpecial;
	else if (mode == S_IFLNK)
		values[count] = NSFileTypeSymbolicLink;
	else if (mode == S_IFIFO)
		values[count] = NSFileTypeFifo;
	else if (mode == S_IFSOCK)
		values[count] = NSFileTypeSocket;
	else
		values[count] = NSFileTypeUnknown;
	count++;
#if HAVE_PWD_H	
	if((pw = getpwuid(statbuf.st_uid)))
		values[count++] = [NSString stringWithCString:pw->pw_name];
#endif /* HAVE_PWD_H */
	
    return [[[NSDictionary alloc] initWithObjects:values 
										  forKeys:keys 
											count:count] autorelease];
}

- (NSDictionary*) fileSystemAttributesAtPath:(NSString*)path
{
	const char *cpath = [self fileSystemRepresentationWithPath: path];
	long long totalsize=0, freesize=0;
	id keys[5] = {
		NSFileSystemSize,
		NSFileSystemFreeSize,
		NSFileSystemNodes,
		NSFileSystemFreeNodes,
		NSFileSystemNumber
	};
	id values[sizeof(keys)/sizeof(keys[0])];
#if 0
	NSLog(@"fileSystemAttributesAtPath:%s", cpath);
#endif
	
#if defined(__WIN32__) || defined(_WIN32)
	DWORD SectorsPerCluster, BytesPerSector, NumberFreeClusters;
	DWORD TotalNumberClusters;
	
    if (!GetDiskFreeSpace(cpath, &SectorsPerCluster, &BytesPerSector, 
						  &NumberFreeClusters, &TotalNumberClusters))
		return nil;
	
    totalsize = TotalNumberClusters * SectorsPerCluster * BytesPerSector;
    freesize = NumberFreeClusters * SectorsPerCluster * BytesPerSector;
    
    values[2] = [NSNumber numberWithLong: LONG_MAX];
    values[3] = [NSNumber numberWithLong: LONG_MAX];
    values[4] = [NSNumber numberWithUnsignedInt: 0];
#else
	
#if HAVE_SYS_VFS_H || HAVE_SYS_STATFS_H
	{
		struct stat statbuf;
		
#if HAVE_STATVFS
		struct statvfs statfsbuf;
#else
		struct statfs statfsbuf;
#endif
		
		if (!cpath || stat(cpath, &statbuf) != 0)
			{
#if 1
			NSLog(@"fileSystemAttributesAtPath: can't stat %s", cpath);
#endif
			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_bavail;
		
		values[2] = [NSNumber numberWithLong: statfsbuf.f_files];
		values[3] = [NSNumber numberWithLong: statfsbuf.f_ffree];
		values[4] = [NSNumber numberWithUnsignedInt: statbuf.st_dev];
	}
    
#else	// no statfs
    return nil;
#endif
#endif /* WIN32 */
	
    values[0] = [NSNumber numberWithLongLong: totalsize];
    values[1] = [NSNumber numberWithLongLong: freesize];
	
    return [[[NSDictionary alloc] initWithObjects:values 
										  forKeys:keys 
											count:5] autorelease];
}

- (BOOL) changeFileAttributes:(NSDictionary*)attributes 
					   atPath:(NSString*)path
{
const char *cpath = [self fileSystemRepresentationWithPath:path];
NSNumber *num;
NSDate *date;
BOOL allOk = YES;

	if(!cpath)
		return NO;
#ifndef __WIN32__
    if ((num = [attributes objectForKey:NSFileOwnerAccountID])) 
		allOk &= (chown(cpath, [num intValue], -1) == 0);
    
    if ((num = [attributes objectForKey:NSFileGroupOwnerAccountID])) 
		allOk &= (chown(cpath, -1, [num intValue]) == 0);
#endif
    
    if ((num = [attributes objectForKey:NSFilePosixPermissions])) 
		allOk &= (chmod(cpath, [num intValue]) == 0);
    
    if ((date = [attributes objectForKey:NSFileModificationDate])) 
		{
		struct stat sb;
#ifdef  _POSIX_VERSION
		struct utimbuf ub;
#else
		time_t ub[2];
#endif

		if (stat(cpath, &sb) != 0)
	    	allOk = NO;
		else 
			{
#ifdef _POSIX_VERSION
			ub.actime = sb.st_atime;
			ub.modtime = [date timeIntervalSince1970];
			allOk &= (utime(cpath, &ub) == 0);
#else
			ub[0] = sb.st_atime;
			ub[1] = [date timeIntervalSince1970];
			allOk &= (utime((char*)cpath, ub) == 0);
#endif
		}	}
    
    return allOk;
}

- (NSArray*) directoryContentsAtPath:(NSString*)path
{ // note: the .hidden list will itself be part of the returned array!
	NSDirectoryEnumerator *de = [self enumeratorAtPath: path];
	NSMutableArray *c;
	NSArray *hidden=[[NSString stringWithContentsOfFile:[path stringByAppendingPathComponent:@".hidden"]] componentsSeparatedByString:@"\n"];
	if(!de)
		return nil;
    ((NSDirectoryEnumerator_t *)de)->_fm.shallow = YES;
	c = [NSMutableArray arrayWithCapacity:25];	// guess for average sized directories
    while((path = [de nextObject]))
		{
		if([hidden containsObject:path])
			continue;  // is in hidden-list
		[c addObject:path];
		}
    return c;
}

- (NSDirectoryEnumerator*) enumeratorAtPath:(NSString*)path
{
NSDirectoryEnumerator_t *de;
BOOL isDir;
DIR *dir;

    if (![self fileExistsAtPath:path isDirectory:&isDir] || !isDir)
		return nil;

	de = (NSDirectoryEnumerator_t *)[NSDirectoryEnumerator alloc];
    de->_pathStack = [NSMutableArray new];	// recurse into directory `path',
    de->_enumStack = [NSMutableArray new];	// push relative path (to root of  
    de->_topPath = [path retain];			// search) on _pathStack and push
											// sys dir enumerator onto enumPath
	//
	// FIXME: handle virtual home /Users/*
	//
    if ((dir = opendir([__fm  fileSystemRepresentationWithPath:path])))
		{
		[de->_pathStack addObject:@""];
		[de->_enumStack addObject:[NSValue valueWithPointer:dir]];
		}

	return [(NSDirectoryEnumerator *)de autorelease];
}

- (NSArray*) subpathsAtPath:(NSString*)path
{
NSDirectoryEnumerator *de = [self enumeratorAtPath: path];
NSMutableArray *c;

	if (!de)
		return nil;

	c = [[NSMutableArray alloc] init];
    while ((path = [de nextObject]))
		[c addObject:path];

    return [c autorelease];
}

- (BOOL) createSymbolicLinkAtPath:(NSString*)path
					  pathContent:(NSString*)otherPath
{
const char *lpath = [self fileSystemRepresentationWithPath:path];
const char *npath = [self fileSystemRepresentationWithPath:otherPath];
	if(!lpath || !npath)
		return NO;
#ifdef __WIN32__							// handle symbolic-link operations
    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;
	if(!cpath)
		return nil;
	llen = readlink(cpath, lpath, PATH_MAX-1);
    if (llen > 0)
		return [self stringWithFileSystemRepresentation:lpath length:llen];
	return nil;
}

- (const char*) fileSystemRepresentationWithPath:(NSString*)path
{
	static NSString *virtualRoot;
	BOOL uflag;
#if 0
	NSLog(@"fileSystemRepresentationWithPath:%@", path);
#endif
	if([path hasPrefix:@"/"])
		{ // adapt absolute paths
		if((uflag=[path hasPrefix:@"/Users/"]) || [path isEqualToString:@"/Users"])
			{ // do search on /mnt/card for virtual home!
			NSString *alternatePath=[@"/mnt/card" stringByAppendingString:path];
			const char *cpath = [alternatePath UTF8String];
			struct stat statbuf;
			if(stat(cpath, &statbuf) == 0)
				return cpath; // exists - use that version
							  // FIXME: should check if we are allowed to write to that directory!
			if(uflag && stat([[alternatePath stringByDeletingLastPathComponent] UTF8String], &statbuf) == 0)
				{ // file exists - and is not /Users itself
				if(((statbuf.st_mode & S_IFMT) == S_IFDIR) && (statbuf.st_mode & S_IWUSR))
					return cpath; // directory exists and is user-writeable: still use file name in case we want to create that file
				}
			}
		if(!virtualRoot)
			{
			NSProcessInfo *pi = [NSProcessInfo processInfo];
			virtualRoot = [[[pi environment] objectForKey:@"QuantumSTEP"] retain];
			if(!virtualRoot)
				virtualRoot=@"/home/myPDA";		// default
#if 0
			NSLog(@"virtualRoot=%@", virtualRoot);
#endif
			}		   
		if(![path hasPrefix:@"/dev"] && ![path hasPrefix:@"/proc"] && ![path hasPrefix:@"/tmp"])	// we could also check for upper/lower case?
			path=[virtualRoot stringByAppendingString:path]; // virtually do a chroot("/home/myPDA")
		}
#if 0
	NSLog(@" -> %@", path);
#endif
	return [path UTF8String];
}

- (NSString *) stringWithFileSystemRepresentation:(const char*)string
										  length:(unsigned int)len
{
#if __mySTEP__
	if(len > 0 && string[0] == '/')
		{ // absolute path
		static const char *virtualCRoot;	// with trailing /
		static int clen;
		if(!virtualCRoot)
			{
			char *str=[@"/" fileSystemRepresentation];
#if 0
			NSLog(@"virtualCRoot=%s", str);
#endif
			clen=strlen(str);
			virtualCRoot=objc_malloc(clen+1);
			strcpy(virtualCRoot, str);	// save (retain) a copy
			}
#if 0
		NSLog(@"stringWithFileSystemRepresentation: %s len:%d virtualRoot=%s", string, len, virtualCRoot);
#endif
		if(len >= clen && strncmp(string, virtualCRoot, clen) == 0)
			return [NSString stringWithCString:string+clen-1 length:len-clen+1];	// is relative to root
		if(len == clen-1 && strncmp(string, virtualCRoot, len) == 0)
			return @"/";	// this is virtual root /
		if(len >= 15 && strncmp(string, "/mnt/card/Users/", 15) == 0)
			return [NSString stringWithFormat:@"/Users/%.*s", len-15, string+15];	// translate virtual home
		if(strcmp(string, "/mnt/card/Users") == 0)
			return @"/Users";	// translate virtual home
		if(len >= 9 && strncmp(string, "/mnt/net/", 9) == 0)
			return [NSString stringWithFormat:@"/Network/%.*s", len-8, string+8];	// translate
		if(len >= 5 && strncmp(string, "/mnt/", 5) == 0)
			return [NSString stringWithFormat:@"/Volumes/%.*s", len-4, string+4];	// translate
		}
    return [NSString _stringWithUTF8String:string length:len];  // all others unchanged
#else
    return [NSString _stringWithUTF8String:string length:len];
#endif
}

- (NSString *) displayNameAtPath:(NSString *)path;
{
	return [[self componentsToDisplayForPath:path] lastObject];
}

- (NSArray *) componentsToDisplayForPath:(NSString *)path;
{
	return [path pathComponents];
	// should translate components to localized version
	// i.e. Applications -> Programme, Users -> Benutzer
}

@end /* NSFileManager */

//*****************************************************************************
//
// 		NSDirectoryEnumerator 
//
//*****************************************************************************

@implementation NSDirectoryEnumerator

- (void) dealloc
{
    while ([_pathStack count])
		{
		closedir((DIR*)[[_enumStack lastObject] pointerValue]);
		[_enumStack removeLastObject];
		[_pathStack removeLastObject];
		[_fileName release];
		[_filePath release];
		_fileName  = _filePath = nil;
		}    
    [_pathStack release];
    [_enumStack release];
    [_fileName release];
    [_filePath release];
    [_topPath release];
	[super dealloc];
}
														// Getting attributes
- (NSDictionary*) directoryAttributes
{
    return [__fm fileAttributesAtPath:_filePath traverseLink:_fm.followLinks];
}

- (NSDictionary*) fileAttributes
{
    return [__fm fileAttributesAtPath:_filePath traverseLink:_fm.followLinks];
}

- (void) skipDescendents								// Skip subdirectories
{
    if ([_pathStack count])
		{
		closedir((DIR*)[[_enumStack lastObject] pointerValue]);
		[_enumStack removeLastObject];
		[_pathStack removeLastObject];
		[_fileName  release];
		[_filePath release];
		_fileName  = _filePath = nil;
		}
}

- (id) nextObject
{ // finds the next file according to the top
    [_fileName release];			// enumerator.  if there is a next file it
    [_filePath release];			// is put in _fileName.  if the current 
    _fileName = _filePath = nil;	// file is a directory and if isRecursive
			// calls recurseIntoDirectory:currentFile.  if current file is a 
			// symlink to a directory and if isRecursive and followLinks calls 
			// recurseIntoDirectory:currentFile.  if at end of current dir pops 
			// stack and attempts to find the next entry in the parent.  Then
			// sets currentFile to nil if there are no more files to enumerate.
    while ([_pathStack count]) 
		{
		DIR_enum_state *dir = (DIR*)[[_enumStack lastObject] pointerValue];
		DIR_enum_item *dirbuf = readdir(dir);
		struct stat statbuf;
		const char *cpath;

		if (dirbuf) 
			{							// Skip "." and ".." directory entries
			if (strcmp(dirbuf->d_name, ".") == 0 
					|| strcmp(dirbuf->d_name, "..") == 0)
				continue;
			if (strncmp(dirbuf->d_name, "._", 2) == 0)
				continue;				// skip resource forks
			// Name of current file
			_fileName = [__fm stringWithFileSystemRepresentation:dirbuf->d_name
							  length:strlen(dirbuf->d_name)];
			_fileName = [[[_pathStack lastObject]
						stringByAppendingPathComponent:_fileName] retain];
												// Full path of current file
			_filePath = [_topPath stringByAppendingPathComponent:_fileName];
			[_filePath retain];
														// Check if directory
			cpath = [__fm fileSystemRepresentationWithPath:_filePath];
														// Do not follow links
//			if (!_fm.followLinks) 
				{
				if (lstat(cpath, &statbuf) < 0)
					break;
											// If link then return it as link
				if (S_IFLNK == (S_IFMT & statbuf.st_mode)) 
					break;
				}
											// Follow links check for directory
			if (stat(cpath, &statbuf) < 0)
				break;
			if (S_IFDIR == (S_IFMT & statbuf.st_mode) && !_fm.shallow) 
				{						// recurses into directory `path', push
				DIR *dir;				// path relative to root of search onto
										// _pathStack and push system dir
										// enumerator on enumPath
				if ((dir = opendir(cpath)))
					{
					[_pathStack addObject: _fileName ];
					[_enumStack addObject: [NSValue valueWithPointer:dir]];
				}	}

			break;
			}
		else
			{
			closedir((DIR*)[[_enumStack lastObject] pointerValue]);
			[_enumStack removeLastObject];
			[_pathStack removeLastObject];
			[_fileName release];
			[_filePath release];
			_fileName = _filePath = nil;
		}	}

    return _fileName ;
}

@end /* NSDirectoryEnumerator */

//*****************************************************************************
//
// 		NSDictionary (NSFileAttributes) 
//
//*****************************************************************************

@implementation NSDictionary (NSFileAttributes)

- (NSNumber*) fileSize		{ return [self objectForKey:NSFileSize]; }
- (NSString*) fileType;		{ return [self objectForKey:NSFileType]; }

- (NSString*) fileOwnerAccountName; 
{
	return [self objectForKey:NSFileOwnerAccountName];
}

- (NSNumber*) fileOwnerAccountID; 
{
	return [self objectForKey:NSFileOwnerAccountID];
}

- (NSString*) fileGroupOwnerAccountName;
{
	return [self objectForKey:NSFileGroupOwnerAccountName];
}

- (NSNumber*) fileGroupOwnerAccountID;
{
	return [self objectForKey:NSFileGroupOwnerAccountID];
}

- (NSDate*) fileCreationDate;
{
	return [self objectForKey:NSFileCreationDate];
}

- (NSDate*) fileModificationDate;
{
	return [self objectForKey:NSFileModificationDate];
}

- (NSNumber*) filePosixPermissions;
{
	return [self objectForKey:NSFilePosixPermissions];
}

- (BOOL) fileIsAppendOnly;
{
	id o=[self objectForKey:NSFileAppendOnly];
	return o?[o boolValue]:NO;
}

- (BOOL) fileIsImmutable;
{
	id o=[self objectForKey:NSFileImmutable];
	return o?[o boolValue]:NO;
}

- (BOOL) fileExtensionHidden;
{
	id o=[self objectForKey:NSFileExtensionHidden];
	return o?[o boolValue]:NO;
}

// will default to '????' as it is not set by NSFileManager
- (unsigned long) fileHFSCreatorCode;
{
	id o=[self objectForKey:NSFileHFSCreatorCode];
	return o?[o unsignedLongValue]:('?'*257*65537);
}

- (unsigned long) fileHFSTypeCode;
{
	id o=[self objectForKey:NSFileHFSTypeCode];
	return o?[o unsignedLongValue]:('?'*257*65537);
}

- (unsigned long) fileSystemFileNumber;
{
	id o=[self objectForKey:NSFileSystemFileNumber];
	return o?[o unsignedLongValue]:NO;
}

- (unsigned long) fileSystemNumber;
{
	id o=[self objectForKey:NSFileSystemNumber];
	return o?[o unsignedLongValue]:NO;
}

@end /* NSFileAttributes */

//*****************************************************************************
//
// 		NSFileManager (PrivateMethods) 
//
//*****************************************************************************

@implementation NSFileManager (PrivateMethods)

- (BOOL) _copyFile:(NSString*)source 
			toFile:(NSString*)destination
			handler:handler
{
int i, bufsize = 2*4096;
int sourceFd, destFd, fileSize, fileMode;
int rbytes, wbytes;
char buffer[bufsize];
const char *cpath = [self fileSystemRepresentationWithPath:source];
NSDictionary *attributes = [self fileAttributesAtPath:source traverseLink:NO];
										// Assumes source is file and exists
    NSAssert1([self fileExistsAtPath:source],@"source '%@' missing", source);
    NSAssert1(attributes,@"could not get the attributes for file '%@'",source);

    fileSize = [[attributes objectForKey:NSFileSize] intValue];
    fileMode = [[attributes objectForKey:NSFilePosixPermissions] intValue];

    if ((sourceFd = open(cpath, O_RDONLY)) < 0) 
		{										// Open source file. In case
		if (handler) 							// of error call the handler
			{
			NSDictionary *e = [NSDictionary dictionaryWithObjectsAndKeys:
								source, @"Path", 
								@"cannot open file for reading", @"Error",nil];

			return [handler fileManager:self shouldProceedAfterError:e];
			}

		return NO;
		}									// Open destination file. In case
											// of error call the handler.
	cpath = [self fileSystemRepresentationWithPath:destination];
    if ((destFd = open(cpath, O_WRONLY|O_CREAT|O_TRUNC, fileMode)) < 0) 
		{
		if (handler) 
			{
			NSDictionary *e = [NSDictionary dictionaryWithObjectsAndKeys:
							destination, @"ToPath",
							@"cannot open file for writing", @"Error", nil];
			close (sourceFd);

			return [handler fileManager:self shouldProceedAfterError:e];
			}

		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) 
		{
		if ((rbytes = read(sourceFd, buffer, bufsize)) < 0) 
			{
			if (handler) 
				{
				NSDictionary *e = [NSDictionary dictionaryWithObjectsAndKeys: 
									source, @"Path",
									@"cannot read from file", @"Error", nil];
				close(sourceFd);
				close(destFd);

				return [handler fileManager:self shouldProceedAfterError:e];
				}

			return NO;
			}

		if ((wbytes = write(destFd, buffer, rbytes)) != rbytes) 
			{
			if (handler) 
				{
				NSDictionary *e = [NSDictionary dictionaryWithObjectsAndKeys:
									source, @"Path", destination, @"ToPath",
									@"cannot write to file", @"Error", nil];
				close(sourceFd);
				close(destFd);

				return [handler fileManager:self shouldProceedAfterError:e];
				}

			return NO;
		}	}

    close(sourceFd);
    close(destFd);

    return YES;
}

- (BOOL) _copyPath:(NSString*)source
			toPath:(NSString*)destination
			handler:handler
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSDirectoryEnumerator *en = [self enumeratorAtPath:source];
NSString *dirEntry;

    while ((dirEntry = [en nextObject])) 
		{
		NSDictionary *attributes = [en fileAttributes];
		NSString *fileType = [attributes objectForKey:NSFileType];
		NSString *sf = [source stringByAppendingPathComponent:dirEntry];
		NSString *df = [destination stringByAppendingPathComponent:dirEntry];

		[handler fileManager:self willProcessPath:sf];
		if ([fileType isEqual:NSFileTypeDirectory]) 
			{
			if (![self createDirectoryAtPath:df attributes:attributes]) 
				{
				if (handler) 
					{
		    		NSDictionary *e=[NSDictionary dictionaryWithObjectsAndKeys:
									df, @"Path",
									@"cannot create directory", @"Error", nil];

					if (![handler fileManager:self shouldProceedAfterError:e])
						return NO;
					}
				else
		    		return NO;
	    		}
			else 
				{
				[en skipDescendents];
				if (![self _copyPath:sf toPath:df handler:handler])
					return NO;
			}	}
		else 
			if ([fileType isEqual:NSFileTypeRegular]) 
				{
				if (![self _copyFile:sf toFile:df handler:handler])
					return NO;
				}
			else 
				if ([fileType isEqual:NSFileTypeSymbolicLink]) 
					{
					if (![self createSymbolicLinkAtPath:df pathContent:sf]) 
						{
						if (handler) 
							{
		    				NSDictionary *e;

		    				e = [NSDictionary dictionaryWithObjectsAndKeys:
								sf, @"Path", df, @"ToPath",
								@"cannot create symbolic link", @"Error", nil];

							if (![handler fileManager:self
										  shouldProceedAfterError:e])
								return NO;
							}
						else
							return NO;
					}	}
				else 
					NSLog(@"cannot copy file '%@' of type '%@'", sf, fileType);

		[self changeFileAttributes:attributes atPath:df];
		}

	[pool release];

	return YES;
}

@end /* NSFileManager (PrivateMethods) */
