PageRenderTime 239ms CodeModel.GetById 8ms app.highlight 217ms RepoModel.GetById 1ms app.codeStats 1ms

/core/sdk-objc/GMUserFileSystem.m

http://macfuse.googlecode.com/
Objective C | 2484 lines | 2062 code | 255 blank | 167 comment | 352 complexity | 1e8431709af237e9cc9653a90d46f833 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1// ================================================================
   2// Copyright (c) 2007, Google Inc.
   3// All rights reserved.
   4//
   5// Redistribution and use in source and binary forms, with or without
   6// modification, are permitted provided that the following conditions are
   7// met:
   8//
   9// * Redistributions of source code must retain the above copyright
  10//   notice, this list of conditions and the following disclaimer.
  11// * Redistributions in binary form must reproduce the above
  12//   copyright notice, this list of conditions and the following disclaimer
  13//   in the documentation and/or other materials provided with the
  14//   distribution.
  15// * Neither the name of Google Inc. nor the names of its
  16//   contributors may be used to endorse or promote products derived from
  17//   this software without specific prior written permission.
  18//
  19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30// ================================================================
  31//
  32//  GMUserFileSystem.m
  33//
  34//  Created by ted on 12/29/07.
  35//  Based on FUSEFileSystem originally by alcor.
  36//
  37#import "GMUserFileSystem.h"
  38
  39#define FUSE_USE_VERSION 26
  40#include <fuse.h>
  41#include <fuse/fuse_darwin.h>
  42
  43#include <string.h>
  44#include <errno.h>
  45#include <fcntl.h>
  46#include <stdio.h>
  47#include <stdlib.h>
  48#include <unistd.h>
  49#include <sys/param.h>
  50#include <sys/mount.h>
  51#include <sys/ioctl.h>
  52#include <sys/sysctl.h>
  53#include <sys/utsname.h>
  54
  55#import <Foundation/Foundation.h>
  56#import "GMAppleDouble.h"
  57#import "GMFinderInfo.h"
  58#import "GMResourceFork.h"
  59#import "GMDataBackedFileDelegate.h"
  60
  61#import "GMDTrace.h"
  62
  63#define GM_EXPORT __attribute__((visibility("default")))
  64
  65// Creates a dtrace-ready string with any newlines removed.
  66#define DTRACE_STRING(s)  \
  67((char *)[[s stringByReplacingOccurrencesOfString:@"\n" withString:@" "] UTF8String])
  68
  69// Notifications
  70GM_EXPORT NSString* const kGMUserFileSystemErrorDomain = @"GMUserFileSystemErrorDomain";
  71GM_EXPORT NSString* const kGMUserFileSystemMountPathKey = @"mountPath";
  72GM_EXPORT NSString* const kGMUserFileSystemErrorKey = @"error";
  73GM_EXPORT NSString* const kGMUserFileSystemMountFailed = @"kGMUserFileSystemMountFailed";
  74GM_EXPORT NSString* const kGMUserFileSystemDidMount = @"kGMUserFileSystemDidMount";
  75GM_EXPORT NSString* const kGMUserFileSystemDidUnmount = @"kGMUserFileSystemDidUnmount";
  76
  77// Attribute keys
  78GM_EXPORT NSString* const kGMUserFileSystemFileFlagsKey = @"kGMUserFileSystemFileFlagsKey";
  79GM_EXPORT NSString* const kGMUserFileSystemFileAccessDateKey = @"kGMUserFileSystemFileAccessDateKey";
  80GM_EXPORT NSString* const kGMUserFileSystemFileChangeDateKey = @"kGMUserFileSystemFileChangeDateKey";
  81GM_EXPORT NSString* const kGMUserFileSystemFileBackupDateKey = @"kGMUserFileSystemFileBackupDateKey";
  82GM_EXPORT NSString* const kGMUserFileSystemVolumeSupportsExtendedDatesKey = @"kGMUserFileSystemVolumeSupportsExtendedDatesKey";
  83
  84// TODO: Remove comment on EXPORT if/when setvolname is supported.
  85/* GM_EXPORT */ NSString* const kGMUserFileSystemVolumeSupportsSetVolumeNameKey = @"kGMUserFileSystemVolumeSupportsSetVolumeNameKey";
  86/* GM_EXPORT */ NSString* const kGMUserFileSystemVolumeNameKey = @"kGMUserFileSystemVolumeNameKey";
  87
  88// FinderInfo and ResourceFork keys
  89GM_EXPORT NSString* const kGMUserFileSystemFinderFlagsKey = @"kGMUserFileSystemFinderFlagsKey";
  90GM_EXPORT NSString* const kGMUserFileSystemFinderExtendedFlagsKey = @"kGMUserFileSystemFinderExtendedFlagsKey";
  91GM_EXPORT NSString* const kGMUserFileSystemCustomIconDataKey = @"kGMUserFileSystemCustomIconDataKey";
  92GM_EXPORT NSString* const kGMUserFileSystemWeblocURLKey = @"kGMUserFileSystemWeblocURLKey";
  93
  94// Used for time conversions to/from tv_nsec.
  95static const double kNanoSecondsPerSecond = 1000000000.0;
  96
  97typedef enum {
  98  // Unable to unmount a dead FUSE files system located at mount point.
  99  GMUserFileSystem_ERROR_UNMOUNT_DEADFS = 1000,
 100  
 101  // Gave up waiting for system removal of existing dir in /Volumes/x after 
 102  // unmounting a dead FUSE file system.
 103  GMUserFileSystem_ERROR_UNMOUNT_DEADFS_RMDIR = 1001,
 104  
 105  // The mount point did not exist, and we were unable to mkdir it.
 106  GMUserFileSystem_ERROR_MOUNT_MKDIR = 1002,
 107  
 108  // fuse_main returned while trying to mount and don't know why.
 109  GMUserFileSystem_ERROR_MOUNT_FUSE_MAIN_INTERNAL = 1003,
 110} GMUserFileSystemErrorCode;
 111
 112typedef enum {
 113  GMUserFileSystem_NOT_MOUNTED,   // Not mounted.
 114  GMUserFileSystem_MOUNTING,      // In the process of mounting.
 115  GMUserFileSystem_INITIALIZING,  // Almost done mounting.
 116  GMUserFileSystem_MOUNTED,       // Confirmed to be mounted.
 117  GMUserFileSystem_UNMOUNTING,    // In the process of unmounting.
 118  GMUserFileSystem_FAILURE,       // Failed state; probably a mount failure.
 119} GMUserFileSystemStatus;
 120
 121@interface GMUserFileSystemInternal : NSObject {
 122  NSString* mountPath_;
 123  GMUserFileSystemStatus status_;
 124  BOOL isTiger_;                  // Are we running on Tiger?
 125  BOOL shouldCheckForResource_;   // Try to handle FinderInfo/Resource Forks?
 126  BOOL isThreadSafe_;  // Is the delegate thread-safe?
 127  BOOL supportsExtendedTimes_;  // Delegate supports create and backup times?
 128  BOOL supportsSetVolumeName_;  // Delegate supports setvolname?
 129  BOOL isReadOnly_;  // Is this mounted read-only?
 130  id delegate_;
 131}
 132- (id)initWithDelegate:(id)delegate isThreadSafe:(BOOL)isThreadSafe;
 133- (void)setDelegate:(id)delegate;
 134@end
 135
 136@implementation GMUserFileSystemInternal
 137
 138- (id)init {
 139  return [self initWithDelegate:nil isThreadSafe:NO];
 140}
 141
 142- (id)initWithDelegate:(id)delegate isThreadSafe:(BOOL)isThreadSafe {
 143  if ((self = [super init])) {
 144    status_ = GMUserFileSystem_NOT_MOUNTED;
 145    isThreadSafe_ = isThreadSafe;
 146    supportsExtendedTimes_ = NO;
 147    supportsSetVolumeName_ = NO;
 148    isReadOnly_ = NO;
 149    [self setDelegate:delegate];
 150
 151    // Version 10.4 requires ._ to appear in directory listings.
 152    long version = fuse_os_version_major_np();
 153    isTiger_ = (version < 9);
 154  }
 155  return self;
 156}
 157- (void)dealloc {
 158  [mountPath_ release];
 159  [super dealloc];
 160}
 161
 162- (NSString *)mountPath { return mountPath_; }
 163- (void)setMountPath:(NSString *)mountPath {
 164  [mountPath_ autorelease];
 165  mountPath_ = [mountPath copy];
 166}
 167- (GMUserFileSystemStatus)status { return status_; }
 168- (void)setStatus:(GMUserFileSystemStatus)status { status_ = status; }
 169- (BOOL)isThreadSafe { return isThreadSafe_; }
 170- (BOOL)supportsExtendedTimes { return supportsExtendedTimes_; }
 171- (void)setSupportsExtendedTimes:(BOOL)val { supportsExtendedTimes_ = val; }
 172- (BOOL)supportsSetVolumeName { return supportsSetVolumeName_; }
 173- (void)setSupportsSetVolumeName:(BOOL)val { supportsSetVolumeName_ = val; }
 174- (BOOL)isTiger { return isTiger_; }
 175- (BOOL)shouldCheckForResource { return shouldCheckForResource_; }
 176- (BOOL)isReadOnly { return isReadOnly_; }
 177- (void)setIsReadOnly:(BOOL)val { isReadOnly_ = val; }
 178- (id)delegate { return delegate_; }
 179- (void)setDelegate:(id)delegate { 
 180  delegate_ = delegate;
 181  shouldCheckForResource_ =
 182    [delegate_ respondsToSelector:@selector(finderAttributesAtPath:error:)] ||
 183    [delegate_ respondsToSelector:@selector(resourceAttributesAtPath:error:)] ||
 184    [delegate_ respondsToSelector:@selector(finderFlagsAtPath:)] ||
 185    [delegate_ respondsToSelector:@selector(iconDataAtPath:)]    ||
 186    [delegate_ respondsToSelector:@selector(URLOfWeblocAtPath:)];
 187  
 188  // Check for deprecated methods.
 189  SEL deprecatedMethods[] = {
 190    @selector(valueOfExtendedAttribute:ofItemAtPath:error:),
 191    @selector(setExtendedAttribute:ofItemAtPath:value:flags:error:),
 192    @selector(finderFlagsAtPath:),
 193    @selector(iconDataAtPath:),
 194    @selector(URLOfWeblocAtPath:),
 195    @selector(truncateFileAtPath:offset:error:),
 196    @selector(attributesOfItemAtPath:error:),
 197    @selector(setAttributes:ofItemAtPath:error:),
 198    @selector(openFileAtPath:mode:fileDelegate:error:),
 199    @selector(createFileAtPath:attributes:fileDelegate:error:),
 200    @selector(releaseFileAtPath:fileDelegate:),
 201    @selector(readFileAtPath:fileDelegate:buffer:size:offset:error:),
 202    @selector(writeFileAtPath:fileDelegate:buffer:size:offset:error:),
 203  };
 204  int i;
 205  for (i = 0; i < sizeof(deprecatedMethods)/sizeof(deprecatedMethods[0]); ++i) {
 206    SEL sel = deprecatedMethods[i];
 207    if ([delegate_ respondsToSelector:sel]) {
 208      NSLog(@"*** WARNING: GMUserFileSystem delegate implements deprecated "
 209            @"selector: %@", NSStringFromSelector(sel));
 210    }
 211  }
 212}
 213
 214@end
 215
 216// Deprecated delegate methods that we still support for backward compatibility
 217// with previously compiled file systems. This will be actively trimmed as 
 218// new releases occur.
 219@interface NSObject (GMUserFileSystemDeprecated)
 220- (NSData *)valueOfExtendedAttribute:(NSString *)name
 221                        ofItemAtPath:(NSString *)path
 222                               error:(NSError **)error;
 223- (BOOL)setExtendedAttribute:(NSString *)name
 224                ofItemAtPath:(NSString *)path
 225                       value:(NSData *)value
 226                       flags:(int)flags
 227                       error:(NSError **)error;
 228- (UInt16)finderFlagsAtPath:(NSString *)path;
 229- (NSData *)iconDataAtPath:(NSString *)path;
 230- (NSURL *)URLOfWeblocAtPath:(NSString *)path;
 231- (BOOL)truncateFileAtPath:(NSString *)path 
 232                    offset:(off_t)offset 
 233                     error:(NSError **)error;
 234- (NSDictionary *)attributesOfItemAtPath:(NSString *)path
 235                                   error:(NSError **)error;
 236- (BOOL)setAttributes:(NSDictionary *)attributes 
 237         ofItemAtPath:(NSString *)path
 238                error:(NSError **)error;
 239- (BOOL)openFileAtPath:(NSString *)path 
 240                  mode:(int)mode
 241          fileDelegate:(id *)fileDelegate
 242                 error:(NSError **)error;
 243- (BOOL)createFileAtPath:(NSString *)path 
 244              attributes:(NSDictionary *)attributes
 245            fileDelegate:(id *)fileDelegate
 246                   error:(NSError **)error;
 247- (void)releaseFileAtPath:(NSString *)path fileDelegate:(id)fileDelegate;
 248- (int)readFileAtPath:(NSString *)path 
 249         fileDelegate:(id)fileDelegate
 250               buffer:(char *)buffer 
 251                 size:(size_t)size 
 252               offset:(off_t)offset
 253                error:(NSError **)error;
 254- (int)writeFileAtPath:(NSString *)path 
 255          fileDelegate:(id)fileDelegate 
 256                buffer:(const char *)buffer
 257                  size:(size_t)size 
 258                offset:(off_t)offset
 259                 error:(NSError **)error;
 260@end
 261
 262@interface GMUserFileSystem (GMUserFileSystemPrivate)
 263
 264// The filesystem for the current thread. Valid only during a fuse callback.
 265+ (GMUserFileSystem *)currentFS;
 266
 267// Convenience method to creates an autoreleased NSError in the 
 268// NSPOSIXErrorDomain. Filesystem errors returned by the delegate must be
 269// standard posix errno values.
 270+ (NSError *)errorWithCode:(int)code;
 271
 272- (void)mount:(NSDictionary *)args;
 273- (void)waitUntilMounted;
 274
 275- (NSDictionary *)finderAttributesAtPath:(NSString *)path;
 276- (NSDictionary *)resourceAttributesAtPath:(NSString *)path;
 277
 278- (BOOL)hasCustomIconAtPath:(NSString *)path;
 279- (BOOL)isDirectoryIconAtPath:(NSString *)path dirPath:(NSString **)dirPath;
 280- (BOOL)isAppleDoubleAtPath:(NSString *)path realPath:(NSString **)realPath;
 281- (NSData *)finderDataForAttributes:(NSDictionary *)attributes;
 282- (NSData *)resourceDataForAttributes:(NSDictionary *)attributes;
 283- (NSData *)appleDoubleContentsAtPath:(NSString *)path;
 284
 285- (NSDictionary *)defaultAttributesOfItemAtPath:(NSString *)path 
 286                                       userData:userData
 287                                          error:(NSError **)error;  
 288- (BOOL)fillStatBuffer:(struct stat *)stbuf 
 289               forPath:(NSString *)path
 290          fileDelegate:(id)fileDelegate
 291                 error:(NSError **)error;
 292- (BOOL)fillStatvfsBuffer:(struct statvfs *)stbuf 
 293                  forPath:(NSString *)path
 294                    error:(NSError **)error;
 295
 296- (void)fuseInit;
 297- (void)fuseDestroy;
 298
 299@end
 300
 301@implementation GMUserFileSystem
 302
 303- (id)init {
 304  return [self initWithDelegate:nil isThreadSafe:NO];
 305}
 306
 307- (id)initWithDelegate:(id)delegate isThreadSafe:(BOOL)isThreadSafe {
 308  if ((self = [super init])) {
 309    internal_ = [[GMUserFileSystemInternal alloc] initWithDelegate:delegate
 310                                                      isThreadSafe:isThreadSafe];
 311  }
 312  return self;
 313}
 314
 315- (void)dealloc {
 316  [internal_ release];
 317  [super dealloc];
 318}
 319
 320- (void)setDelegate:(id)delegate {
 321  [internal_ setDelegate:delegate];
 322}
 323- (id)delegate {
 324  return [internal_ delegate];
 325}
 326
 327- (BOOL)enableExtendedTimes {
 328  return [internal_ supportsExtendedTimes];
 329}
 330- (BOOL)enableSetVolumeName {
 331  return [internal_ supportsSetVolumeName];
 332}
 333
 334- (void)mountAtPath:(NSString *)mountPath 
 335        withOptions:(NSArray *)options {
 336  [self mountAtPath:mountPath
 337        withOptions:options
 338   shouldForeground:YES
 339    detachNewThread:YES];
 340}
 341
 342- (void)mountAtPath:(NSString *)mountPath 
 343        withOptions:(NSArray *)options
 344   shouldForeground:(BOOL)shouldForeground
 345    detachNewThread:(BOOL)detachNewThread {
 346  [internal_ setMountPath:mountPath];
 347  NSMutableArray* optionsCopy = [NSMutableArray array];
 348  for (int i = 0; i < [options count]; ++i) {
 349    NSString* option = [options objectAtIndex:i];
 350    if ([option caseInsensitiveCompare:@"rdonly"] == NSOrderedSame ||
 351        [option caseInsensitiveCompare:@"ro"] == NSOrderedSame) {
 352      [internal_ setIsReadOnly:YES];
 353    }
 354    [optionsCopy addObject:[[option copy] autorelease]];
 355  }
 356  NSDictionary* args = 
 357  [[NSDictionary alloc] initWithObjectsAndKeys:
 358   optionsCopy, @"options",
 359   [NSNumber numberWithBool:shouldForeground], @"shouldForeground", 
 360   nil, nil];
 361  if (detachNewThread) {
 362    [NSThread detachNewThreadSelector:@selector(mount:) 
 363                             toTarget:self 
 364                           withObject:args];
 365  } else {
 366    [self mount:args];
 367  }
 368}
 369
 370- (void)unmount {
 371  if ([internal_ status] == GMUserFileSystem_MOUNTED) {
 372    NSArray* args = [NSArray arrayWithObjects:@"-v", [internal_ mountPath], nil];
 373    NSTask* unmountTask = [NSTask launchedTaskWithLaunchPath:@"/sbin/umount" 
 374                                                   arguments:args];
 375    [unmountTask waitUntilExit];
 376  }
 377}
 378
 379+ (NSError *)errorWithCode:(int)code {
 380  return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:nil];
 381}
 382
 383+ (GMUserFileSystem *)currentFS {
 384  struct fuse_context* context = fuse_get_context();
 385  assert(context);
 386  return (GMUserFileSystem *)context->private_data;
 387}
 388
 389#define FUSEDEVIOCGETHANDSHAKECOMPLETE _IOR('F', 2, u_int32_t)
 390static const int kMaxWaitForMountTries = 50;
 391static const int kWaitForMountUSleepInterval = 100000;  // 100 ms
 392- (void)waitUntilMounted {
 393  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
 394  
 395  for (int i = 0; i < kMaxWaitForMountTries; ++i) {
 396    UInt32 handShakeComplete = 0;
 397    int ret = ioctl(fuse_device_fd_np([[internal_ mountPath] UTF8String]), 
 398                    FUSEDEVIOCGETHANDSHAKECOMPLETE, 
 399                    &handShakeComplete);
 400    if (ret == 0 && handShakeComplete) {
 401      [internal_ setStatus:GMUserFileSystem_MOUNTED];
 402      
 403      // Successfully mounted, so post notification.
 404      NSDictionary* userInfo = 
 405        [NSDictionary dictionaryWithObjectsAndKeys:
 406         [internal_ mountPath], kGMUserFileSystemMountPathKey,
 407         nil, nil];
 408      NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
 409      [center postNotificationName:kGMUserFileSystemDidMount object:self
 410                          userInfo:userInfo];
 411      [pool release];
 412      return;
 413    }
 414    usleep(kWaitForMountUSleepInterval);
 415  }
 416  
 417  // Tried for a long time and no luck :-(
 418  // Unmount and report failure?
 419  [pool release];
 420}
 421
 422- (void)fuseInit {
 423  [internal_ setStatus:GMUserFileSystem_INITIALIZING];
 424
 425  NSError* error = nil;
 426  NSDictionary* attribs = [self attributesOfFileSystemForPath:@"/" error:&error];
 427  if (attribs) {
 428    NSNumber* supports;
 429    supports = [attribs objectForKey:kGMUserFileSystemVolumeSupportsExtendedDatesKey];
 430    if (supports && [supports boolValue]) {
 431      [internal_ setSupportsExtendedTimes:YES];
 432    }
 433    supports = [attribs objectForKey:kGMUserFileSystemVolumeSupportsSetVolumeNameKey];
 434    if (supports && [supports boolValue]) {
 435      [internal_ setSupportsSetVolumeName:YES];
 436    }    
 437  }
 438  
 439  // The mount point won't actually show up until this winds its way
 440  // back through the kernel after this routine returns. In order to post
 441  // the kGMUserFileSystemDidMount notification we start a new thread that will
 442  // poll until it is mounted.
 443  [NSThread detachNewThreadSelector:@selector(waitUntilMounted) 
 444                           toTarget:self 
 445                         withObject:nil];
 446}
 447
 448- (void)fuseDestroy {
 449  if ([[internal_ delegate] respondsToSelector:@selector(willUnmount)]) {
 450    [[internal_ delegate] willUnmount];
 451  }
 452  [internal_ setStatus:GMUserFileSystem_UNMOUNTING];
 453
 454  NSDictionary* userInfo = 
 455    [NSDictionary dictionaryWithObjectsAndKeys:
 456     [internal_ mountPath], kGMUserFileSystemMountPathKey,
 457     nil, nil];
 458  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
 459  [center postNotificationName:kGMUserFileSystemDidUnmount object:self
 460                      userInfo:userInfo];
 461  [internal_ setStatus:GMUserFileSystem_NOT_MOUNTED];
 462}
 463
 464#pragma mark Finder Info, Resource Forks and HFS headers
 465
 466- (NSDictionary *)finderAttributesAtPath:(NSString *)path {
 467  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
 468    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
 469  }
 470
 471  UInt16 flags = 0;
 472
 473  // If a directory icon, we'll make invisible and update the path to parent.
 474  if ([self isDirectoryIconAtPath:path dirPath:&path]) {
 475    flags |= kIsInvisible;
 476  }
 477
 478  id delegate = [internal_ delegate];
 479  if ([delegate respondsToSelector:@selector(finderAttributesAtPath:error:)]) {
 480    NSError* error = nil;
 481    NSDictionary* dict = [delegate finderAttributesAtPath:path error:&error];
 482    if (dict != nil) {
 483      if ([dict objectForKey:kGMUserFileSystemCustomIconDataKey]) {
 484        // They have custom icon data, so make sure the FinderFlags bit is set.
 485        flags |= kHasCustomIcon;
 486      }
 487      if (flags != 0) {
 488        // May need to update kGMUserFileSystemFinderFlagsKey if different.
 489        NSNumber* finderFlags = [dict objectForKey:kGMUserFileSystemFinderFlagsKey];
 490        if (finderFlags != nil) {
 491          UInt16 tmp = (UInt16)[finderFlags longValue];
 492          if (flags == tmp) {
 493            return dict;  // They already have our desired flags.
 494          }          
 495          flags |= tmp;
 496        }
 497        // Doh! We need to create a new dict with the updated flags key.
 498        NSMutableDictionary* newDict = 
 499          [NSMutableDictionary dictionaryWithDictionary:dict];
 500        [newDict setObject:[NSNumber numberWithLong:flags] 
 501                    forKey:kGMUserFileSystemFinderFlagsKey];
 502        return newDict;
 503      }
 504      return dict;
 505    }
 506    // Fall through and create dictionary based on flags if necessary.
 507  } else if ([delegate respondsToSelector:@selector(finderFlagsAtPath:)]) {
 508    flags |= [delegate finderFlagsAtPath:path];
 509  } else if ([delegate respondsToSelector:@selector(iconDataAtPath:)] &&
 510             [delegate iconDataAtPath:path] != nil) {
 511    flags |= kHasCustomIcon;
 512  }
 513  if (flags != 0) {
 514    return [NSDictionary dictionaryWithObject:[NSNumber numberWithLong:flags]
 515                                       forKey:kGMUserFileSystemFinderFlagsKey];
 516  }
 517  return nil;
 518}
 519
 520- (NSDictionary *)resourceAttributesAtPath:(NSString *)path {
 521  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
 522    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
 523  }
 524  
 525  id delegate = [internal_ delegate];
 526  if ([delegate respondsToSelector:@selector(resourceAttributesAtPath:error:)]) {
 527    NSError* error = nil;
 528    return [delegate resourceAttributesAtPath:path error:&error];
 529  }
 530
 531  // Support for deprecated selectors.
 532  NSURL* url = nil;
 533  if ([path hasSuffix:@".webloc"] &&
 534      [delegate respondsToSelector:@selector(URLOfWeblocAtPath:)]) {
 535    url = [delegate URLOfWeblocAtPath:path];
 536  }
 537  NSData* imageData = nil;
 538  if ([delegate respondsToSelector:@selector(iconDataAtPath:)]) {
 539    imageData = [delegate iconDataAtPath:path];
 540  }
 541  if (imageData || url) {
 542    NSMutableDictionary* dict = [NSMutableDictionary dictionary];
 543    if (imageData) {
 544      [dict setObject:imageData forKey:kGMUserFileSystemCustomIconDataKey];
 545    }
 546    if (url) {
 547      [dict setObject:url forKey:kGMUserFileSystemWeblocURLKey];
 548    }
 549    return dict;
 550  }
 551  return nil;
 552}
 553
 554- (BOOL)hasCustomIconAtPath:(NSString *)path {
 555  if ([path isEqualToString:@"/"]) {
 556    return NO;  // For a volume icon they should use the volicon= option.
 557  }
 558  NSDictionary* finderAttribs = [self finderAttributesAtPath:path];
 559  if (finderAttribs) {
 560    NSNumber* finderFlags = 
 561      [finderAttribs objectForKey:kGMUserFileSystemFinderFlagsKey];
 562    if (finderFlags) {
 563      UInt16 flags = (UInt16)[finderFlags longValue];
 564      return (flags & kHasCustomIcon) == kHasCustomIcon;
 565    }
 566  }
 567  return NO;
 568  }
 569
 570- (BOOL)isDirectoryIconAtPath:(NSString *)path dirPath:(NSString **)dirPath {
 571  NSString* name = [path lastPathComponent];
 572  if ([name isEqualToString:@"Icon\r"]) {
 573    if (dirPath) {
 574      *dirPath = [path stringByDeletingLastPathComponent];
 575    }
 576    return YES;
 577  }
 578  return NO;
 579}
 580
 581- (BOOL)isAppleDoubleAtPath:(NSString *)path realPath:(NSString **)realPath {
 582  NSString* name = [path lastPathComponent];
 583  if ([name hasPrefix:@"._"]) {
 584    if (realPath) {
 585      name = [name substringFromIndex:2];
 586      *realPath = [path stringByDeletingLastPathComponent];
 587      *realPath = [*realPath stringByAppendingPathComponent:name];
 588    }
 589    return YES;
 590  }
 591  return NO;
 592}
 593
 594// If the given attribs dictionary contains any FinderInfo attributes then 
 595// returns NSData for FinderInfo; otherwise returns nil.
 596- (NSData *)finderDataForAttributes:(NSDictionary *)attribs {
 597  if (!attribs) { 
 598    return nil;
 599  }
 600
 601  GMFinderInfo* info = [GMFinderInfo finderInfo];
 602  BOOL attributeFound = NO;  // Have we found at least one relevant attribute?
 603
 604  NSNumber* flags = [attribs objectForKey:kGMUserFileSystemFinderFlagsKey];
 605  if (flags) {
 606    attributeFound = YES;
 607    [info setFlags:(UInt16)[flags longValue]];
 608  }
 609  
 610  NSNumber* extendedFlags = 
 611    [attribs objectForKey:kGMUserFileSystemFinderExtendedFlagsKey];
 612  if (extendedFlags) {
 613    attributeFound = YES;
 614    [info setExtendedFlags:(UInt16)[extendedFlags longValue]];
 615  }
 616  
 617  NSNumber* typeCode = [attribs objectForKey:NSFileHFSTypeCode];
 618  if (typeCode) {
 619    attributeFound = YES;
 620    [info setTypeCode:(OSType)[typeCode longValue]];
 621  }
 622
 623  NSNumber* creatorCode = [attribs objectForKey:NSFileHFSCreatorCode];
 624  if (creatorCode) {
 625    attributeFound = YES;
 626    [info setCreatorCode:(OSType)[creatorCode longValue]];
 627  }
 628
 629  return attributeFound ? [info data] : nil;
 630}
 631
 632// If the given attribs dictionary contains any ResourceFork attributes then 
 633// returns NSData for the ResourceFork; otherwise returns nil.
 634- (NSData *)resourceDataForAttributes:(NSDictionary *)attribs {
 635  if (!attribs) {
 636    return nil;
 637  }
 638
 639  GMResourceFork* fork = [GMResourceFork resourceFork];
 640  BOOL attributeFound = NO;  // Have we found at least one relevant attribute?
 641  
 642  NSData* imageData = [attribs objectForKey:kGMUserFileSystemCustomIconDataKey];
 643  if (imageData) {
 644    attributeFound = YES;
 645    [fork addResourceWithType:'icns'
 646                        resID:kCustomIconResource // -16455
 647                         name:nil
 648                         data:imageData];    
 649  }
 650  NSURL* url = [attribs objectForKey:kGMUserFileSystemWeblocURLKey];
 651  if (url) {
 652    attributeFound = YES;
 653    NSString* urlString = [url absoluteString];
 654    NSData* data = [urlString dataUsingEncoding:NSUTF8StringEncoding];
 655    [fork addResourceWithType:'url '
 656                        resID:256
 657                         name:nil
 658                         data:data];
 659  }
 660  return attributeFound ? [fork data] : nil;
 661}
 662
 663// Returns the AppleDouble file contents, if any, for the given path. You should
 664// call this with the realPath out-param from a call to isAppleDoubleAtPath:.
 665//
 666// On 10.5 and (hopefully) above, the Finder will end up using the extended
 667// attributes and so we won't need to serve ._ files. 
 668- (NSData *)appleDoubleContentsAtPath:(NSString *)path {
 669  NSDictionary* finderAttributes = [self finderAttributesAtPath:path];
 670  NSData* finderData = [self finderDataForAttributes:finderAttributes];
 671 
 672  // We treat the ._ for a directory and it's ._Icon\r file the same. This means
 673  // that we'll put extra resource-fork information in directory's ._ file even 
 674  // though it isn't needed. It's worth it given that it only affects 10.4.
 675  [self isDirectoryIconAtPath:path dirPath:&path];
 676
 677  NSDictionary* resourceAttributes = [self resourceAttributesAtPath:path];
 678  NSData* resourceData = [self resourceDataForAttributes:resourceAttributes];
 679  if (finderData != nil || resourceData != nil) {
 680    GMAppleDouble* doubleFile = [GMAppleDouble appleDouble];
 681    if (finderData) {
 682      [doubleFile addEntryWithID:DoubleEntryFinderInfo data:finderData];
 683    }
 684    if (resourceData) {
 685      [doubleFile addEntryWithID:DoubleEntryResourceFork 
 686                            data:resourceData];
 687    }
 688    return [doubleFile data];
 689  }
 690  return nil;
 691}
 692
 693#pragma mark Internal Stat Operations
 694
 695- (BOOL)fillStatvfsBuffer:(struct statvfs *)stbuf 
 696                  forPath:(NSString *)path 
 697                    error:(NSError **)error {
 698  NSDictionary* attributes = [self attributesOfFileSystemForPath:path error:error];
 699  if (!attributes) {
 700    return NO;
 701  }
 702  
 703  // Maximum length of filenames
 704  // TODO: Create our own key so that a fileSystem can override this.
 705  stbuf->f_namemax = 255;
 706  
 707  // Block size
 708  // TODO: Create our own key so that a fileSystem can override this.
 709  stbuf->f_bsize = stbuf->f_frsize = 4096;
 710  
 711  // Size in blocks
 712  NSNumber* size = [attributes objectForKey:NSFileSystemSize];
 713  assert(size);
 714  stbuf->f_blocks = (fsblkcnt_t)([size longLongValue] / stbuf->f_frsize);
 715  
 716  // Number of free / available blocks
 717  NSNumber* freeSize = [attributes objectForKey:NSFileSystemFreeSize];
 718  assert(freeSize);
 719  stbuf->f_bfree = stbuf->f_bavail = 
 720    (fsblkcnt_t)([freeSize longLongValue] / stbuf->f_frsize);
 721  
 722  // Number of nodes
 723  NSNumber* numNodes = [attributes objectForKey:NSFileSystemNodes];
 724  assert(numNodes);
 725  stbuf->f_files = (fsfilcnt_t)[numNodes longLongValue];
 726  
 727  // Number of free / available nodes
 728  NSNumber* freeNodes = [attributes objectForKey:NSFileSystemFreeNodes];
 729  assert(freeNodes);
 730  stbuf->f_ffree = stbuf->f_favail = (fsfilcnt_t)[freeNodes longLongValue];
 731  
 732  return YES;
 733}
 734
 735- (BOOL)fillStatBuffer:(struct stat *)stbuf 
 736               forPath:(NSString *)path 
 737              userData:(id)userData
 738                 error:(NSError **)error {
 739  NSDictionary* attributes = [self defaultAttributesOfItemAtPath:path 
 740                                                        userData:userData
 741                                                           error:error];
 742  if (!attributes) {
 743    return NO;
 744  }
 745
 746  // Inode
 747  NSNumber* inode = [attributes objectForKey:NSFileSystemFileNumber];
 748  if (inode) {
 749    stbuf->st_ino = [inode longLongValue];
 750  }
 751  
 752  // Permissions (mode)
 753  NSNumber* perm = [attributes objectForKey:NSFilePosixPermissions];
 754  stbuf->st_mode = [perm longValue];
 755  NSString* fileType = [attributes objectForKey:NSFileType];
 756  if ([fileType isEqualToString:NSFileTypeDirectory ]) {
 757    stbuf->st_mode |= S_IFDIR;
 758  } else if ([fileType isEqualToString:NSFileTypeRegular]) {
 759    stbuf->st_mode |= S_IFREG;
 760  } else if ([fileType isEqualToString:NSFileTypeSymbolicLink]) {
 761    stbuf->st_mode |= S_IFLNK;
 762  } else {
 763    *error = [GMUserFileSystem errorWithCode:EFTYPE];
 764    return NO;
 765  }
 766  
 767  // Owner and Group
 768  // Note that if the owner or group IDs are not specified, the effective
 769  // user and group IDs for the current process are used as defaults.
 770  NSNumber* uid = [attributes objectForKey:NSFileOwnerAccountID];
 771  NSNumber* gid = [attributes objectForKey:NSFileGroupOwnerAccountID];
 772  stbuf->st_uid = uid ? [uid longValue] : geteuid();
 773  stbuf->st_gid = gid ? [gid longValue] : getegid();
 774
 775  // nlink
 776  NSNumber* nlink = [attributes objectForKey:NSFileReferenceCount];
 777  stbuf->st_nlink = [nlink longValue];
 778
 779  // flags
 780  NSNumber* flags = [attributes objectForKey:kGMUserFileSystemFileFlagsKey];
 781  if (flags) {
 782    stbuf->st_flags = [flags longValue];
 783  } else {
 784    // Just in case they tried to use NSFileImmutable or NSFileAppendOnly
 785    NSNumber* immutableFlag = [attributes objectForKey:NSFileImmutable];
 786    if (immutableFlag && [immutableFlag boolValue]) {
 787      stbuf->st_flags |= UF_IMMUTABLE;
 788    }
 789    NSNumber* appendFlag = [attributes objectForKey:NSFileAppendOnly];
 790    if (appendFlag && [appendFlag boolValue]) {
 791      stbuf->st_flags |= UF_APPEND;
 792    }
 793  }
 794
 795  // NOTE: We default atime,ctime to mtime if it is provided.
 796  NSDate* mdate = [attributes objectForKey:NSFileModificationDate];
 797  if (mdate) {
 798    const double seconds_dp = [mdate timeIntervalSince1970];
 799    const time_t t_sec = (time_t) seconds_dp;
 800    const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond); 
 801    const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0;
 802
 803    stbuf->st_mtimespec.tv_sec = t_sec;
 804    stbuf->st_mtimespec.tv_nsec = t_nsec;
 805    stbuf->st_atimespec = stbuf->st_mtimespec;  // Default to mtime
 806    stbuf->st_ctimespec = stbuf->st_mtimespec;  // Default to mtime
 807  }
 808  NSDate* adate = [attributes objectForKey:kGMUserFileSystemFileAccessDateKey];
 809  if (adate) {
 810    const double seconds_dp = [adate timeIntervalSince1970];
 811    const time_t t_sec = (time_t) seconds_dp;
 812    const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond); 
 813    const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0;
 814    stbuf->st_atimespec.tv_sec = t_sec;
 815    stbuf->st_atimespec.tv_nsec = t_nsec;
 816  }    
 817  NSDate* cdate = [attributes objectForKey:kGMUserFileSystemFileChangeDateKey];
 818  if (cdate) {
 819    const double seconds_dp = [cdate timeIntervalSince1970];
 820    const time_t t_sec = (time_t) seconds_dp;
 821    const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond); 
 822    const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0;
 823    stbuf->st_ctimespec.tv_sec = t_sec;
 824    stbuf->st_ctimespec.tv_nsec = t_nsec;
 825  }
 826
 827#if __DARWIN_64_BIT_INO_T
 828  NSDate* bdate = [attributes objectForKey:NSFileCreationDate];
 829  if (bdate) {
 830    const double seconds_dp = [bdate timeIntervalSince1970];
 831    const time_t t_sec = (time_t) seconds_dp;
 832    const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond); 
 833    const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0;
 834    stbuf->st_birthtimespec.tv_sec = t_sec;
 835    stbuf->st_birthtimespec.tv_nsec = t_nsec;
 836  }
 837#endif
 838
 839  // Size for regular files.
 840  // TODO: Revisit size for directories.
 841  if (![fileType isEqualToString:NSFileTypeDirectory]) {
 842    NSNumber* size = [attributes objectForKey:NSFileSize];
 843    if (size) {
 844      stbuf->st_size = [size longLongValue];
 845    }
 846  }
 847
 848  // Set the number of blocks used so that Finder will display size on disk 
 849  // properly. The man page says that this is in terms of 512 byte blocks.
 850  if (stbuf->st_size > 0) {
 851    stbuf->st_blocks = stbuf->st_size / 512;
 852    if (stbuf->st_size % 512) {
 853      ++(stbuf->st_blocks);
 854    }
 855  }
 856
 857  return YES;  
 858}
 859
 860#pragma mark Moving an Item
 861
 862- (BOOL)moveItemAtPath:(NSString *)source 
 863                toPath:(NSString *)destination
 864                 error:(NSError **)error {
 865  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
 866    NSString* traceinfo = 
 867      [NSString stringWithFormat:@"%@ -> %@", source, destination];
 868    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
 869  }
 870
 871  if ([[internal_ delegate] respondsToSelector:@selector(moveItemAtPath:toPath:error:)]) {
 872    return [[internal_ delegate] moveItemAtPath:source toPath:destination error:error];
 873  }  
 874  
 875  *error = [GMUserFileSystem errorWithCode:EACCES];
 876  return NO;
 877}
 878
 879#pragma mark Removing an Item
 880
 881- (BOOL)removeDirectoryAtPath:(NSString *)path error:(NSError **)error {
 882  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
 883    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
 884  }  
 885
 886  if ([[internal_ delegate] respondsToSelector:@selector(removeDirectoryAtPath:error:)]) {
 887    return [[internal_ delegate] removeDirectoryAtPath:path error:error];
 888  }
 889  return [self removeItemAtPath:path error:error];
 890}
 891
 892- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error {
 893  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
 894    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
 895  }  
 896
 897  if ([[internal_ delegate] respondsToSelector:@selector(removeItemAtPath:error:)]) {
 898    return [[internal_ delegate] removeItemAtPath:path error:error];
 899  }
 900
 901  *error = [GMUserFileSystem errorWithCode:EACCES];
 902  return NO;
 903}
 904
 905#pragma mark Creating an Item
 906
 907- (BOOL)createDirectoryAtPath:(NSString *)path 
 908                   attributes:(NSDictionary *)attributes
 909                        error:(NSError **)error {
 910  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
 911    NSMutableString* traceinfo = 
 912     [NSMutableString stringWithFormat:@"%@ [%@]", path, attributes]; 
 913    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
 914  }
 915  
 916  if ([[internal_ delegate] respondsToSelector:@selector(createDirectoryAtPath:attributes:error:)]) {
 917    return [[internal_ delegate] createDirectoryAtPath:path attributes:attributes error:error];
 918  }
 919
 920  *error = [GMUserFileSystem errorWithCode:EACCES];
 921  return NO;
 922}
 923
 924- (BOOL)createFileAtPath:(NSString *)path 
 925              attributes:(NSDictionary *)attributes
 926                userData:(id *)userData
 927                   error:(NSError **)error {
 928  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
 929    NSString* traceinfo = [NSString stringWithFormat:@"%@ [%@]", path, attributes]; 
 930    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
 931  }
 932
 933  if ([[internal_ delegate] respondsToSelector:@selector(createFileAtPath:attributes:userData:error:)]) {
 934    return [[internal_ delegate] createFileAtPath:path attributes:attributes 
 935                                         userData:userData error:error];
 936  } else if ([[internal_ delegate] respondsToSelector:@selector(createFileAtPath:attributes:fileDelegate:error:)]) {
 937    // NOTE: For backward compatibility with version 1.7 and prior.
 938    return [[internal_ delegate] createFileAtPath:path attributes:attributes 
 939                                     fileDelegate:userData error:error];
 940  }
 941
 942  *error = [GMUserFileSystem errorWithCode:EACCES];
 943  return NO;
 944}
 945
 946
 947#pragma mark Linking an Item
 948
 949- (BOOL)linkItemAtPath:(NSString *)path
 950                toPath:(NSString *)otherPath
 951                 error:(NSError **)error {
 952  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
 953    NSString* traceinfo = [NSString stringWithFormat:@"%@ -> %@", path, otherPath];
 954    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
 955  }
 956
 957  if ([[internal_ delegate] respondsToSelector:@selector(linkItemAtPath:toPath:error:)]) {
 958    return [[internal_ delegate] linkItemAtPath:path toPath:otherPath error:error];
 959  }  
 960
 961  *error = [GMUserFileSystem errorWithCode:ENOTSUP];  // Note: error not in man page.
 962  return NO;
 963}
 964
 965#pragma mark Symbolic Links
 966
 967- (BOOL)createSymbolicLinkAtPath:(NSString *)path 
 968             withDestinationPath:(NSString *)otherPath
 969                           error:(NSError **)error {
 970  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
 971    NSString* traceinfo = [NSString stringWithFormat:@"%@ -> %@", path, otherPath];
 972    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
 973  }  
 974  
 975  if ([[internal_ delegate] respondsToSelector:@selector(createSymbolicLinkAtPath:withDestinationPath:error:)]) {
 976    return [[internal_ delegate] createSymbolicLinkAtPath:path
 977                                      withDestinationPath:otherPath
 978                                                    error:error];
 979  }
 980
 981  *error = [GMUserFileSystem errorWithCode:ENOTSUP];  // Note: error not in man page.
 982  return NO; 
 983}
 984
 985- (NSString *)destinationOfSymbolicLinkAtPath:(NSString *)path
 986                                        error:(NSError **)error {
 987  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
 988    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
 989  }
 990  
 991  if ([[internal_ delegate] respondsToSelector:@selector(destinationOfSymbolicLinkAtPath:error:)]) {
 992    return [[internal_ delegate] destinationOfSymbolicLinkAtPath:path error:error];
 993  }
 994
 995  *error = [GMUserFileSystem errorWithCode:ENOENT];
 996  return nil;
 997}
 998
 999#pragma mark File Contents
1000
1001// NOTE: Only call this if the delegate does indeed support this method.
1002- (NSData *)contentsAtPath:(NSString *)path {
1003  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
1004    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
1005  }
1006
1007  id delegate = [internal_ delegate];
1008  return [delegate contentsAtPath:path];
1009}
1010
1011- (BOOL)openFileAtPath:(NSString *)path 
1012                  mode:(int)mode
1013              userData:(id *)userData 
1014                 error:(NSError **)error {
1015  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
1016    NSString* traceinfo = [NSString stringWithFormat:@"%@, mode=0x%x", path, mode];
1017    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
1018  }
1019
1020  id delegate = [internal_ delegate];
1021  if ([delegate respondsToSelector:@selector(contentsAtPath:)]) {
1022    NSData* data = [self contentsAtPath:path];
1023    if (data != nil) {
1024      *userData = [GMDataBackedFileDelegate fileDelegateWithData:data];
1025      return YES;
1026    }
1027  } else if ([delegate respondsToSelector:@selector(openFileAtPath:mode:userData:error:)]) {
1028    if ([delegate openFileAtPath:path 
1029                            mode:mode 
1030                        userData:userData 
1031                           error:error]) {
1032      return YES;  // They handled it.
1033    }
1034  } else if ([delegate respondsToSelector:@selector(openFileAtPath:mode:fileDelegate:error:)]) {
1035    if ([delegate openFileAtPath:path 
1036                            mode:mode 
1037                    fileDelegate:userData
1038                           error:error]) {
1039      // NOTE: For backward compatibility with version 1.7 and prior.
1040      return YES;  // They handled it.
1041    }
1042  }
1043
1044  // Still unable to open the file; maybe it is an Icon\r or AppleDouble?
1045  if ([internal_ shouldCheckForResource]) {
1046    NSData* data = nil;  // Synthesized data that we provide a file delegate for.
1047
1048    // Is it an Icon\r file that we handle?
1049    if ([self isDirectoryIconAtPath:path dirPath:nil]) {
1050      data = [NSData data];  // The Icon\r file is empty.
1051    }
1052
1053    // (Tiger Only): Maybe it is an AppleDouble file that we handle?
1054    if ([internal_ isTiger]) {
1055      NSString* realPath;
1056      if ([self isAppleDoubleAtPath:path realPath:&realPath]) {
1057        data = [self appleDoubleContentsAtPath:realPath];
1058      }
1059    }
1060    if (data != nil) {
1061      if ((mode & O_ACCMODE) == O_RDONLY) {
1062        *userData = [GMDataBackedFileDelegate fileDelegateWithData:data];
1063      } else {
1064        NSMutableData* mutableData = [NSMutableData dataWithData:data];
1065        *userData = 
1066          [GMMutableDataBackedFileDelegate fileDelegateWithData:mutableData];
1067      }
1068      return YES;  // Handled by a synthesized file delegate.
1069    }
1070  }
1071  
1072  if (*error == nil) {
1073    *error = [GMUserFileSystem errorWithCode:ENOENT];
1074  }
1075  return NO;
1076}
1077
1078- (void)releaseFileAtPath:(NSString *)path userData:(id)userData {
1079  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
1080    NSString* traceinfo =
1081      [NSString stringWithFormat:@"%@, userData=%p", path, userData];
1082    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
1083  }
1084  
1085  if (userData != nil && 
1086      [userData isKindOfClass:[GMDataBackedFileDelegate class]]) {
1087    return;  // Don't report releaseFileAtPath for internal file.
1088  }
1089  if ([[internal_ delegate] respondsToSelector:@selector(releaseFileAtPath:userData:)]) {
1090    [[internal_ delegate] releaseFileAtPath:path userData:userData];
1091  } else if ([[internal_ delegate] respondsToSelector:@selector(releaseFileAtPath:fileDelegate:)]) {
1092    // NOTE: For backward compatibility with version 1.7 and prior.
1093    [[internal_ delegate] releaseFileAtPath:path fileDelegate:userData];
1094  }
1095}
1096
1097- (int)readFileAtPath:(NSString *)path 
1098             userData:(id)userData
1099               buffer:(char *)buffer 
1100                 size:(size_t)size 
1101               offset:(off_t)offset
1102                error:(NSError **)error {
1103  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
1104    NSString* traceinfo =
1105      [NSString stringWithFormat:@"%@, userData=%p, offset=%lld, size=%d", 
1106       path, userData, offset, size];
1107    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
1108  }
1109
1110  if (userData != nil &&
1111      [userData respondsToSelector:@selector(readToBuffer:size:offset:error:)]) {
1112    return [userData readToBuffer:buffer size:size offset:offset error:error];
1113  } else if ([[internal_ delegate] respondsToSelector:@selector(readFileAtPath:userData:buffer:size:offset:error:)]) {
1114    return [[internal_ delegate] readFileAtPath:path
1115                                       userData:userData
1116                                         buffer:buffer
1117                                           size:size
1118                                         offset:offset
1119                                          error:error];
1120  } else if ([[internal_ delegate] respondsToSelector:@selector(readFileAtPath:fileDelegate:buffer:size:offset:error:)]) {
1121    // NOTE: For backward compatibility with version 1.7 and prior.
1122    return [[internal_ delegate] readFileAtPath:path
1123                                   fileDelegate:userData
1124                                         buffer:buffer
1125                                           size:size
1126                                         offset:offset
1127                                          error:error];
1128  }
1129  *error = [GMUserFileSystem errorWithCode:EACCES];
1130  return -1;
1131}
1132
1133- (int)writeFileAtPath:(NSString *)path 
1134              userData:(id)userData
1135                buffer:(const char *)buffer
1136                  size:(size_t)size 
1137                offset:(off_t)offset
1138                 error:(NSError **)error {
1139  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
1140    NSString* traceinfo = 
1141      [NSString stringWithFormat:@"%@, userData=%p, offset=%lld, size=%d", 
1142       path, userData, offset, size];
1143    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
1144  }
1145
1146  if (userData != nil &&
1147      [userData respondsToSelector:@selector(writeFromBuffer:size:offset:error:)]) {
1148    return [userData writeFromBuffer:buffer size:size offset:offset error:error];
1149  } else if ([[internal_ delegate] respondsToSelector:@selector(writeFileAtPath:userData:buffer:size:offset:error:)]) {
1150    return [[internal_ delegate] writeFileAtPath:path
1151                                        userData:userData
1152                                          buffer:buffer
1153                                            size:size
1154                                          offset:offset
1155                                           error:error];
1156  } else if ([[internal_ delegate] respondsToSelector:@selector(writeFileAtPath:fileDelegate:buffer:size:offset:error:)]) {
1157    // NOTE: For backward compatibility with version 1.7 and prior.
1158    return [[internal_ delegate] writeFileAtPath:path
1159                                    fileDelegate:userData
1160                                          buffer:buffer
1161                                            size:size
1162                                          offset:offset
1163                                           error:error];
1164  }
1165  *error = [GMUserFileSystem errorWithCode:EACCES];
1166  return -1; 
1167}
1168
1169// NOTE: For backward compatibility with version 1.7 and prior.
1170- (BOOL)truncateFileAtPath:(NSString *)path
1171              fileDelegate:(id)fileDelegate
1172                    offset:(off_t)offset 
1173                     error:(NSError **)error
1174                   handled:(BOOL*)handled {
1175  if (fileDelegate != nil &&
1176      [fileDelegate respondsToSelector:@selector(truncateToOffset:error:)]) {
1177    *handled = YES;
1178    return [fileDelegate truncateToOffset:offset error:error];
1179  } else if ([[internal_ delegate] respondsToSelector:@selector(truncateFileAtPath:offset:error:)]) {
1180    *handled = YES;
1181    return [[internal_ delegate] truncateFileAtPath:path 
1182                                             offset:offset 
1183                                              error:error];
1184  }
1185  *handled = NO;
1186  return NO;
1187}
1188
1189- (BOOL)exchangeDataOfItemAtPath:(NSString *)path1
1190                  withItemAtPath:(NSString *)path2
1191                           error:(NSError **)error {
1192  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
1193    NSString* traceinfo = [NSString stringWithFormat:@"%@ <-> %@", path1, path2];
1194    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
1195  }
1196
1197  if ([[internal_ delegate] respondsToSelector:@selector(exchangeDataOfItemAtPath:withItemAtPath:error:)]) {
1198    return [[internal_ delegate] exchangeDataOfItemAtPath:path1
1199                                           withItemAtPath:path2
1200                                                    error:error];
1201  }  
1202  *error = [GMUserFileSystem errorWithCode:ENOSYS];
1203  return NO;
1204}
1205
1206#pragma mark Directory Contents
1207
1208- (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error {
1209  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
1210    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
1211  }
1212
1213  NSArray* contents = nil;
1214  if ([[internal_ delegate] respondsToSelector:@selector(contentsOfDirectoryAtPath:error:)]) {
1215    contents = [[internal_ delegate] contentsOfDirectoryAtPath:path error:error];
1216  } else if ([path isEqualToString:@"/"]) {
1217    contents = [NSArray array];  // Give them an empty root directory for free.
1218  }
1219  if (contents != nil && 
1220      [internal_ isTiger] &&
1221      [internal_ shouldCheckForResource]) {
1222    // Note: Tiger (10.4) requires that the ._ file are explicitly listed in 
1223    // the directory contents if you want a custom icon to show up. If they
1224    // don't provide their own ._ file and they have a custom icon, then we'll
1225    // add the ._ file to the directory contents.
1226    NSMutableSet* fullContents = [NSMutableSet setWithArray:contents];
1227    for (int i = 0; i < [contents count]; ++i) {
1228      NSString* name = [contents objectAtIndex:i];
1229      if ([name hasPrefix:@"._"]) {
1230        continue;  // Skip over any AppleDouble that they provide.
1231      }
1232      NSString* doubleName = [NSString stringWithFormat:@"._%@", name];
1233      if ([fullContents containsObject:doubleName]) {
1234        continue;  // They provided their own AppleDouble for 'name'.
1235      }
1236      NSString* pathPlusName = [path stringByAppendingPathComponent:name];
1237      if ([self hasCustomIconAtPath:pathPlusName]) {
1238        [fullContents addObject:doubleName];
1239      }
1240    }
1241    if ([self hasCustomIconAtPath:path]) {
1242      [fullContents addObject:@"Icon\r"];
1243      [fullContents addObject:@"._Icon\r"];
1244    }
1245    contents = [fullContents allObjects];
1246  }
1247  return contents;
1248}
1249
1250#pragma mark Getting and Setting Attributes
1251
1252- (BOOL)supportsAttributesOfItemAtPath {
1253  id delegate = [internal_ delegate];
1254  return [delegate respondsToSelector:@selector(attributesOfItemAtPath:userData:error:)] ||
1255         [delegate respondsToSelector:@selector(attributesOfItemAtPath:error:)];
1256}
1257
1258- (NSDictionary *)attributesOfItemAtPath:(NSString *)path
1259                                userData:userData
1260                                   error:(NSError **)error {
1261  if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
1262    NSString* traceinfo =
1263      [NSString stringWithFormat:@"%@, userData=%p", path, userData];
1264    MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
1265  }
1266
1267  id delegate = [internal_ delegate];
1268  if ([delegate respondsToSelector:@selector(attributesOfItemAtPath:userData:error:)]) {
1269    return [delegate attributesOfItemAtPath:path userData:userData error:error];
1270  } else if ([delegate respondsToSelector:@selector(attributesOfItemAtPath:error:)]) {
1271    return [delegate attributesOfItemAtPath:path error:error];
1272  }
1273  return nil;
1274}
1275
1276// Get attributesOfItemAtPath from the delegate with default values.
1277- (NSDictionary *)defaultAttributesOfItemAtPath:(NSString *)path 
1278                                       userData:userData
1279                                          error:(NSError **)error {
1280  // Set up default item attributes.
1281  NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
1282  BOOL isReadOnly = [internal_ isReadOnly];
1283  [attributes setObject:[NSNumber numberWithLong:(isReadOnly ? 0555 : 0775)]
1284                 forKey:NSFilePosixPermissions];
1285  [attributes setObject:[NSNumber numberWithLong:1]
1286                 forKey:NSFileReferenceCount];    // 1 means "don't know"
1287  if ([path isEqualToString:@"/"]) {
1288    [attributes setObject:NSFileTypeDirectory forKey:NSFileType];
1289  } else {
1290    [attributes setObject:NSFileTypeRegular forKey:NSFileType];
1291  }
1292  
1293  id delegate = [internal_ delegate];
1294  BOOL isAppleDouble = NO;   // May only be set to YES on Tiger.
1295  BOOL isDirectoryIcon = NO;
1296
1297  // The delegate can override any of the above defaults by implementing the
1298  // attributesOfItemAtPath: selector and returning a custom dictionary.
1299  NSDictionary* customAttribs = nil;
1300  BOOL supportsAttributesSelector = [self supportsAttributesOfItemAtPath];
1301  if (supportsAttribute

Large files files are truncated, but you can click here to view the full file