[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/fuse4x/framework/master/GMUserFileSystem.m [Back]  [Original]

// ================================================================
// 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

Web Proxy Viewer  |  New URL  |  Original Page