// ================================================================ // Copyright (c) 2007, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // ================================================================ // // GMUserFileSystem.m // // Created by ted on 12/29/07. // Based on FUSEFileSystem originally by alcor. // #import "GMUserFileSystem.h" #define FUSE_USE_VERSION 26 #include #include #include #include #include #include #include #include #include #include #include #include #import #import "GMAppleDouble.h" #import "GMFinderInfo.h" #import "GMResourceFork.h" #import "GMDataBackedFileDelegate.h" #import "GMDTrace.h" #define GM_EXPORT __attribute__((visibility("default"))) // Creates a dtrace-ready string with any newlines removed. #define DTRACE_STRING(s) \ ((char *)[[s stringByReplacingOccurrencesOfString:@"\n" withString:@" "] UTF8String]) // Notifications GM_EXPORT NSString* const kGMUserFileSystemErrorDomain = @"GMUserFileSystemErrorDomain"; GM_EXPORT NSString* const kGMUserFileSystemMountPathKey = @"mountPath"; GM_EXPORT NSString* const kGMUserFileSystemErrorKey = @"error"; GM_EXPORT NSString* const kGMUserFileSystemMountFailed = @"kGMUserFileSystemMountFailed"; GM_EXPORT NSString* const kGMUserFileSystemDidMount = @"kGMUserFileSystemDidMount"; GM_EXPORT NSString* const kGMUserFileSystemDidUnmount = @"kGMUserFileSystemDidUnmount"; // Attribute keys GM_EXPORT NSString* const kGMUserFileSystemFileFlagsKey = @"kGMUserFileSystemFileFlagsKey"; GM_EXPORT NSString* const kGMUserFileSystemFileAccessDateKey = @"kGMUserFileSystemFileAccessDateKey"; GM_EXPORT NSString* const kGMUserFileSystemFileChangeDateKey = @"kGMUserFileSystemFileChangeDateKey"; GM_EXPORT NSString* const kGMUserFileSystemFileBackupDateKey = @"kGMUserFileSystemFileBackupDateKey"; GM_EXPORT NSString* const kGMUserFileSystemFileSizeInBlocksKey = @"kGMUserFileSystemFileSizeInBlocksKey"; GM_EXPORT NSString* const kGMUserFileSystemVolumeSupportsExtendedDatesKey = @"kGMUserFileSystemVolumeSupportsExtendedDatesKey"; GM_EXPORT NSString* const kGMUserFileSystemVolumeMaxFilenameLengthKey = @"kGMUserFileSystemVolumeMaxFilenameLengthKey"; GM_EXPORT NSString* const kGMUserFileSystemVolumeFileSystemBlockSizeKey = @"kGMUserFileSystemVolumeFileSystemBlockSizeKey"; // TODO: Remove comment on EXPORT if/when setvolname is supported. /* GM_EXPORT */ NSString* const kGMUserFileSystemVolumeSupportsSetVolumeNameKey = @"kGMUserFileSystemVolumeSupportsSetVolumeNameKey"; /* GM_EXPORT */ NSString* const kGMUserFileSystemVolumeNameKey = @"kGMUserFileSystemVolumeNameKey"; // FinderInfo and ResourceFork keys GM_EXPORT NSString* const kGMUserFileSystemFinderFlagsKey = @"kGMUserFileSystemFinderFlagsKey"; GM_EXPORT NSString* const kGMUserFileSystemFinderExtendedFlagsKey = @"kGMUserFileSystemFinderExtendedFlagsKey"; GM_EXPORT NSString* const kGMUserFileSystemCustomIconDataKey = @"kGMUserFileSystemCustomIconDataKey"; GM_EXPORT NSString* const kGMUserFileSystemWeblocURLKey = @"kGMUserFileSystemWeblocURLKey"; // Used for time conversions to/from tv_nsec. static const double kNanoSecondsPerSecond = 1000000000.0; typedef enum { // Unable to unmount a dead FUSE files system located at mount point. GMUserFileSystem_ERROR_UNMOUNT_DEADFS = 1000, // Gave up waiting for system removal of existing dir in /Volumes/x after // unmounting a dead FUSE file system. GMUserFileSystem_ERROR_UNMOUNT_DEADFS_RMDIR = 1001, // The mount point did not exist, and we were unable to mkdir it. GMUserFileSystem_ERROR_MOUNT_MKDIR = 1002, // fuse_main returned while trying to mount and don't know why. GMUserFileSystem_ERROR_MOUNT_FUSE_MAIN_INTERNAL = 1003, } GMUserFileSystemErrorCode; typedef enum { GMUserFileSystem_NOT_MOUNTED, // Not mounted. GMUserFileSystem_MOUNTING, // In the process of mounting. GMUserFileSystem_INITIALIZING, // Almost done mounting. GMUserFileSystem_MOUNTED, // Confirmed to be mounted. GMUserFileSystem_UNMOUNTING, // In the process of unmounting. GMUserFileSystem_FAILURE, // Failed state; probably a mount failure. } GMUserFileSystemStatus; @interface GMUserFileSystemInternal : NSObject { NSString* mountPath_; GMUserFileSystemStatus status_; BOOL shouldCheckForResource_; // Try to handle FinderInfo/Resource Forks? BOOL isThreadSafe_; // Is the delegate thread-safe? BOOL supportsExtendedTimes_; // Delegate supports create and backup times? BOOL supportsSetVolumeName_; // Delegate supports setvolname? BOOL isReadOnly_; // Is this mounted read-only? id delegate_; } - (id)initWithDelegate:(id)delegate isThreadSafe:(BOOL)isThreadSafe; - (void)setDelegate:(id)delegate; @end @implementation GMUserFileSystemInternal - (id)init { return [self initWithDelegate:nil isThreadSafe:NO]; } - (id)initWithDelegate:(id)delegate isThreadSafe:(BOOL)isThreadSafe { if ((self = [super init])) { status_ = GMUserFileSystem_NOT_MOUNTED; isThreadSafe_ = isThreadSafe; supportsExtendedTimes_ = NO; supportsSetVolumeName_ = NO; isReadOnly_ = NO; [self setDelegate:delegate]; } return self; } - (void)dealloc { [mountPath_ release]; [super dealloc]; } - (NSString *)mountPath { return mountPath_; } - (void)setMountPath:(NSString *)mountPath { [mountPath_ autorelease]; mountPath_ = [mountPath copy]; } - (GMUserFileSystemStatus)status { return status_; } - (void)setStatus:(GMUserFileSystemStatus)status { status_ = status; } - (BOOL)isThreadSafe { return isThreadSafe_; } - (BOOL)supportsExtendedTimes { return supportsExtendedTimes_; } - (void)setSupportsExtendedTimes:(BOOL)val { supportsExtendedTimes_ = val; } - (BOOL)supportsSetVolumeName { return supportsSetVolumeName_; } - (void)setSupportsSetVolumeName:(BOOL)val { supportsSetVolumeName_ = val; } - (BOOL)shouldCheckForResource { return shouldCheckForResource_; } - (BOOL)isReadOnly { return isReadOnly_; } - (void)setIsReadOnly:(BOOL)val { isReadOnly_ = val; } - (id)delegate { return delegate_; } - (void)setDelegate:(id)delegate { delegate_ = delegate; shouldCheckForResource_ = [delegate_ respondsToSelector:@selector(finderAttributesAtPath:error:)] || [delegate_ respondsToSelector:@selector(resourceAttributesAtPath:error:)] || [delegate_ respondsToSelector:@selector(finderFlagsAtPath:)] || [delegate_ respondsToSelector:@selector(iconDataAtPath:)] || [delegate_ respondsToSelector:@selector(URLOfWeblocAtPath:)]; // Check for deprecated methods. SEL deprecatedMethods[] = { @selector(valueOfExtendedAttribute:ofItemAtPath:error:), @selector(setExtendedAttribute:ofItemAtPath:value:flags:error:), @selector(finderFlagsAtPath:), @selector(iconDataAtPath:), @selector(URLOfWeblocAtPath:), @selector(truncateFileAtPath:offset:error:), @selector(attributesOfItemAtPath:error:), @selector(setAttributes:ofItemAtPath:error:), @selector(openFileAtPath:mode:fileDelegate:error:), @selector(createFileAtPath:attributes:fileDelegate:error:), @selector(releaseFileAtPath:fileDelegate:), @selector(readFileAtPath:fileDelegate:buffer:size:offset:error:), @selector(writeFileAtPath:fileDelegate:buffer:size:offset:error:), }; int i; for (i = 0; i < sizeof(deprecatedMethods)/sizeof(deprecatedMethods[0]); ++i) { SEL sel = deprecatedMethods[i]; if ([delegate_ respondsToSelector:sel]) { NSLog(@"*** WARNING: GMUserFileSystem delegate implements deprecated " @"selector: %@", NSStringFromSelector(sel)); } } } @end // Deprecated delegate methods that we still support for backward compatibility // with previously compiled file systems. This will be actively trimmed as // new releases occur. @interface NSObject (GMUserFileSystemDeprecated) - (NSData *)valueOfExtendedAttribute:(NSString *)name ofItemAtPath:(NSString *)path error:(NSError **)error; - (BOOL)setExtendedAttribute:(NSString *)name ofItemAtPath:(NSString *)path value:(NSData *)value flags:(int)flags error:(NSError **)error; - (UInt16)finderFlagsAtPath:(NSString *)path; - (NSData *)iconDataAtPath:(NSString *)path; - (NSURL *)URLOfWeblocAtPath:(NSString *)path; - (BOOL)truncateFileAtPath:(NSString *)path offset:(off_t)offset error:(NSError **)error; - (NSDictionary *)attributesOfItemAtPath:(NSString *)path error:(NSError **)error; - (BOOL)setAttributes:(NSDictionary *)attributes ofItemAtPath:(NSString *)path error:(NSError **)error; - (BOOL)openFileAtPath:(NSString *)path mode:(int)mode fileDelegate:(id *)fileDelegate error:(NSError **)error; - (BOOL)createFileAtPath:(NSString *)path attributes:(NSDictionary *)attributes fileDelegate:(id *)fileDelegate error:(NSError **)error; - (void)releaseFileAtPath:(NSString *)path fileDelegate:(id)fileDelegate; - (int)readFileAtPath:(NSString *)path fileDelegate:(id)fileDelegate buffer:(char *)buffer size:(size_t)size offset:(off_t)offset error:(NSError **)error; - (int)writeFileAtPath:(NSString *)path fileDelegate:(id)fileDelegate buffer:(const char *)buffer size:(size_t)size offset:(off_t)offset error:(NSError **)error; @end @interface GMUserFileSystem (GMUserFileSystemPrivate) // The filesystem for the current thread. Valid only during a fuse callback. + (GMUserFileSystem *)currentFS; // Convenience method to creates an autoreleased NSError in the // NSPOSIXErrorDomain. Filesystem errors returned by the delegate must be // standard posix errno values. + (NSError *)errorWithCode:(int)code; - (void)mount:(NSDictionary *)args; - (NSDictionary *)finderAttributesAtPath:(NSString *)path; - (NSDictionary *)resourceAttributesAtPath:(NSString *)path; - (BOOL)hasCustomIconAtPath:(NSString *)path; - (BOOL)isDirectoryIconAtPath:(NSString *)path dirPath:(NSString **)dirPath; - (BOOL)isAppleDoubleAtPath:(NSString *)path realPath:(NSString **)realPath; - (NSData *)finderDataForAttributes:(NSDictionary *)attributes; - (NSData *)resourceDataForAttributes:(NSDictionary *)attributes; - (NSData *)appleDoubleContentsAtPath:(NSString *)path; - (NSDictionary *)defaultAttributesOfItemAtPath:(NSString *)path userData:userData error:(NSError **)error; - (BOOL)fillStatBuffer:(struct stat *)stbuf forPath:(NSString *)path fileDelegate:(id)fileDelegate error:(NSError **)error; - (BOOL)fillStatvfsBuffer:(struct statvfs *)stbuf forPath:(NSString *)path error:(NSError **)error; - (void)fuseInit; - (void)fuseDestroy; @end @implementation GMUserFileSystem - (id)init { return [self initWithDelegate:nil isThreadSafe:NO]; } - (id)initWithDelegate:(id)delegate isThreadSafe:(BOOL)isThreadSafe { if ((self = [super init])) { internal_ = [[GMUserFileSystemInternal alloc] initWithDelegate:delegate isThreadSafe:isThreadSafe]; } return self; } - (void)dealloc { [internal_ release]; [super dealloc]; } - (void)setDelegate:(id)delegate { [internal_ setDelegate:delegate]; } - (id)delegate { return [internal_ delegate]; } - (BOOL)enableExtendedTimes { return [internal_ supportsExtendedTimes]; } - (BOOL)enableSetVolumeName { return [internal_ supportsSetVolumeName]; } - (void)mountAtPath:(NSString *)mountPath withOptions:(NSArray *)options { [self mountAtPath:mountPath withOptions:options shouldForeground:YES detachNewThread:YES]; } - (void)mountAtPath:(NSString *)mountPath withOptions:(NSArray *)options shouldForeground:(BOOL)shouldForeground detachNewThread:(BOOL)detachNewThread { [internal_ setMountPath:mountPath]; NSMutableArray* optionsCopy = [NSMutableArray array]; for (int i = 0; i < [options count]; ++i) { NSString* option = [options objectAtIndex:i]; if ([option caseInsensitiveCompare:@"rdonly"] == NSOrderedSame || [option caseInsensitiveCompare:@"ro"] == NSOrderedSame) { [internal_ setIsReadOnly:YES]; } [optionsCopy addObject:[[option copy] autorelease]]; } NSDictionary* args = [[NSDictionary alloc] initWithObjectsAndKeys: optionsCopy, @"options", [NSNumber numberWithBool:shouldForeground], @"shouldForeground", nil, nil]; if (detachNewThread) { [NSThread detachNewThreadSelector:@selector(mount:) toTarget:self withObject:args]; } else { [self mount:args]; } } - (void)unmount { if ([internal_ status] == GMUserFileSystem_MOUNTED) { NSArray* args = [NSArray arrayWithObjects:@"-v", [internal_ mountPath], nil]; NSTask* unmountTask = [NSTask launchedTaskWithLaunchPath:@"/sbin/umount" arguments:args]; [unmountTask waitUntilExit]; } } + (NSError *)errorWithCode:(int)code { return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:nil]; } + (GMUserFileSystem *)currentFS { struct fuse_context* context = fuse_get_context(); assert(context); return (GMUserFileSystem *)context->private_data; } - (void)notifyMounted { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; // Successfully mounted, so post notification. NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: [internal_ mountPath], kGMUserFileSystemMountPathKey, nil, nil]; NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; [center postNotificationName:kGMUserFileSystemDidMount object:self userInfo:userInfo]; [pool release]; } - (void)fuseInit { [internal_ setStatus:GMUserFileSystem_INITIALIZING]; NSError* error = nil; NSDictionary* attribs = [self attributesOfFileSystemForPath:@"/" error:&error]; if (attribs) { NSNumber* supports; supports = [attribs objectForKey:kGMUserFileSystemVolumeSupportsExtendedDatesKey]; if (supports && [supports boolValue]) { [internal_ setSupportsExtendedTimes:YES]; } supports = [attribs objectForKey:kGMUserFileSystemVolumeSupportsSetVolumeNameKey]; if (supports && [supports boolValue]) { [internal_ setSupportsSetVolumeName:YES]; } } [internal_ setStatus:GMUserFileSystem_MOUNTED]; [NSThread detachNewThreadSelector:@selector(notifyMounted) toTarget:self withObject:nil]; } - (void)fuseDestroy { if ([[internal_ delegate] respondsToSelector:@selector(willUnmount)]) { [[internal_ delegate] willUnmount]; } [internal_ setStatus:GMUserFileSystem_UNMOUNTING]; NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: [internal_ mountPath], kGMUserFileSystemMountPathKey, nil, nil]; NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; [center postNotificationName:kGMUserFileSystemDidUnmount object:self userInfo:userInfo]; [internal_ setStatus:GMUserFileSystem_NOT_MOUNTED]; } #pragma mark Finder Info, Resource Forks and HFS headers - (NSDictionary *)finderAttributesAtPath:(NSString *)path { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path)); } UInt16 flags = 0; // If a directory icon, we'll make invisible and update the path to parent. if ([self isDirectoryIconAtPath:path dirPath:&path]) { flags |= kIsInvisible; } id delegate = [internal_ delegate]; if ([delegate respondsToSelector:@selector(finderAttributesAtPath:error:)]) { NSError* error = nil; NSDictionary* dict = [delegate finderAttributesAtPath:path error:&error]; if (dict != nil) { if ([dict objectForKey:kGMUserFileSystemCustomIconDataKey]) { // They have custom icon data, so make sure the FinderFlags bit is set. flags |= kHasCustomIcon; } if (flags != 0) { // May need to update kGMUserFileSystemFinderFlagsKey if different. NSNumber* finderFlags = [dict objectForKey:kGMUserFileSystemFinderFlagsKey]; if (finderFlags != nil) { UInt16 tmp = (UInt16)[finderFlags longValue]; if (flags == tmp) { return dict; // They already have our desired flags. } flags |= tmp; } // Doh! We need to create a new dict with the updated flags key. NSMutableDictionary* newDict = [NSMutableDictionary dictionaryWithDictionary:dict]; [newDict setObject:[NSNumber numberWithLong:flags] forKey:kGMUserFileSystemFinderFlagsKey]; return newDict; } return dict; } // Fall through and create dictionary based on flags if necessary. } else if ([delegate respondsToSelector:@selector(finderFlagsAtPath:)]) { flags |= [delegate finderFlagsAtPath:path]; } else if ([delegate respondsToSelector:@selector(iconDataAtPath:)] && [delegate iconDataAtPath:path] != nil) { flags |= kHasCustomIcon; } if (flags != 0) { return [NSDictionary dictionaryWithObject:[NSNumber numberWithLong:flags] forKey:kGMUserFileSystemFinderFlagsKey]; } return nil; } - (NSDictionary *)resourceAttributesAtPath:(NSString *)path { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path)); } id delegate = [internal_ delegate]; if ([delegate respondsToSelector:@selector(resourceAttributesAtPath:error:)]) { NSError* error = nil; return [delegate resourceAttributesAtPath:path error:&error]; } // Support for deprecated selectors. NSURL* url = nil; if ([path hasSuffix:@".webloc"] && [delegate respondsToSelector:@selector(URLOfWeblocAtPath:)]) { url = [delegate URLOfWeblocAtPath:path]; } NSData* imageData = nil; if ([delegate respondsToSelector:@selector(iconDataAtPath:)]) { imageData = [delegate iconDataAtPath:path]; } if (imageData || url) { NSMutableDictionary* dict = [NSMutableDictionary dictionary]; if (imageData) { [dict setObject:imageData forKey:kGMUserFileSystemCustomIconDataKey]; } if (url) { [dict setObject:url forKey:kGMUserFileSystemWeblocURLKey]; } return dict; } return nil; } - (BOOL)hasCustomIconAtPath:(NSString *)path { if ([path isEqualToString:@"/"]) { return NO; // For a volume icon they should use the volicon= option. } NSDictionary* finderAttribs = [self finderAttributesAtPath:path]; if (finderAttribs) { NSNumber* finderFlags = [finderAttribs objectForKey:kGMUserFileSystemFinderFlagsKey]; if (finderFlags) { UInt16 flags = (UInt16)[finderFlags longValue]; return (flags & kHasCustomIcon) == kHasCustomIcon; } } return NO; } - (BOOL)isDirectoryIconAtPath:(NSString *)path dirPath:(NSString **)dirPath { NSString* name = [path lastPathComponent]; if ([name isEqualToString:@"Icon\r"]) { if (dirPath) { *dirPath = [path stringByDeletingLastPathComponent]; } return YES; } return NO; } - (BOOL)isAppleDoubleAtPath:(NSString *)path realPath:(NSString **)realPath { NSString* name = [path lastPathComponent]; if ([name hasPrefix:@"._"]) { if (realPath) { name = [name substringFromIndex:2]; *realPath = [path stringByDeletingLastPathComponent]; *realPath = [*realPath stringByAppendingPathComponent:name]; } return YES; } return NO; } // If the given attribs dictionary contains any FinderInfo attributes then // returns NSData for FinderInfo; otherwise returns nil. - (NSData *)finderDataForAttributes:(NSDictionary *)attribs { if (!attribs) { return nil; } GMFinderInfo* info = [GMFinderInfo finderInfo]; BOOL attributeFound = NO; // Have we found at least one relevant attribute? NSNumber* flags = [attribs objectForKey:kGMUserFileSystemFinderFlagsKey]; if (flags) { attributeFound = YES; [info setFlags:(UInt16)[flags longValue]]; } NSNumber* extendedFlags = [attribs objectForKey:kGMUserFileSystemFinderExtendedFlagsKey]; if (extendedFlags) { attributeFound = YES; [info setExtendedFlags:(UInt16)[extendedFlags longValue]]; } NSNumber* typeCode = [attribs objectForKey:NSFileHFSTypeCode]; if (typeCode) { attributeFound = YES; [info setTypeCode:(OSType)[typeCode longValue]]; } NSNumber* creatorCode = [attribs objectForKey:NSFileHFSCreatorCode]; if (creatorCode) { attributeFound = YES; [info setCreatorCode:(OSType)[creatorCode longValue]]; } return attributeFound ? [info data] : nil; } // If the given attribs dictionary contains any ResourceFork attributes then // returns NSData for the ResourceFork; otherwise returns nil. - (NSData *)resourceDataForAttributes:(NSDictionary *)attribs { if (!attribs) { return nil; } GMResourceFork* fork = [GMResourceFork resourceFork]; BOOL attributeFound = NO; // Have we found at least one relevant attribute? NSData* imageData = [attribs objectForKey:kGMUserFileSystemCustomIconDataKey]; if (imageData) { attributeFound = YES; [fork addResourceWithType:'icns' resID:kCustomIconResource // -16455 name:nil data:imageData]; } NSURL* url = [attribs objectForKey:kGMUserFileSystemWeblocURLKey]; if (url) { attributeFound = YES; NSString* urlString = [url absoluteString]; NSData* data = [urlString dataUsingEncoding:NSUTF8StringEncoding]; [fork addResourceWithType:'url ' resID:256 name:nil data:data]; } return attributeFound ? [fork data] : nil; } // Returns the AppleDouble file contents, if any, for the given path. You should // call this with the realPath out-param from a call to isAppleDoubleAtPath:. // // On 10.5 and (hopefully) above, the Finder will end up using the extended // attributes and so we won't need to serve ._ files. - (NSData *)appleDoubleContentsAtPath:(NSString *)path { NSDictionary* finderAttributes = [self finderAttributesAtPath:path]; NSData* finderData = [self finderDataForAttributes:finderAttributes]; // We treat the ._ for a directory and it's ._Icon\r file the same. This means // that we'll put extra resource-fork information in directory's ._ file even // though it isn't needed. It's worth it given that it only affects 10.4. [self isDirectoryIconAtPath:path dirPath:&path]; NSDictionary* resourceAttributes = [self resourceAttributesAtPath:path]; NSData* resourceData = [self resourceDataForAttributes:resourceAttributes]; if (finderData != nil || resourceData != nil) { GMAppleDouble* doubleFile = [GMAppleDouble appleDouble]; if (finderData) { [doubleFile addEntryWithID:DoubleEntryFinderInfo data:finderData]; } if (resourceData) { [doubleFile addEntryWithID:DoubleEntryResourceFork data:resourceData]; } return [doubleFile data]; } return nil; } #pragma mark Internal Stat Operations - (BOOL)fillStatvfsBuffer:(struct statvfs *)stbuf forPath:(NSString *)path error:(NSError **)error { NSDictionary* attributes = [self attributesOfFileSystemForPath:path error:error]; if (!attributes) { return NO; } // Maximum length of filenames NSNumber* namemax = [attributes objectForKey:kGMUserFileSystemVolumeMaxFilenameLengthKey]; assert(namemax); stbuf->f_namemax = [namemax unsignedLongValue]; // Block size NSNumber* blocksize = [attributes objectForKey:kGMUserFileSystemVolumeFileSystemBlockSizeKey]; assert(blocksize); stbuf->f_bsize = stbuf->f_frsize = [blocksize unsignedLongValue]; // Size in blocks NSNumber* size = [attributes objectForKey:NSFileSystemSize]; assert(size); stbuf->f_blocks = (fsblkcnt_t)([size longLongValue] / stbuf->f_frsize); // Number of free / available blocks NSNumber* freeSize = [attributes objectForKey:NSFileSystemFreeSize]; assert(freeSize); stbuf->f_bfree = stbuf->f_bavail = (fsblkcnt_t)([freeSize longLongValue] / stbuf->f_frsize); // Number of nodes NSNumber* numNodes = [attributes objectForKey:NSFileSystemNodes]; assert(numNodes); stbuf->f_files = (fsfilcnt_t)[numNodes longLongValue]; // Number of free / available nodes NSNumber* freeNodes = [attributes objectForKey:NSFileSystemFreeNodes]; assert(freeNodes); stbuf->f_ffree = stbuf->f_favail = (fsfilcnt_t)[freeNodes longLongValue]; return YES; } - (BOOL)fillStatBuffer:(struct stat *)stbuf forPath:(NSString *)path userData:(id)userData error:(NSError **)error { NSDictionary* attributes = [self defaultAttributesOfItemAtPath:path userData:userData error:error]; if (!attributes) { return NO; } // Inode NSNumber* inode = [attributes objectForKey:NSFileSystemFileNumber]; if (inode) { stbuf->st_ino = [inode longLongValue]; } // Permissions (mode) NSNumber* perm = [attributes objectForKey:NSFilePosixPermissions]; stbuf->st_mode = [perm longValue]; NSString* fileType = [attributes objectForKey:NSFileType]; if ([fileType isEqualToString:NSFileTypeDirectory ]) { stbuf->st_mode |= S_IFDIR; } else if ([fileType isEqualToString:NSFileTypeRegular]) { stbuf->st_mode |= S_IFREG; } else if ([fileType isEqualToString:NSFileTypeSymbolicLink]) { stbuf->st_mode |= S_IFLNK; } else { *error = [GMUserFileSystem errorWithCode:EFTYPE]; return NO; } // Owner and Group // Note that if the owner or group IDs are not specified, the effective // user and group IDs for the current process are used as defaults. NSNumber* uid = [attributes objectForKey:NSFileOwnerAccountID]; NSNumber* gid = [attributes objectForKey:NSFileGroupOwnerAccountID]; stbuf->st_uid = uid ? [uid longValue] : geteuid(); stbuf->st_gid = gid ? [gid longValue] : getegid(); // nlink NSNumber* nlink = [attributes objectForKey:NSFileReferenceCount]; stbuf->st_nlink = [nlink longValue]; // flags NSNumber* flags = [attributes objectForKey:kGMUserFileSystemFileFlagsKey]; if (flags) { stbuf->st_flags = [flags longValue]; } else { // Just in case they tried to use NSFileImmutable or NSFileAppendOnly NSNumber* immutableFlag = [attributes objectForKey:NSFileImmutable]; if (immutableFlag && [immutableFlag boolValue]) { stbuf->st_flags |= UF_IMMUTABLE; } NSNumber* appendFlag = [attributes objectForKey:NSFileAppendOnly]; if (appendFlag && [appendFlag boolValue]) { stbuf->st_flags |= UF_APPEND; } } // NOTE: We default atime,ctime to mtime if it is provided. NSDate* mdate = [attributes objectForKey:NSFileModificationDate]; if (mdate) { const double seconds_dp = [mdate timeIntervalSince1970]; const time_t t_sec = (time_t) seconds_dp; const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond); const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0; stbuf->st_mtimespec.tv_sec = t_sec; stbuf->st_mtimespec.tv_nsec = t_nsec; stbuf->st_atimespec = stbuf->st_mtimespec; // Default to mtime stbuf->st_ctimespec = stbuf->st_mtimespec; // Default to mtime } NSDate* adate = [attributes objectForKey:kGMUserFileSystemFileAccessDateKey]; if (adate) { const double seconds_dp = [adate timeIntervalSince1970]; const time_t t_sec = (time_t) seconds_dp; const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond); const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0; stbuf->st_atimespec.tv_sec = t_sec; stbuf->st_atimespec.tv_nsec = t_nsec; } NSDate* cdate = [attributes objectForKey:kGMUserFileSystemFileChangeDateKey]; if (cdate) { const double seconds_dp = [cdate timeIntervalSince1970]; const time_t t_sec = (time_t) seconds_dp; const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond); const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0; stbuf->st_ctimespec.tv_sec = t_sec; stbuf->st_ctimespec.tv_nsec = t_nsec; } #ifdef _DARWIN_FEATURE_64_BIT_INODE NSDate* bdate = [attributes objectForKey:NSFileCreationDate]; if (bdate) { const double seconds_dp = [bdate timeIntervalSince1970]; const time_t t_sec = (time_t) seconds_dp; const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond); const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0; stbuf->st_birthtimespec.tv_sec = t_sec; stbuf->st_birthtimespec.tv_nsec = t_nsec; } #endif // File size // Note that the actual file size of a directory depends on the internal // representation of directories in the particular file system. In general // this is not the combined size of the files in that directory. NSNumber* size = [attributes objectForKey:NSFileSize]; if (size) { stbuf->st_size = [size longLongValue]; } // Set the number of blocks used so that Finder will display size on disk // properly. The man page says that this is in terms of 512 byte blocks. NSNumber* blocks = [attributes objectForKey:kGMUserFileSystemFileSizeInBlocksKey]; if (blocks) { stbuf->st_blocks = [blocks longLongValue]; } else if (stbuf->st_size > 0) { stbuf->st_blocks = stbuf->st_size / 512; if (stbuf->st_size % 512) { ++(stbuf->st_blocks); } } return YES; } #pragma mark Moving an Item - (BOOL)moveItemAtPath:(NSString *)source toPath:(NSString *)destination error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@ -> %@", source, destination]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } if ([[internal_ delegate] respondsToSelector:@selector(moveItemAtPath:toPath:error:)]) { return [[internal_ delegate] moveItemAtPath:source toPath:destination error:error]; } *error = [GMUserFileSystem errorWithCode:EACCES]; return NO; } #pragma mark Removing an Item - (BOOL)removeDirectoryAtPath:(NSString *)path error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path)); } if ([[internal_ delegate] respondsToSelector:@selector(removeDirectoryAtPath:error:)]) { return [[internal_ delegate] removeDirectoryAtPath:path error:error]; } return [self removeItemAtPath:path error:error]; } - (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path)); } if ([[internal_ delegate] respondsToSelector:@selector(removeItemAtPath:error:)]) { return [[internal_ delegate] removeItemAtPath:path error:error]; } *error = [GMUserFileSystem errorWithCode:EACCES]; return NO; } #pragma mark Creating an Item - (BOOL)createDirectoryAtPath:(NSString *)path attributes:(NSDictionary *)attributes error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSMutableString* traceinfo = [NSMutableString stringWithFormat:@"%@ [%@]", path, attributes]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } if ([[internal_ delegate] respondsToSelector:@selector(createDirectoryAtPath:attributes:error:)]) { return [[internal_ delegate] createDirectoryAtPath:path attributes:attributes error:error]; } *error = [GMUserFileSystem errorWithCode:EACCES]; return NO; } - (BOOL)createFileAtPath:(NSString *)path attributes:(NSDictionary *)attributes userData:(id *)userData error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@ [%@]", path, attributes]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } if ([[internal_ delegate] respondsToSelector:@selector(createFileAtPath:attributes:userData:error:)]) { return [[internal_ delegate] createFileAtPath:path attributes:attributes userData:userData error:error]; } else if ([[internal_ delegate] respondsToSelector:@selector(createFileAtPath:attributes:fileDelegate:error:)]) { // NOTE: For backward compatibility with version 1.7 and prior. return [[internal_ delegate] createFileAtPath:path attributes:attributes fileDelegate:userData error:error]; } *error = [GMUserFileSystem errorWithCode:EACCES]; return NO; } #pragma mark Linking an Item - (BOOL)linkItemAtPath:(NSString *)path toPath:(NSString *)otherPath error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@ -> %@", path, otherPath]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } if ([[internal_ delegate] respondsToSelector:@selector(linkItemAtPath:toPath:error:)]) { return [[internal_ delegate] linkItemAtPath:path toPath:otherPath error:error]; } *error = [GMUserFileSystem errorWithCode:ENOTSUP]; // Note: error not in man page. return NO; } #pragma mark Symbolic Links - (BOOL)createSymbolicLinkAtPath:(NSString *)path withDestinationPath:(NSString *)otherPath error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@ -> %@", path, otherPath]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } if ([[internal_ delegate] respondsToSelector:@selector(createSymbolicLinkAtPath:withDestinationPath:error:)]) { return [[internal_ delegate] createSymbolicLinkAtPath:path withDestinationPath:otherPath error:error]; } *error = [GMUserFileSystem errorWithCode:ENOTSUP]; // Note: error not in man page. return NO; } - (NSString *)destinationOfSymbolicLinkAtPath:(NSString *)path error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path)); } if ([[internal_ delegate] respondsToSelector:@selector(destinationOfSymbolicLinkAtPath:error:)]) { return [[internal_ delegate] destinationOfSymbolicLinkAtPath:path error:error]; } *error = [GMUserFileSystem errorWithCode:ENOENT]; return nil; } #pragma mark File Contents // NOTE: Only call this if the delegate does indeed support this method. - (NSData *)contentsAtPath:(NSString *)path { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path)); } id delegate = [internal_ delegate]; return [delegate contentsAtPath:path]; } - (BOOL)openFileAtPath:(NSString *)path mode:(int)mode userData:(id *)userData error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@, mode=0x%x", path, mode]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } id delegate = [internal_ delegate]; if ([delegate respondsToSelector:@selector(contentsAtPath:)]) { NSData* data = [self contentsAtPath:path]; if (data != nil) { *userData = [GMDataBackedFileDelegate fileDelegateWithData:data]; return YES; } } else if ([delegate respondsToSelector:@selector(openFileAtPath:mode:userData:error:)]) { if ([delegate openFileAtPath:path mode:mode userData:userData error:error]) { return YES; // They handled it. } } else if ([delegate respondsToSelector:@selector(openFileAtPath:mode:fileDelegate:error:)]) { if ([delegate openFileAtPath:path mode:mode fileDelegate:userData error:error]) { // NOTE: For backward compatibility with version 1.7 and prior. return YES; // They handled it. } } // Still unable to open the file; maybe it is an Icon\r or AppleDouble? if ([internal_ shouldCheckForResource]) { NSData* data = nil; // Synthesized data that we provide a file delegate for. // Is it an Icon\r file that we handle? if ([self isDirectoryIconAtPath:path dirPath:nil]) { data = [NSData data]; // The Icon\r file is empty. } if (data != nil) { if ((mode & O_ACCMODE) == O_RDONLY) { *userData = [GMDataBackedFileDelegate fileDelegateWithData:data]; } else { NSMutableData* mutableData = [NSMutableData dataWithData:data]; *userData = [GMMutableDataBackedFileDelegate fileDelegateWithData:mutableData]; } return YES; // Handled by a synthesized file delegate. } } if (*error == nil) { *error = [GMUserFileSystem errorWithCode:ENOENT]; } return NO; } - (void)releaseFileAtPath:(NSString *)path userData:(id)userData { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@, userData=%p", path, userData]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } if (userData != nil && [userData isKindOfClass:[GMDataBackedFileDelegate class]]) { return; // Don't report releaseFileAtPath for internal file. } if ([[internal_ delegate] respondsToSelector:@selector(releaseFileAtPath:userData:)]) { [[internal_ delegate] releaseFileAtPath:path userData:userData]; } else if ([[internal_ delegate] respondsToSelector:@selector(releaseFileAtPath:fileDelegate:)]) { // NOTE: For backward compatibility with version 1.7 and prior. [[internal_ delegate] releaseFileAtPath:path fileDelegate:userData]; } } - (int)readFileAtPath:(NSString *)path userData:(id)userData buffer:(char *)buffer size:(size_t)size offset:(off_t)offset error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@, userData=%p, offset=%lld, size=%zd", path, userData, offset, size]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } if (userData != nil && [userData respondsToSelector:@selector(readToBuffer:size:offset:error:)]) { return [userData readToBuffer:buffer size:size offset:offset error:error]; } else if ([[internal_ delegate] respondsToSelector:@selector(readFileAtPath:userData:buffer:size:offset:error:)]) { return [[internal_ delegate] readFileAtPath:path userData:userData buffer:buffer size:size offset:offset error:error]; } else if ([[internal_ delegate] respondsToSelector:@selector(readFileAtPath:fileDelegate:buffer:size:offset:error:)]) { // NOTE: For backward compatibility with version 1.7 and prior. return [[internal_ delegate] readFileAtPath:path fileDelegate:userData buffer:buffer size:size offset:offset error:error]; } *error = [GMUserFileSystem errorWithCode:EACCES]; return -1; } - (int)writeFileAtPath:(NSString *)path userData:(id)userData buffer:(const char *)buffer size:(size_t)size offset:(off_t)offset error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@, userData=%p, offset=%lld, size=%zd", path, userData, offset, size]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } if (userData != nil && [userData respondsToSelector:@selector(writeFromBuffer:size:offset:error:)]) { return [userData writeFromBuffer:buffer size:size offset:offset error:error]; } else if ([[internal_ delegate] respondsToSelector:@selector(writeFileAtPath:userData:buffer:size:offset:error:)]) { return [[internal_ delegate] writeFileAtPath:path userData:userData buffer:buffer size:size offset:offset error:error]; } else if ([[internal_ delegate] respondsToSelector:@selector(writeFileAtPath:fileDelegate:buffer:size:offset:error:)]) { // NOTE: For backward compatibility with version 1.7 and prior. return [[internal_ delegate] writeFileAtPath:path fileDelegate:userData buffer:buffer size:size offset:offset error:error]; } *error = [GMUserFileSystem errorWithCode:EACCES]; return -1; } // NOTE: For backward compatibility with version 1.7 and prior. - (BOOL)truncateFileAtPath:(NSString *)path fileDelegate:(id)fileDelegate offset:(off_t)offset error:(NSError **)error handled:(BOOL*)handled { if (fileDelegate != nil && [fileDelegate respondsToSelector:@selector(truncateToOffset:error:)]) { *handled = YES; return [fileDelegate truncateToOffset:offset error:error]; } else if ([[internal_ delegate] respondsToSelector:@selector(truncateFileAtPath:offset:error:)]) { *handled = YES; return [[internal_ delegate] truncateFileAtPath:path offset:offset error:error]; } *handled = NO; return NO; } - (BOOL)exchangeDataOfItemAtPath:(NSString *)path1 withItemAtPath:(NSString *)path2 error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@ <-> %@", path1, path2]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } if ([[internal_ delegate] respondsToSelector:@selector(exchangeDataOfItemAtPath:withItemAtPath:error:)]) { return [[internal_ delegate] exchangeDataOfItemAtPath:path1 withItemAtPath:path2 error:error]; } *error = [GMUserFileSystem errorWithCode:ENOSYS]; return NO; } #pragma mark Directory Contents - (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path)); } NSArray* contents = nil; if ([[internal_ delegate] respondsToSelector:@selector(contentsOfDirectoryAtPath:error:)]) { contents = [[internal_ delegate] contentsOfDirectoryAtPath:path error:error]; } else if ([path isEqualToString:@"/"]) { contents = [NSArray array]; // Give them an empty root directory for free. } return contents; } #pragma mark Getting and Setting Attributes - (BOOL)supportsAttributesOfItemAtPath { id delegate = [internal_ delegate]; return [delegate respondsToSelector:@selector(attributesOfItemAtPath:userData:error:)] || [delegate respondsToSelector:@selector(attributesOfItemAtPath:error:)]; } - (NSDictionary *)attributesOfItemAtPath:(NSString *)path userData:userData error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@, userData=%p", path, userData]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } id delegate = [internal_ delegate]; if ([delegate respondsToSelector:@selector(attributesOfItemAtPath:userData:error:)]) { return [delegate attributesOfItemAtPath:path userData:userData error:error]; } else if ([delegate respondsToSelector:@selector(attributesOfItemAtPath:error:)]) { return [delegate attributesOfItemAtPath:path error:error]; } return nil; } // Get attributesOfItemAtPath from the delegate with default values. - (NSDictionary *)defaultAttributesOfItemAtPath:(NSString *)path userData:userData error:(NSError **)error { // Set up default item attributes. NSMutableDictionary* attributes = [NSMutableDictionary dictionary]; BOOL isReadOnly = [internal_ isReadOnly]; [attributes setObject:[NSNumber numberWithLong:(isReadOnly ? 0555 : 0775)] forKey:NSFilePosixPermissions]; [attributes setObject:[NSNumber numberWithLong:1] forKey:NSFileReferenceCount]; // 1 means "don't know" if ([path isEqualToString:@"/"]) { [attributes setObject:NSFileTypeDirectory forKey:NSFileType]; } else { [attributes setObject:NSFileTypeRegular forKey:NSFileType]; } id delegate = [internal_ delegate]; BOOL isAppleDouble = NO; // May only be set to YES on Tiger. BOOL isDirectoryIcon = NO; // The delegate can override any of the above defaults by implementing the // attributesOfItemAtPath: selector and returning a custom dictionary. NSDictionary* customAttribs = nil; BOOL supportsAttributesSelector = [self supportsAttributesOfItemAtPath]; if (supportsAttributesSelector) { customAttribs = [self attributesOfItemAtPath:path userData:userData error:error]; } // Maybe this is the root directory? If so, we'll claim it always exists. if (!customAttribs && [path isEqualToString:@"/"]) { return attributes; // The root directory always exists. } // Maybe check to see if this is a special file that we should handle. If they // wanted to handle it, then they would have given us back customAttribs. if (!customAttribs && [internal_ shouldCheckForResource]) { // If the maybe-fixed-up path is a directoryIcon, we'll modify the path to // refer to the parent directory and note that we are a directory icon. isDirectoryIcon = [self isDirectoryIconAtPath:path dirPath:&path]; // Maybe we'll try again to get custom attribs on the real path. if (supportsAttributesSelector && (isAppleDouble || isDirectoryIcon)) { customAttribs = [self attributesOfItemAtPath:path userData:userData error:error]; } } if (customAttribs) { [attributes addEntriesFromDictionary:customAttribs]; } else if (supportsAttributesSelector) { // They explicitly support attributesOfItemAtPath: and returned nil. if (!(*error)) { *error = [GMUserFileSystem errorWithCode:ENOENT]; } return nil; } // If this is a directory Icon\r then it is an empty file and we're done. if (isDirectoryIcon && !isAppleDouble) { if ([self hasCustomIconAtPath:path]) { [attributes setObject:NSFileTypeRegular forKey:NSFileType]; [attributes setObject:[NSNumber numberWithLongLong:0] forKey:NSFileSize]; return attributes; } *error = [GMUserFileSystem errorWithCode:ENOENT]; return nil; } // If this is a ._ then we'll need to compute its size and we're done. This // will never be true on post-Tiger. if (isAppleDouble) { NSData* data = [self appleDoubleContentsAtPath:path]; if (data != nil) { [attributes setObject:NSFileTypeRegular forKey:NSFileType]; [attributes setObject:[NSNumber numberWithLongLong:[data length]] forKey:NSFileSize]; return attributes; } *error = [GMUserFileSystem errorWithCode:ENOENT]; return nil; } // If they don't supply a size and it is a file then we try to compute it. if (![attributes objectForKey:NSFileSize] && ![[attributes objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory] && [delegate respondsToSelector:@selector(contentsAtPath:)]) { NSData* data = [self contentsAtPath:path]; if (data == nil) { *error = [GMUserFileSystem errorWithCode:ENOENT]; return nil; } [attributes setObject:[NSNumber numberWithLongLong:[data length]] forKey:NSFileSize]; } return attributes; } - (NSDictionary *)extendedTimesOfItemAtPath:(NSString *)path userData:(id)userData error:(NSError **)error { if (![self supportsAttributesOfItemAtPath]) { *error = [GMUserFileSystem errorWithCode:ENOSYS]; return nil; } return [self attributesOfItemAtPath:path userData:userData error:error]; } - (NSDictionary *)attributesOfFileSystemForPath:(NSString *)path error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path)); } NSMutableDictionary* attributes = [NSMutableDictionary dictionary]; NSNumber* defaultSize = [NSNumber numberWithLongLong:(2LL * 1024 * 1024 * 1024)]; [attributes setObject:defaultSize forKey:NSFileSystemSize]; [attributes setObject:defaultSize forKey:NSFileSystemFreeSize]; [attributes setObject:defaultSize forKey:NSFileSystemNodes]; [attributes setObject:defaultSize forKey:NSFileSystemFreeNodes]; [attributes setObject:[NSNumber numberWithInt:255] forKey:kGMUserFileSystemVolumeMaxFilenameLengthKey]; [attributes setObject:[NSNumber numberWithInt:4096] forKey:kGMUserFileSystemVolumeFileSystemBlockSizeKey]; // The delegate can override any of the above defaults by implementing the // attributesOfFileSystemForPath selector and returning a custom dictionary. if ([[internal_ delegate] respondsToSelector:@selector(attributesOfFileSystemForPath:error:)]) { *error = nil; NSDictionary* customAttribs = [[internal_ delegate] attributesOfFileSystemForPath:path error:error]; if (!customAttribs) { if (!(*error)) { *error = [GMUserFileSystem errorWithCode:ENODEV]; } return nil; } [attributes addEntriesFromDictionary:customAttribs]; } return attributes; } - (BOOL)setAttributes:(NSDictionary *)attributes ofItemAtPath:(NSString *)path userData:(id)userData error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@, userData=%p, attributes=%@", path, userData, attributes]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } // NOTE: For backward compatibility with version 1.7 and prior. if ([attributes objectForKey:NSFileSize] != nil) { BOOL handled = NO; // Did they have a delegate method that handles truncation? NSNumber* offsetNumber = [attributes objectForKey:NSFileSize]; off_t offset = [offsetNumber longLongValue]; BOOL ret = [self truncateFileAtPath:path fileDelegate:userData offset:offset error:error handled:&handled]; if (handled && (!ret || [attributes count] == 1)) { // Either the truncate call failed, or we only had NSFileSize, so we are done. return ret; } } if ([[internal_ delegate] respondsToSelector:@selector(setAttributes:ofItemAtPath:userData:error:)]) { return [[internal_ delegate] setAttributes:attributes ofItemAtPath:path userData:userData error:error]; } else if ([[internal_ delegate] respondsToSelector:@selector(setAttributes:ofItemAtPath:error:)]) { return [[internal_ delegate] setAttributes:attributes ofItemAtPath:path error:error]; } *error = [GMUserFileSystem errorWithCode:ENODEV]; return NO; } - (BOOL)setAttributes:(NSDictionary *)attributes ofFileSystemAtPath:(NSString *)path error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@, attributes=%@", path, attributes]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } if ([[internal_ delegate] respondsToSelector:@selector(setAttributes:ofFileSystemAtPath:error:)]) { return [[internal_ delegate] setAttributes:attributes ofFileSystemAtPath:path error:error]; } *error = [GMUserFileSystem errorWithCode:ENOSYS]; return NO; } #pragma mark Extended Attributes - (NSArray *)extendedAttributesOfItemAtPath:path error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path)); } if ([[internal_ delegate] respondsToSelector:@selector(extendedAttributesOfItemAtPath:error:)]) { return [[internal_ delegate] extendedAttributesOfItemAtPath:path error:error]; } *error = [GMUserFileSystem errorWithCode:ENOTSUP]; return nil; } - (NSData *)valueOfExtendedAttribute:(NSString *)name ofItemAtPath:(NSString *)path position:(off_t)position error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@, name=%@, position=%lld", path, name, position]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } id delegate = [internal_ delegate]; NSData* data = nil; BOOL xattrSupported = NO; if ([delegate respondsToSelector:@selector(valueOfExtendedAttribute:ofItemAtPath:position:error:)]) { xattrSupported = YES; data = [delegate valueOfExtendedAttribute:name ofItemAtPath:path position:position error:error]; } else if ([delegate respondsToSelector:@selector(valueOfExtendedAttribute:ofItemAtPath:error:)]) { // NOTE: For backward compatibility with version 1.5 and prior. xattrSupported = YES; data = [delegate valueOfExtendedAttribute:name ofItemAtPath:path error:error]; } // On 10.5+ we might supply FinderInfo/ResourceFork as xattr for them. if (!data && [internal_ shouldCheckForResource]) { if ([name isEqualToString:@"com.apple.FinderInfo"]) { NSDictionary* finderAttributes = [self finderAttributesAtPath:path]; data = [self finderDataForAttributes:finderAttributes]; } else if ([name isEqualToString:@"com.apple.ResourceFork"]) { [self isDirectoryIconAtPath:path dirPath:&path]; // Maybe update path. NSDictionary* attributes = [self resourceAttributesAtPath:path]; data = [self resourceDataForAttributes:attributes]; } if (data != nil && position > 0) { // We have all the data, but they are only requesting a subrange. size_t length = [data length]; if (position > length) { *error = [GMUserFileSystem errorWithCode:ERANGE]; return nil; } data = [data subdataWithRange:NSMakeRange(position, length - position)]; } } if (data == nil && *error == nil) { *error = [GMUserFileSystem errorWithCode:xattrSupported ? ENOATTR : ENOTSUP]; } return data; } - (BOOL)setExtendedAttribute:(NSString *)name ofItemAtPath:(NSString *)path value:(NSData *)value position:(off_t)position options:(int)options error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@, name=%@, position=%lld, options=0x%x", path, name, position, options]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } id delegate = [internal_ delegate]; if ([delegate respondsToSelector:@selector(setExtendedAttribute:ofItemAtPath:value:position:options:error:)]) { return [delegate setExtendedAttribute:name ofItemAtPath:path value:value position:position options:options error:error]; } else if ([delegate respondsToSelector:@selector(setExtendedAttribute:ofItemAtPath:value:flags:error:)]) { // NOTE: For backward compatibility with version 1.5 and prior. return [delegate setExtendedAttribute:name ofItemAtPath:path value:value flags:options error:error]; } *error = [GMUserFileSystem errorWithCode:ENOTSUP]; return NO; } - (BOOL)removeExtendedAttribute:(NSString *)name ofItemAtPath:(NSString *)path error:(NSError **)error { if (FUSE4X_OBJC_DELEGATE_ENTRY_ENABLED()) { NSString* traceinfo = [NSString stringWithFormat:@"%@, name=%@", path, name]; FUSE4X_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo)); } id delegate = [internal_ delegate]; if ([delegate respondsToSelector:@selector(removeExtendedAttribute:ofItemAtPath:error:)]) { return [delegate removeExtendedAttribute:name ofItemAtPath:path error:error]; } *error = [GMUserFileSystem errorWithCode:ENOTSUP]; return NO; } #pragma mark FUSE Operations #define MAYBE_USE_ERROR(var, error) \ if ((error) != nil && \ [[(error) domain] isEqualToString:NSPOSIXErrorDomain]) { \ int code = [(error) code]; \ if (code != 0) { \ (var) = -code; \ } \ } static int fusefm_statfs(const char* path, struct statvfs* stbuf) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -ENOENT; @try { memset(stbuf, 0, sizeof(struct statvfs)); NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; if ([fs fillStatvfsBuffer:stbuf forPath:[NSString stringWithUTF8String:path] error:&error]) { ret = 0; } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -ENOENT; @try { memset(stbuf, 0, sizeof(struct stat)); NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; id userData = fi ? (id)(uintptr_t)fi->fh : nil; if ([fs fillStatBuffer:stbuf forPath:[NSString stringWithUTF8String:path] userData:userData error:&error]) { ret = 0; } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_getattr(const char *path, struct stat *stbuf) { return fusefm_fgetattr(path, stbuf, nil); } static int fusefm_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -ENOENT; @try { NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; NSArray *contents = [fs contentsOfDirectoryAtPath:[NSString stringWithUTF8String:path] error:&error]; if (contents) { ret = 0; filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); for (int i = 0, count = [contents count]; i < count; i++) { filler(buf, [[contents objectAtIndex:i] UTF8String], NULL, 0); } } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_create(const char* path, mode_t mode, struct fuse_file_info* fi) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -EACCES; @try { NSError* error = nil; id userData = nil; unsigned long perm = mode & ALLPERMS; NSDictionary* attribs = [NSDictionary dictionaryWithObject:[NSNumber numberWithLong:perm] forKey:NSFilePosixPermissions]; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; if ([fs createFileAtPath:[NSString stringWithUTF8String:path] attributes:attribs userData:&userData error:&error]) { ret = 0; if (userData != nil) { [userData retain]; fi->fh = (uintptr_t)userData; } } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_open(const char *path, struct fuse_file_info *fi) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -ENOENT; // TODO: Default to 0 (success) since a file-system does // not necessarily need to implement open? @try { id userData = nil; NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; if ([fs openFileAtPath:[NSString stringWithUTF8String:path] mode:fi->flags userData:&userData error:&error]) { ret = 0; if (userData != nil) { [userData retain]; fi->fh = (uintptr_t)userData; } } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_release(const char *path, struct fuse_file_info *fi) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; @try { id userData = (id)(uintptr_t)fi->fh; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; [fs releaseFileAtPath:[NSString stringWithUTF8String:path] userData:userData]; if (userData) { [userData release]; } } @catch (id exception) { } [pool release]; return 0; } static int fusefm_fsync(const char* path, int isdatasync, struct fuse_file_info* fi) { // TODO: Support fsync? return 0; } static int fusefm_write(const char* path, const char* buf, size_t size, off_t offset, struct fuse_file_info* fi) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -EIO; @try { NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; ret = [fs writeFileAtPath:[NSString stringWithUTF8String:path] userData:(id)(uintptr_t)fi->fh buffer:buf size:size offset:offset error:&error]; MAYBE_USE_ERROR(ret, error); } @catch (id exception) { } [pool release]; return ret; } static int fusefm_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -EIO; @try { NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; ret = [fs readFileAtPath:[NSString stringWithUTF8String:path] userData:(id)(uintptr_t)fi->fh buffer:buf size:size offset:offset error:&error]; MAYBE_USE_ERROR(ret, error); } @catch (id exception) { } [pool release]; return ret; } static int fusefm_readlink(const char *path, char *buf, size_t size) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -ENOENT; @try { NSString* linkPath = [NSString stringWithUTF8String:path]; NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; NSString *pathContent = [fs destinationOfSymbolicLinkAtPath:linkPath error:&error]; if (pathContent != nil) { ret = 0; [pathContent getFileSystemRepresentation:buf maxLength:size]; } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_getxattr(const char *path, const char *name, char *value, size_t size, uint32_t position) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -ENOATTR; @try { NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; NSData *data = [fs valueOfExtendedAttribute:[NSString stringWithUTF8String:name] ofItemAtPath:[NSString stringWithUTF8String:path] position:position error:&error]; if (data != nil) { ret = [data length]; // default to returning size of buffer. if (value) { if (size > [data length]) { size = [data length]; } [data getBytes:value length:size]; ret = size; // bytes read } } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_setxattr(const char *path, const char *name, const char *value, size_t size, int options, uint32_t position) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -EPERM; @try { NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; if ([fs setExtendedAttribute:[NSString stringWithUTF8String:name] ofItemAtPath:[NSString stringWithUTF8String:path] value:[NSData dataWithBytes:value length:size] position:position options:options error:&error]) { ret = 0; } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_removexattr(const char *path, const char *name) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -ENOATTR; @try { NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; if ([fs removeExtendedAttribute:[NSString stringWithUTF8String:name] ofItemAtPath:[NSString stringWithUTF8String:path] error:&error]) { ret = 0; } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_listxattr(const char *path, char *list, size_t size) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -ENOTSUP; @try { NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; NSArray* attributeNames = [fs extendedAttributesOfItemAtPath:[NSString stringWithUTF8String:path] error:&error]; if (attributeNames != nil) { char zero = 0; NSMutableData* data = [NSMutableData dataWithCapacity:size]; for (int i = 0, count = [attributeNames count]; i < count; i++) { [data appendData:[[attributeNames objectAtIndex:i] dataUsingEncoding:NSUTF8StringEncoding]]; [data appendBytes:&zero length:1]; } ret = [data length]; // default to returning size of buffer. if (list) { if (size > [data length]) { size = [data length]; } [data getBytes:list length:size]; } } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_rename(const char* path, const char* toPath) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -EACCES; @try { NSString* source = [NSString stringWithUTF8String:path]; NSString* destination = [NSString stringWithUTF8String:toPath]; NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; if ([fs moveItemAtPath:source toPath:destination error:&error]) { ret = 0; // Success! } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_mkdir(const char* path, mode_t mode) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -EACCES; @try { NSError* error = nil; unsigned long perm = mode & ALLPERMS; NSDictionary* attribs = [NSDictionary dictionaryWithObject:[NSNumber numberWithLong:perm] forKey:NSFilePosixPermissions]; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; if ([fs createDirectoryAtPath:[NSString stringWithUTF8String:path] attributes:attribs error:(NSError **)error]) { ret = 0; // Success! } else { if (error != nil) { ret = -[error code]; } } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_unlink(const char* path) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -EACCES; @try { NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; if ([fs removeItemAtPath:[NSString stringWithUTF8String:path] error:&error]) { ret = 0; // Success! } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_rmdir(const char* path) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -EACCES; @try { NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; if ([fs removeDirectoryAtPath:[NSString stringWithUTF8String:path] error:&error]) { ret = 0; // Success! } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_symlink(const char* path1, const char* path2) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -EACCES; @try { NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; if ([fs createSymbolicLinkAtPath:[NSString stringWithUTF8String:path2] withDestinationPath:[NSString stringWithUTF8String:path1] error:&error]) { ret = 0; // Success! } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_link(const char* path1, const char* path2) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -EACCES; @try { NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; if ([fs linkItemAtPath:[NSString stringWithUTF8String:path1] toPath:[NSString stringWithUTF8String:path2] error:&error]) { ret = 0; // Success! } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static void* fusefm_init(struct fuse_conn_info* conn) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; [fs retain]; @try { [fs fuseInit]; } @catch (id exception) { } if ([fs enableExtendedTimes]) { FUSE_ENABLE_XTIMES(conn); } #if 0 // TODO: Remove #if 0 if/when setvolname is supported. if ([fs enableSetVolumeName]) { FUSE_ENABLE_SETVOLNAME(conn); } #endif [pool release]; return fs; } static void fusefm_destroy(void* private_data) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; GMUserFileSystem* fs = (GMUserFileSystem *)private_data; @try { [fs fuseDestroy]; } @catch (id exception) { } [fs release]; [pool release]; } static int fusefm_setvolname(const char* name) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -ENOSYS; @try { NSError* error = nil; NSDictionary* attribs = [NSDictionary dictionaryWithObject:[NSString stringWithUTF8String:name] forKey:kGMUserFileSystemVolumeNameKey]; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; if ([fs setAttributes:attribs ofFileSystemAtPath:@"/" error:&error]) { ret = 0; } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_exchange(const char* p1, const char* p2, unsigned long opts) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -ENOSYS; @try { NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; if ([fs exchangeDataOfItemAtPath:[NSString stringWithUTF8String:p1] withItemAtPath:[NSString stringWithUTF8String:p2] error:&error]) { ret = 0; } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_getxtimes(const char* path, struct timespec* bkuptime, struct timespec* crtime) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = -ENOENT; @try { NSError* error = nil; GMUserFileSystem* fs = [GMUserFileSystem currentFS]; NSDictionary* attribs = [fs extendedTimesOfItemAtPath:[NSString stringWithUTF8String:path] userData:nil // TODO: Maybe this should support FH? error:&error]; if (attribs) { ret = 0; NSDate* creationDate = [attribs objectForKey:NSFileCreationDate]; if (creationDate) { const double seconds_dp = [creationDate timeIntervalSince1970]; const time_t t_sec = (time_t) seconds_dp; const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond); const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0; crtime->tv_sec = t_sec; crtime->tv_nsec = t_nsec; } else { memset(crtime, 0, sizeof(*crtime)); } NSDate* backupDate = [attribs objectForKey:kGMUserFileSystemFileBackupDateKey]; if (backupDate) { const double seconds_dp = [backupDate timeIntervalSince1970]; const time_t t_sec = (time_t) seconds_dp; const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond); const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0; bkuptime->tv_sec = t_sec; bkuptime->tv_nsec = t_nsec; } else { memset(bkuptime, 0, sizeof(*bkuptime)); } } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static NSDate* dateWithTimespec(const struct timespec* spec) { const NSTimeInterval time_ns = spec->tv_nsec; const NSTimeInterval time_sec = spec->tv_sec + (time_ns / kNanoSecondsPerSecond); return [NSDate dateWithTimeIntervalSince1970:time_sec]; } static NSDictionary* dictionaryWithAttributes(const struct setattr_x* attrs) { NSMutableDictionary* dict = [NSMutableDictionary dictionary]; if (SETATTR_WANTS_MODE(attrs)) { unsigned long perm = attrs->mode & ALLPERMS; [dict setObject:[NSNumber numberWithLong:perm] forKey:NSFilePosixPermissions]; } if (SETATTR_WANTS_UID(attrs)) { [dict setObject:[NSNumber numberWithLong:attrs->uid] forKey:NSFileOwnerAccountID]; } if (SETATTR_WANTS_GID(attrs)) { [dict setObject:[NSNumber numberWithLong:attrs->gid] forKey:NSFileGroupOwnerAccountID]; } if (SETATTR_WANTS_SIZE(attrs)) { [dict setObject:[NSNumber numberWithLongLong:attrs->size] forKey:NSFileSize]; } if (SETATTR_WANTS_ACCTIME(attrs)) { [dict setObject:dateWithTimespec(&(attrs->acctime)) forKey:kGMUserFileSystemFileAccessDateKey]; } if (SETATTR_WANTS_MODTIME(attrs)) { [dict setObject:dateWithTimespec(&(attrs->modtime)) forKey:NSFileModificationDate]; } if (SETATTR_WANTS_CRTIME(attrs)) { [dict setObject:dateWithTimespec(&(attrs->crtime)) forKey:NSFileCreationDate]; } if (SETATTR_WANTS_CHGTIME(attrs)) { [dict setObject:dateWithTimespec(&(attrs->chgtime)) forKey:kGMUserFileSystemFileChangeDateKey]; } if (SETATTR_WANTS_BKUPTIME(attrs)) { [dict setObject:dateWithTimespec(&(attrs->bkuptime)) forKey:kGMUserFileSystemFileBackupDateKey]; } if (SETATTR_WANTS_FLAGS(attrs)) { [dict setObject:[NSNumber numberWithLong:attrs->flags] forKey:kGMUserFileSystemFileFlagsKey]; } return dict; } static int fusefm_fsetattr_x(const char* path, struct setattr_x* attrs, struct fuse_file_info* fi) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int ret = 0; // NOTE: Return success by default. @try { NSError* error = nil; NSDictionary* attribs = dictionaryWithAttributes(attrs); GMUserFileSystem* fs = [GMUserFileSystem currentFS]; if ([fs setAttributes:attribs ofItemAtPath:[NSString stringWithUTF8String:path] userData:(fi ? (id)(uintptr_t)fi->fh : nil) error:&error]) { ret = 0; } else { MAYBE_USE_ERROR(ret, error); } } @catch (id exception) { } [pool release]; return ret; } static int fusefm_setattr_x(const char* path, struct setattr_x* attrs) { return fusefm_fsetattr_x(path, attrs, nil); } #undef MAYBE_USE_ERROR static struct fuse_operations fusefm_oper = { .init = fusefm_init, .destroy = fusefm_destroy, .statfs = fusefm_statfs, .getattr = fusefm_getattr, .fgetattr = fusefm_fgetattr, .readdir = fusefm_readdir, .open = fusefm_open, .release = fusefm_release, .read = fusefm_read, .readlink = fusefm_readlink, .write = fusefm_write, .create = fusefm_create, .getxattr = fusefm_getxattr, .setxattr = fusefm_setxattr, .removexattr = fusefm_removexattr, .listxattr = fusefm_listxattr, .mkdir = fusefm_mkdir, .unlink = fusefm_unlink, .rmdir = fusefm_rmdir, .symlink = fusefm_symlink, .rename = fusefm_rename, .link = fusefm_link, .fsync = fusefm_fsync, .setvolname = fusefm_setvolname, .exchange = fusefm_exchange, .getxtimes = fusefm_getxtimes, .setattr_x = fusefm_setattr_x, .fsetattr_x = fusefm_fsetattr_x, }; #pragma mark Internal Mount - (void)postMountError:(NSError *)error { assert([internal_ status] == GMUserFileSystem_MOUNTING); [internal_ setStatus:GMUserFileSystem_FAILURE]; NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: [internal_ mountPath], kGMUserFileSystemMountPathKey, error, kGMUserFileSystemErrorKey, nil, nil]; NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; [center postNotificationName:kGMUserFileSystemMountFailed object:self userInfo:userInfo]; } // The stat field member we use to check for a dead file system. #ifdef _DARWIN_FEATURE_64_BIT_INODE #define DEAD_FS_FIELD f_fssubtype #else #define DEAD_FS_FIELD f_reserved1 #endif - (void)mount:(NSDictionary *)args { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; assert([internal_ status] == GMUserFileSystem_NOT_MOUNTED); [internal_ setStatus:GMUserFileSystem_MOUNTING]; NSArray* options = [args objectForKey:@"options"]; BOOL isThreadSafe = [internal_ isThreadSafe]; BOOL shouldForeground = [[args objectForKey:@"shouldForeground"] boolValue]; // Maybe there is a dead fuse FS stuck on our mount point? struct statfs statfs_buf; memset(&statfs_buf, 0, sizeof(statfs_buf)); int rc = statfs([[internal_ mountPath] UTF8String], &statfs_buf); if (rc == 0) { if (statfs_buf.DEAD_FS_FIELD == (short)(-1)) { // We use a special indicator value from Fuse4X in the f_fssubtype field // to indicate that the currently mounted filesystem is dead. It probably // crashed and was never unmounted. // NOTE: If we ever drop 10.4 support, then we can use statfs64 and get // the f_fssubtype field properly here. Until then, it is in f_reserved1. rc = unmount([[internal_ mountPath] UTF8String], 0); if (rc != 0) { NSString* description = @"Unable to unmount an existing 'dead' filesystem."; NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: description, NSLocalizedDescriptionKey, [GMUserFileSystem errorWithCode:errno], NSUnderlyingErrorKey, nil, nil]; NSError* error = [NSError errorWithDomain:kGMUserFileSystemErrorDomain code:GMUserFileSystem_ERROR_UNMOUNT_DEADFS userInfo:userInfo]; [self postMountError:error]; [pool release]; return; } if ([[internal_ mountPath] hasPrefix:@"/Volumes/"]) { // Directories for mounts in @"/Volumes/..." are removed automatically // when an unmount occurs. This is an asynchronous process, so we need // to wait until the directory is removed before proceeding. Otherwise, // it may be removed after we try to create the mount directory and the // mount attempt will fail. BOOL isDirectoryRemoved = NO; static const int kWaitForDeadFSTimeoutSeconds = 5; struct stat stat_buf; for (int i = 0; i < 2 * kWaitForDeadFSTimeoutSeconds; ++i) { usleep(500000); // .5 seconds rc = stat([[internal_ mountPath] UTF8String], &stat_buf); if (rc != 0 && errno == ENOENT) { isDirectoryRemoved = YES; break; } } if (!isDirectoryRemoved) { NSString* description = @"Gave up waiting for directory under /Volumes to be removed after " "cleaning up a dead file system mount."; NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: description, NSLocalizedDescriptionKey, nil, nil]; NSError* error = [NSError errorWithDomain:kGMUserFileSystemErrorDomain code:GMUserFileSystem_ERROR_UNMOUNT_DEADFS_RMDIR userInfo:userInfo]; [self postMountError:error]; [pool release]; return; } } } } // Check and create mount path as necessary. struct stat stat_buf; memset(&stat_buf, 0, sizeof(stat_buf)); rc = stat([[internal_ mountPath] UTF8String], &stat_buf); if (rc == 0) { if (!(stat_buf.st_mode & S_IFDIR)) { [self postMountError:[GMUserFileSystem errorWithCode:ENOTDIR]]; [pool release]; return; } } else { switch (errno) { case ENOTDIR: { [self postMountError:[GMUserFileSystem errorWithCode:ENOTDIR]]; [pool release]; return; } case ENOENT: { // The mount directory does not exists; we'll create as a courtesy. rc = mkdir([[internal_ mountPath] UTF8String], 0775); if (rc != 0) { NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: @"Unable to create directory for mount point.", NSLocalizedDescriptionKey, [GMUserFileSystem errorWithCode:errno], NSUnderlyingErrorKey, nil, nil]; NSError* error = [NSError errorWithDomain:kGMUserFileSystemErrorDomain code:GMUserFileSystem_ERROR_MOUNT_MKDIR userInfo:userInfo]; [self postMountError:error]; [pool release]; return; } break; } } } // Trigger initialization of NSFileManager. This is rather lame, but if we // don't call directoryContents before we mount our FUSE filesystem and // the filesystem uses NSFileManager we may deadlock. It seems that the // NSFileManager class will do lazy init and will query all mounted // filesystems. This leads to deadlock when we re-enter our mounted fuse fs. // Once initialized it seems to work fine. NSFileManager* fileManager = [[[NSFileManager alloc] init] autorelease]; [fileManager contentsOfDirectoryAtPath:@"/Volumes" error:nil]; NSMutableArray* arguments = [NSMutableArray arrayWithObject:[[NSBundle mainBundle] executablePath]]; if (!isThreadSafe) { [arguments addObject:@"-s"]; // Force single-threaded mode. } if (shouldForeground) { [arguments addObject:@"-f"]; // Forground rather than daemonize. } for (int i = 0; i < [options count]; ++i) { NSString* option = [options objectAtIndex:i]; if ([option length] > 0) { [arguments addObject:[NSString stringWithFormat:@"-o%@",option]]; } } [arguments addObject:[internal_ mountPath]]; [args release]; // We don't need packaged up args any more. // Start Fuse Main int argc = [arguments count]; const char* argv[argc]; for (int i = 0, count = [arguments count]; i < count; i++) { NSString* argument = [arguments objectAtIndex:i]; argv[i] = strdup([argument UTF8String]); // We'll just leak this for now. } if ([[internal_ delegate] respondsToSelector:@selector(willMount)]) { [[internal_ delegate] willMount]; } [pool release]; int ret = fuse_main(argc, (char **)argv, &fusefm_oper, self); pool = [[NSAutoreleasePool alloc] init]; if ([internal_ status] == GMUserFileSystem_MOUNTING) { // If we returned from fuse_main while we still think we are // mounting then an error must have occurred during mount. NSString* description = [NSString stringWithFormat:@ "Internal fuse error (rc=%d) while attempting to mount the file system. " "For now, the best way to diagnose is to look for error messages using " "Console.", ret]; NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: description, NSLocalizedDescriptionKey, nil, nil]; NSError* error = [NSError errorWithDomain:kGMUserFileSystemErrorDomain code:GMUserFileSystem_ERROR_MOUNT_FUSE_MAIN_INTERNAL userInfo:userInfo]; [self postMountError:error]; } else { [internal_ setStatus:GMUserFileSystem_NOT_MOUNTED]; } [pool release]; } @end