PageRenderTime 60ms CodeModel.GetById 14ms RepoModel.GetById 0ms 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
Possible License(s): Apache-2.0, BSD-3-Clause, GPL-2.0
  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. #define FUSE_USE_VERSION 26
  39. #include <fuse.h>
  40. #include <fuse/fuse_darwin.h>
  41. #include <string.h>
  42. #include <errno.h>
  43. #include <fcntl.h>
  44. #include <stdio.h>
  45. #include <stdlib.h>
  46. #include <unistd.h>
  47. #include <sys/param.h>
  48. #include <sys/mount.h>
  49. #include <sys/ioctl.h>
  50. #include <sys/sysctl.h>
  51. #include <sys/utsname.h>
  52. #import <Foundation/Foundation.h>
  53. #import "GMAppleDouble.h"
  54. #import "GMFinderInfo.h"
  55. #import "GMResourceFork.h"
  56. #import "GMDataBackedFileDelegate.h"
  57. #import "GMDTrace.h"
  58. #define GM_EXPORT __attribute__((visibility("default")))
  59. // Creates a dtrace-ready string with any newlines removed.
  60. #define DTRACE_STRING(s) \
  61. ((char *)[[s stringByReplacingOccurrencesOfString:@"\n" withString:@" "] UTF8String])
  62. // Notifications
  63. GM_EXPORT NSString* const kGMUserFileSystemErrorDomain = @"GMUserFileSystemErrorDomain";
  64. GM_EXPORT NSString* const kGMUserFileSystemMountPathKey = @"mountPath";
  65. GM_EXPORT NSString* const kGMUserFileSystemErrorKey = @"error";
  66. GM_EXPORT NSString* const kGMUserFileSystemMountFailed = @"kGMUserFileSystemMountFailed";
  67. GM_EXPORT NSString* const kGMUserFileSystemDidMount = @"kGMUserFileSystemDidMount";
  68. GM_EXPORT NSString* const kGMUserFileSystemDidUnmount = @"kGMUserFileSystemDidUnmount";
  69. // Attribute keys
  70. GM_EXPORT NSString* const kGMUserFileSystemFileFlagsKey = @"kGMUserFileSystemFileFlagsKey";
  71. GM_EXPORT NSString* const kGMUserFileSystemFileAccessDateKey = @"kGMUserFileSystemFileAccessDateKey";
  72. GM_EXPORT NSString* const kGMUserFileSystemFileChangeDateKey = @"kGMUserFileSystemFileChangeDateKey";
  73. GM_EXPORT NSString* const kGMUserFileSystemFileBackupDateKey = @"kGMUserFileSystemFileBackupDateKey";
  74. GM_EXPORT NSString* const kGMUserFileSystemVolumeSupportsExtendedDatesKey = @"kGMUserFileSystemVolumeSupportsExtendedDatesKey";
  75. // TODO: Remove comment on EXPORT if/when setvolname is supported.
  76. /* GM_EXPORT */ NSString* const kGMUserFileSystemVolumeSupportsSetVolumeNameKey = @"kGMUserFileSystemVolumeSupportsSetVolumeNameKey";
  77. /* GM_EXPORT */ NSString* const kGMUserFileSystemVolumeNameKey = @"kGMUserFileSystemVolumeNameKey";
  78. // FinderInfo and ResourceFork keys
  79. GM_EXPORT NSString* const kGMUserFileSystemFinderFlagsKey = @"kGMUserFileSystemFinderFlagsKey";
  80. GM_EXPORT NSString* const kGMUserFileSystemFinderExtendedFlagsKey = @"kGMUserFileSystemFinderExtendedFlagsKey";
  81. GM_EXPORT NSString* const kGMUserFileSystemCustomIconDataKey = @"kGMUserFileSystemCustomIconDataKey";
  82. GM_EXPORT NSString* const kGMUserFileSystemWeblocURLKey = @"kGMUserFileSystemWeblocURLKey";
  83. // Used for time conversions to/from tv_nsec.
  84. static const double kNanoSecondsPerSecond = 1000000000.0;
  85. typedef enum {
  86. // Unable to unmount a dead FUSE files system located at mount point.
  87. GMUserFileSystem_ERROR_UNMOUNT_DEADFS = 1000,
  88. // Gave up waiting for system removal of existing dir in /Volumes/x after
  89. // unmounting a dead FUSE file system.
  90. GMUserFileSystem_ERROR_UNMOUNT_DEADFS_RMDIR = 1001,
  91. // The mount point did not exist, and we were unable to mkdir it.
  92. GMUserFileSystem_ERROR_MOUNT_MKDIR = 1002,
  93. // fuse_main returned while trying to mount and don't know why.
  94. GMUserFileSystem_ERROR_MOUNT_FUSE_MAIN_INTERNAL = 1003,
  95. } GMUserFileSystemErrorCode;
  96. typedef enum {
  97. GMUserFileSystem_NOT_MOUNTED, // Not mounted.
  98. GMUserFileSystem_MOUNTING, // In the process of mounting.
  99. GMUserFileSystem_INITIALIZING, // Almost done mounting.
  100. GMUserFileSystem_MOUNTED, // Confirmed to be mounted.
  101. GMUserFileSystem_UNMOUNTING, // In the process of unmounting.
  102. GMUserFileSystem_FAILURE, // Failed state; probably a mount failure.
  103. } GMUserFileSystemStatus;
  104. @interface GMUserFileSystemInternal : NSObject {
  105. NSString* mountPath_;
  106. GMUserFileSystemStatus status_;
  107. BOOL isTiger_; // Are we running on Tiger?
  108. BOOL shouldCheckForResource_; // Try to handle FinderInfo/Resource Forks?
  109. BOOL isThreadSafe_; // Is the delegate thread-safe?
  110. BOOL supportsExtendedTimes_; // Delegate supports create and backup times?
  111. BOOL supportsSetVolumeName_; // Delegate supports setvolname?
  112. BOOL isReadOnly_; // Is this mounted read-only?
  113. id delegate_;
  114. }
  115. - (id)initWithDelegate:(id)delegate isThreadSafe:(BOOL)isThreadSafe;
  116. - (void)setDelegate:(id)delegate;
  117. @end
  118. @implementation GMUserFileSystemInternal
  119. - (id)init {
  120. return [self initWithDelegate:nil isThreadSafe:NO];
  121. }
  122. - (id)initWithDelegate:(id)delegate isThreadSafe:(BOOL)isThreadSafe {
  123. if ((self = [super init])) {
  124. status_ = GMUserFileSystem_NOT_MOUNTED;
  125. isThreadSafe_ = isThreadSafe;
  126. supportsExtendedTimes_ = NO;
  127. supportsSetVolumeName_ = NO;
  128. isReadOnly_ = NO;
  129. [self setDelegate:delegate];
  130. // Version 10.4 requires ._ to appear in directory listings.
  131. long version = fuse_os_version_major_np();
  132. isTiger_ = (version < 9);
  133. }
  134. return self;
  135. }
  136. - (void)dealloc {
  137. [mountPath_ release];
  138. [super dealloc];
  139. }
  140. - (NSString *)mountPath { return mountPath_; }
  141. - (void)setMountPath:(NSString *)mountPath {
  142. [mountPath_ autorelease];
  143. mountPath_ = [mountPath copy];
  144. }
  145. - (GMUserFileSystemStatus)status { return status_; }
  146. - (void)setStatus:(GMUserFileSystemStatus)status { status_ = status; }
  147. - (BOOL)isThreadSafe { return isThreadSafe_; }
  148. - (BOOL)supportsExtendedTimes { return supportsExtendedTimes_; }
  149. - (void)setSupportsExtendedTimes:(BOOL)val { supportsExtendedTimes_ = val; }
  150. - (BOOL)supportsSetVolumeName { return supportsSetVolumeName_; }
  151. - (void)setSupportsSetVolumeName:(BOOL)val { supportsSetVolumeName_ = val; }
  152. - (BOOL)isTiger { return isTiger_; }
  153. - (BOOL)shouldCheckForResource { return shouldCheckForResource_; }
  154. - (BOOL)isReadOnly { return isReadOnly_; }
  155. - (void)setIsReadOnly:(BOOL)val { isReadOnly_ = val; }
  156. - (id)delegate { return delegate_; }
  157. - (void)setDelegate:(id)delegate {
  158. delegate_ = delegate;
  159. shouldCheckForResource_ =
  160. [delegate_ respondsToSelector:@selector(finderAttributesAtPath:error:)] ||
  161. [delegate_ respondsToSelector:@selector(resourceAttributesAtPath:error:)] ||
  162. [delegate_ respondsToSelector:@selector(finderFlagsAtPath:)] ||
  163. [delegate_ respondsToSelector:@selector(iconDataAtPath:)] ||
  164. [delegate_ respondsToSelector:@selector(URLOfWeblocAtPath:)];
  165. // Check for deprecated methods.
  166. SEL deprecatedMethods[] = {
  167. @selector(valueOfExtendedAttribute:ofItemAtPath:error:),
  168. @selector(setExtendedAttribute:ofItemAtPath:value:flags:error:),
  169. @selector(finderFlagsAtPath:),
  170. @selector(iconDataAtPath:),
  171. @selector(URLOfWeblocAtPath:),
  172. @selector(truncateFileAtPath:offset:error:),
  173. @selector(attributesOfItemAtPath:error:),
  174. @selector(setAttributes:ofItemAtPath:error:),
  175. @selector(openFileAtPath:mode:fileDelegate:error:),
  176. @selector(createFileAtPath:attributes:fileDelegate:error:),
  177. @selector(releaseFileAtPath:fileDelegate:),
  178. @selector(readFileAtPath:fileDelegate:buffer:size:offset:error:),
  179. @selector(writeFileAtPath:fileDelegate:buffer:size:offset:error:),
  180. };
  181. int i;
  182. for (i = 0; i < sizeof(deprecatedMethods)/sizeof(deprecatedMethods[0]); ++i) {
  183. SEL sel = deprecatedMethods[i];
  184. if ([delegate_ respondsToSelector:sel]) {
  185. NSLog(@"*** WARNING: GMUserFileSystem delegate implements deprecated "
  186. @"selector: %@", NSStringFromSelector(sel));
  187. }
  188. }
  189. }
  190. @end
  191. // Deprecated delegate methods that we still support for backward compatibility
  192. // with previously compiled file systems. This will be actively trimmed as
  193. // new releases occur.
  194. @interface NSObject (GMUserFileSystemDeprecated)
  195. - (NSData *)valueOfExtendedAttribute:(NSString *)name
  196. ofItemAtPath:(NSString *)path
  197. error:(NSError **)error;
  198. - (BOOL)setExtendedAttribute:(NSString *)name
  199. ofItemAtPath:(NSString *)path
  200. value:(NSData *)value
  201. flags:(int)flags
  202. error:(NSError **)error;
  203. - (UInt16)finderFlagsAtPath:(NSString *)path;
  204. - (NSData *)iconDataAtPath:(NSString *)path;
  205. - (NSURL *)URLOfWeblocAtPath:(NSString *)path;
  206. - (BOOL)truncateFileAtPath:(NSString *)path
  207. offset:(off_t)offset
  208. error:(NSError **)error;
  209. - (NSDictionary *)attributesOfItemAtPath:(NSString *)path
  210. error:(NSError **)error;
  211. - (BOOL)setAttributes:(NSDictionary *)attributes
  212. ofItemAtPath:(NSString *)path
  213. error:(NSError **)error;
  214. - (BOOL)openFileAtPath:(NSString *)path
  215. mode:(int)mode
  216. fileDelegate:(id *)fileDelegate
  217. error:(NSError **)error;
  218. - (BOOL)createFileAtPath:(NSString *)path
  219. attributes:(NSDictionary *)attributes
  220. fileDelegate:(id *)fileDelegate
  221. error:(NSError **)error;
  222. - (void)releaseFileAtPath:(NSString *)path fileDelegate:(id)fileDelegate;
  223. - (int)readFileAtPath:(NSString *)path
  224. fileDelegate:(id)fileDelegate
  225. buffer:(char *)buffer
  226. size:(size_t)size
  227. offset:(off_t)offset
  228. error:(NSError **)error;
  229. - (int)writeFileAtPath:(NSString *)path
  230. fileDelegate:(id)fileDelegate
  231. buffer:(const char *)buffer
  232. size:(size_t)size
  233. offset:(off_t)offset
  234. error:(NSError **)error;
  235. @end
  236. @interface GMUserFileSystem (GMUserFileSystemPrivate)
  237. // The filesystem for the current thread. Valid only during a fuse callback.
  238. + (GMUserFileSystem *)currentFS;
  239. // Convenience method to creates an autoreleased NSError in the
  240. // NSPOSIXErrorDomain. Filesystem errors returned by the delegate must be
  241. // standard posix errno values.
  242. + (NSError *)errorWithCode:(int)code;
  243. - (void)mount:(NSDictionary *)args;
  244. - (void)waitUntilMounted;
  245. - (NSDictionary *)finderAttributesAtPath:(NSString *)path;
  246. - (NSDictionary *)resourceAttributesAtPath:(NSString *)path;
  247. - (BOOL)hasCustomIconAtPath:(NSString *)path;
  248. - (BOOL)isDirectoryIconAtPath:(NSString *)path dirPath:(NSString **)dirPath;
  249. - (BOOL)isAppleDoubleAtPath:(NSString *)path realPath:(NSString **)realPath;
  250. - (NSData *)finderDataForAttributes:(NSDictionary *)attributes;
  251. - (NSData *)resourceDataForAttributes:(NSDictionary *)attributes;
  252. - (NSData *)appleDoubleContentsAtPath:(NSString *)path;
  253. - (NSDictionary *)defaultAttributesOfItemAtPath:(NSString *)path
  254. userData:userData
  255. error:(NSError **)error;
  256. - (BOOL)fillStatBuffer:(struct stat *)stbuf
  257. forPath:(NSString *)path
  258. fileDelegate:(id)fileDelegate
  259. error:(NSError **)error;
  260. - (BOOL)fillStatvfsBuffer:(struct statvfs *)stbuf
  261. forPath:(NSString *)path
  262. error:(NSError **)error;
  263. - (void)fuseInit;
  264. - (void)fuseDestroy;
  265. @end
  266. @implementation GMUserFileSystem
  267. - (id)init {
  268. return [self initWithDelegate:nil isThreadSafe:NO];
  269. }
  270. - (id)initWithDelegate:(id)delegate isThreadSafe:(BOOL)isThreadSafe {
  271. if ((self = [super init])) {
  272. internal_ = [[GMUserFileSystemInternal alloc] initWithDelegate:delegate
  273. isThreadSafe:isThreadSafe];
  274. }
  275. return self;
  276. }
  277. - (void)dealloc {
  278. [internal_ release];
  279. [super dealloc];
  280. }
  281. - (void)setDelegate:(id)delegate {
  282. [internal_ setDelegate:delegate];
  283. }
  284. - (id)delegate {
  285. return [internal_ delegate];
  286. }
  287. - (BOOL)enableExtendedTimes {
  288. return [internal_ supportsExtendedTimes];
  289. }
  290. - (BOOL)enableSetVolumeName {
  291. return [internal_ supportsSetVolumeName];
  292. }
  293. - (void)mountAtPath:(NSString *)mountPath
  294. withOptions:(NSArray *)options {
  295. [self mountAtPath:mountPath
  296. withOptions:options
  297. shouldForeground:YES
  298. detachNewThread:YES];
  299. }
  300. - (void)mountAtPath:(NSString *)mountPath
  301. withOptions:(NSArray *)options
  302. shouldForeground:(BOOL)shouldForeground
  303. detachNewThread:(BOOL)detachNewThread {
  304. [internal_ setMountPath:mountPath];
  305. NSMutableArray* optionsCopy = [NSMutableArray array];
  306. for (int i = 0; i < [options count]; ++i) {
  307. NSString* option = [options objectAtIndex:i];
  308. if ([option caseInsensitiveCompare:@"rdonly"] == NSOrderedSame ||
  309. [option caseInsensitiveCompare:@"ro"] == NSOrderedSame) {
  310. [internal_ setIsReadOnly:YES];
  311. }
  312. [optionsCopy addObject:[[option copy] autorelease]];
  313. }
  314. NSDictionary* args =
  315. [[NSDictionary alloc] initWithObjectsAndKeys:
  316. optionsCopy, @"options",
  317. [NSNumber numberWithBool:shouldForeground], @"shouldForeground",
  318. nil, nil];
  319. if (detachNewThread) {
  320. [NSThread detachNewThreadSelector:@selector(mount:)
  321. toTarget:self
  322. withObject:args];
  323. } else {
  324. [self mount:args];
  325. }
  326. }
  327. - (void)unmount {
  328. if ([internal_ status] == GMUserFileSystem_MOUNTED) {
  329. NSArray* args = [NSArray arrayWithObjects:@"-v", [internal_ mountPath], nil];
  330. NSTask* unmountTask = [NSTask launchedTaskWithLaunchPath:@"/sbin/umount"
  331. arguments:args];
  332. [unmountTask waitUntilExit];
  333. }
  334. }
  335. + (NSError *)errorWithCode:(int)code {
  336. return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:nil];
  337. }
  338. + (GMUserFileSystem *)currentFS {
  339. struct fuse_context* context = fuse_get_context();
  340. assert(context);
  341. return (GMUserFileSystem *)context->private_data;
  342. }
  343. #define FUSEDEVIOCGETHANDSHAKECOMPLETE _IOR('F', 2, u_int32_t)
  344. static const int kMaxWaitForMountTries = 50;
  345. static const int kWaitForMountUSleepInterval = 100000; // 100 ms
  346. - (void)waitUntilMounted {
  347. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  348. for (int i = 0; i < kMaxWaitForMountTries; ++i) {
  349. UInt32 handShakeComplete = 0;
  350. int ret = ioctl(fuse_device_fd_np([[internal_ mountPath] UTF8String]),
  351. FUSEDEVIOCGETHANDSHAKECOMPLETE,
  352. &handShakeComplete);
  353. if (ret == 0 && handShakeComplete) {
  354. [internal_ setStatus:GMUserFileSystem_MOUNTED];
  355. // Successfully mounted, so post notification.
  356. NSDictionary* userInfo =
  357. [NSDictionary dictionaryWithObjectsAndKeys:
  358. [internal_ mountPath], kGMUserFileSystemMountPathKey,
  359. nil, nil];
  360. NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  361. [center postNotificationName:kGMUserFileSystemDidMount object:self
  362. userInfo:userInfo];
  363. [pool release];
  364. return;
  365. }
  366. usleep(kWaitForMountUSleepInterval);
  367. }
  368. // Tried for a long time and no luck :-(
  369. // Unmount and report failure?
  370. [pool release];
  371. }
  372. - (void)fuseInit {
  373. [internal_ setStatus:GMUserFileSystem_INITIALIZING];
  374. NSError* error = nil;
  375. NSDictionary* attribs = [self attributesOfFileSystemForPath:@"/" error:&error];
  376. if (attribs) {
  377. NSNumber* supports;
  378. supports = [attribs objectForKey:kGMUserFileSystemVolumeSupportsExtendedDatesKey];
  379. if (supports && [supports boolValue]) {
  380. [internal_ setSupportsExtendedTimes:YES];
  381. }
  382. supports = [attribs objectForKey:kGMUserFileSystemVolumeSupportsSetVolumeNameKey];
  383. if (supports && [supports boolValue]) {
  384. [internal_ setSupportsSetVolumeName:YES];
  385. }
  386. }
  387. // The mount point won't actually show up until this winds its way
  388. // back through the kernel after this routine returns. In order to post
  389. // the kGMUserFileSystemDidMount notification we start a new thread that will
  390. // poll until it is mounted.
  391. [NSThread detachNewThreadSelector:@selector(waitUntilMounted)
  392. toTarget:self
  393. withObject:nil];
  394. }
  395. - (void)fuseDestroy {
  396. if ([[internal_ delegate] respondsToSelector:@selector(willUnmount)]) {
  397. [[internal_ delegate] willUnmount];
  398. }
  399. [internal_ setStatus:GMUserFileSystem_UNMOUNTING];
  400. NSDictionary* userInfo =
  401. [NSDictionary dictionaryWithObjectsAndKeys:
  402. [internal_ mountPath], kGMUserFileSystemMountPathKey,
  403. nil, nil];
  404. NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  405. [center postNotificationName:kGMUserFileSystemDidUnmount object:self
  406. userInfo:userInfo];
  407. [internal_ setStatus:GMUserFileSystem_NOT_MOUNTED];
  408. }
  409. #pragma mark Finder Info, Resource Forks and HFS headers
  410. - (NSDictionary *)finderAttributesAtPath:(NSString *)path {
  411. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  412. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
  413. }
  414. UInt16 flags = 0;
  415. // If a directory icon, we'll make invisible and update the path to parent.
  416. if ([self isDirectoryIconAtPath:path dirPath:&path]) {
  417. flags |= kIsInvisible;
  418. }
  419. id delegate = [internal_ delegate];
  420. if ([delegate respondsToSelector:@selector(finderAttributesAtPath:error:)]) {
  421. NSError* error = nil;
  422. NSDictionary* dict = [delegate finderAttributesAtPath:path error:&error];
  423. if (dict != nil) {
  424. if ([dict objectForKey:kGMUserFileSystemCustomIconDataKey]) {
  425. // They have custom icon data, so make sure the FinderFlags bit is set.
  426. flags |= kHasCustomIcon;
  427. }
  428. if (flags != 0) {
  429. // May need to update kGMUserFileSystemFinderFlagsKey if different.
  430. NSNumber* finderFlags = [dict objectForKey:kGMUserFileSystemFinderFlagsKey];
  431. if (finderFlags != nil) {
  432. UInt16 tmp = (UInt16)[finderFlags longValue];
  433. if (flags == tmp) {
  434. return dict; // They already have our desired flags.
  435. }
  436. flags |= tmp;
  437. }
  438. // Doh! We need to create a new dict with the updated flags key.
  439. NSMutableDictionary* newDict =
  440. [NSMutableDictionary dictionaryWithDictionary:dict];
  441. [newDict setObject:[NSNumber numberWithLong:flags]
  442. forKey:kGMUserFileSystemFinderFlagsKey];
  443. return newDict;
  444. }
  445. return dict;
  446. }
  447. // Fall through and create dictionary based on flags if necessary.
  448. } else if ([delegate respondsToSelector:@selector(finderFlagsAtPath:)]) {
  449. flags |= [delegate finderFlagsAtPath:path];
  450. } else if ([delegate respondsToSelector:@selector(iconDataAtPath:)] &&
  451. [delegate iconDataAtPath:path] != nil) {
  452. flags |= kHasCustomIcon;
  453. }
  454. if (flags != 0) {
  455. return [NSDictionary dictionaryWithObject:[NSNumber numberWithLong:flags]
  456. forKey:kGMUserFileSystemFinderFlagsKey];
  457. }
  458. return nil;
  459. }
  460. - (NSDictionary *)resourceAttributesAtPath:(NSString *)path {
  461. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  462. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
  463. }
  464. id delegate = [internal_ delegate];
  465. if ([delegate respondsToSelector:@selector(resourceAttributesAtPath:error:)]) {
  466. NSError* error = nil;
  467. return [delegate resourceAttributesAtPath:path error:&error];
  468. }
  469. // Support for deprecated selectors.
  470. NSURL* url = nil;
  471. if ([path hasSuffix:@".webloc"] &&
  472. [delegate respondsToSelector:@selector(URLOfWeblocAtPath:)]) {
  473. url = [delegate URLOfWeblocAtPath:path];
  474. }
  475. NSData* imageData = nil;
  476. if ([delegate respondsToSelector:@selector(iconDataAtPath:)]) {
  477. imageData = [delegate iconDataAtPath:path];
  478. }
  479. if (imageData || url) {
  480. NSMutableDictionary* dict = [NSMutableDictionary dictionary];
  481. if (imageData) {
  482. [dict setObject:imageData forKey:kGMUserFileSystemCustomIconDataKey];
  483. }
  484. if (url) {
  485. [dict setObject:url forKey:kGMUserFileSystemWeblocURLKey];
  486. }
  487. return dict;
  488. }
  489. return nil;
  490. }
  491. - (BOOL)hasCustomIconAtPath:(NSString *)path {
  492. if ([path isEqualToString:@"/"]) {
  493. return NO; // For a volume icon they should use the volicon= option.
  494. }
  495. NSDictionary* finderAttribs = [self finderAttributesAtPath:path];
  496. if (finderAttribs) {
  497. NSNumber* finderFlags =
  498. [finderAttribs objectForKey:kGMUserFileSystemFinderFlagsKey];
  499. if (finderFlags) {
  500. UInt16 flags = (UInt16)[finderFlags longValue];
  501. return (flags & kHasCustomIcon) == kHasCustomIcon;
  502. }
  503. }
  504. return NO;
  505. }
  506. - (BOOL)isDirectoryIconAtPath:(NSString *)path dirPath:(NSString **)dirPath {
  507. NSString* name = [path lastPathComponent];
  508. if ([name isEqualToString:@"Icon\r"]) {
  509. if (dirPath) {
  510. *dirPath = [path stringByDeletingLastPathComponent];
  511. }
  512. return YES;
  513. }
  514. return NO;
  515. }
  516. - (BOOL)isAppleDoubleAtPath:(NSString *)path realPath:(NSString **)realPath {
  517. NSString* name = [path lastPathComponent];
  518. if ([name hasPrefix:@"._"]) {
  519. if (realPath) {
  520. name = [name substringFromIndex:2];
  521. *realPath = [path stringByDeletingLastPathComponent];
  522. *realPath = [*realPath stringByAppendingPathComponent:name];
  523. }
  524. return YES;
  525. }
  526. return NO;
  527. }
  528. // If the given attribs dictionary contains any FinderInfo attributes then
  529. // returns NSData for FinderInfo; otherwise returns nil.
  530. - (NSData *)finderDataForAttributes:(NSDictionary *)attribs {
  531. if (!attribs) {
  532. return nil;
  533. }
  534. GMFinderInfo* info = [GMFinderInfo finderInfo];
  535. BOOL attributeFound = NO; // Have we found at least one relevant attribute?
  536. NSNumber* flags = [attribs objectForKey:kGMUserFileSystemFinderFlagsKey];
  537. if (flags) {
  538. attributeFound = YES;
  539. [info setFlags:(UInt16)[flags longValue]];
  540. }
  541. NSNumber* extendedFlags =
  542. [attribs objectForKey:kGMUserFileSystemFinderExtendedFlagsKey];
  543. if (extendedFlags) {
  544. attributeFound = YES;
  545. [info setExtendedFlags:(UInt16)[extendedFlags longValue]];
  546. }
  547. NSNumber* typeCode = [attribs objectForKey:NSFileHFSTypeCode];
  548. if (typeCode) {
  549. attributeFound = YES;
  550. [info setTypeCode:(OSType)[typeCode longValue]];
  551. }
  552. NSNumber* creatorCode = [attribs objectForKey:NSFileHFSCreatorCode];
  553. if (creatorCode) {
  554. attributeFound = YES;
  555. [info setCreatorCode:(OSType)[creatorCode longValue]];
  556. }
  557. return attributeFound ? [info data] : nil;
  558. }
  559. // If the given attribs dictionary contains any ResourceFork attributes then
  560. // returns NSData for the ResourceFork; otherwise returns nil.
  561. - (NSData *)resourceDataForAttributes:(NSDictionary *)attribs {
  562. if (!attribs) {
  563. return nil;
  564. }
  565. GMResourceFork* fork = [GMResourceFork resourceFork];
  566. BOOL attributeFound = NO; // Have we found at least one relevant attribute?
  567. NSData* imageData = [attribs objectForKey:kGMUserFileSystemCustomIconDataKey];
  568. if (imageData) {
  569. attributeFound = YES;
  570. [fork addResourceWithType:'icns'
  571. resID:kCustomIconResource // -16455
  572. name:nil
  573. data:imageData];
  574. }
  575. NSURL* url = [attribs objectForKey:kGMUserFileSystemWeblocURLKey];
  576. if (url) {
  577. attributeFound = YES;
  578. NSString* urlString = [url absoluteString];
  579. NSData* data = [urlString dataUsingEncoding:NSUTF8StringEncoding];
  580. [fork addResourceWithType:'url '
  581. resID:256
  582. name:nil
  583. data:data];
  584. }
  585. return attributeFound ? [fork data] : nil;
  586. }
  587. // Returns the AppleDouble file contents, if any, for the given path. You should
  588. // call this with the realPath out-param from a call to isAppleDoubleAtPath:.
  589. //
  590. // On 10.5 and (hopefully) above, the Finder will end up using the extended
  591. // attributes and so we won't need to serve ._ files.
  592. - (NSData *)appleDoubleContentsAtPath:(NSString *)path {
  593. NSDictionary* finderAttributes = [self finderAttributesAtPath:path];
  594. NSData* finderData = [self finderDataForAttributes:finderAttributes];
  595. // We treat the ._ for a directory and it's ._Icon\r file the same. This means
  596. // that we'll put extra resource-fork information in directory's ._ file even
  597. // though it isn't needed. It's worth it given that it only affects 10.4.
  598. [self isDirectoryIconAtPath:path dirPath:&path];
  599. NSDictionary* resourceAttributes = [self resourceAttributesAtPath:path];
  600. NSData* resourceData = [self resourceDataForAttributes:resourceAttributes];
  601. if (finderData != nil || resourceData != nil) {
  602. GMAppleDouble* doubleFile = [GMAppleDouble appleDouble];
  603. if (finderData) {
  604. [doubleFile addEntryWithID:DoubleEntryFinderInfo data:finderData];
  605. }
  606. if (resourceData) {
  607. [doubleFile addEntryWithID:DoubleEntryResourceFork
  608. data:resourceData];
  609. }
  610. return [doubleFile data];
  611. }
  612. return nil;
  613. }
  614. #pragma mark Internal Stat Operations
  615. - (BOOL)fillStatvfsBuffer:(struct statvfs *)stbuf
  616. forPath:(NSString *)path
  617. error:(NSError **)error {
  618. NSDictionary* attributes = [self attributesOfFileSystemForPath:path error:error];
  619. if (!attributes) {
  620. return NO;
  621. }
  622. // Maximum length of filenames
  623. // TODO: Create our own key so that a fileSystem can override this.
  624. stbuf->f_namemax = 255;
  625. // Block size
  626. // TODO: Create our own key so that a fileSystem can override this.
  627. stbuf->f_bsize = stbuf->f_frsize = 4096;
  628. // Size in blocks
  629. NSNumber* size = [attributes objectForKey:NSFileSystemSize];
  630. assert(size);
  631. stbuf->f_blocks = (fsblkcnt_t)([size longLongValue] / stbuf->f_frsize);
  632. // Number of free / available blocks
  633. NSNumber* freeSize = [attributes objectForKey:NSFileSystemFreeSize];
  634. assert(freeSize);
  635. stbuf->f_bfree = stbuf->f_bavail =
  636. (fsblkcnt_t)([freeSize longLongValue] / stbuf->f_frsize);
  637. // Number of nodes
  638. NSNumber* numNodes = [attributes objectForKey:NSFileSystemNodes];
  639. assert(numNodes);
  640. stbuf->f_files = (fsfilcnt_t)[numNodes longLongValue];
  641. // Number of free / available nodes
  642. NSNumber* freeNodes = [attributes objectForKey:NSFileSystemFreeNodes];
  643. assert(freeNodes);
  644. stbuf->f_ffree = stbuf->f_favail = (fsfilcnt_t)[freeNodes longLongValue];
  645. return YES;
  646. }
  647. - (BOOL)fillStatBuffer:(struct stat *)stbuf
  648. forPath:(NSString *)path
  649. userData:(id)userData
  650. error:(NSError **)error {
  651. NSDictionary* attributes = [self defaultAttributesOfItemAtPath:path
  652. userData:userData
  653. error:error];
  654. if (!attributes) {
  655. return NO;
  656. }
  657. // Inode
  658. NSNumber* inode = [attributes objectForKey:NSFileSystemFileNumber];
  659. if (inode) {
  660. stbuf->st_ino = [inode longLongValue];
  661. }
  662. // Permissions (mode)
  663. NSNumber* perm = [attributes objectForKey:NSFilePosixPermissions];
  664. stbuf->st_mode = [perm longValue];
  665. NSString* fileType = [attributes objectForKey:NSFileType];
  666. if ([fileType isEqualToString:NSFileTypeDirectory ]) {
  667. stbuf->st_mode |= S_IFDIR;
  668. } else if ([fileType isEqualToString:NSFileTypeRegular]) {
  669. stbuf->st_mode |= S_IFREG;
  670. } else if ([fileType isEqualToString:NSFileTypeSymbolicLink]) {
  671. stbuf->st_mode |= S_IFLNK;
  672. } else {
  673. *error = [GMUserFileSystem errorWithCode:EFTYPE];
  674. return NO;
  675. }
  676. // Owner and Group
  677. // Note that if the owner or group IDs are not specified, the effective
  678. // user and group IDs for the current process are used as defaults.
  679. NSNumber* uid = [attributes objectForKey:NSFileOwnerAccountID];
  680. NSNumber* gid = [attributes objectForKey:NSFileGroupOwnerAccountID];
  681. stbuf->st_uid = uid ? [uid longValue] : geteuid();
  682. stbuf->st_gid = gid ? [gid longValue] : getegid();
  683. // nlink
  684. NSNumber* nlink = [attributes objectForKey:NSFileReferenceCount];
  685. stbuf->st_nlink = [nlink longValue];
  686. // flags
  687. NSNumber* flags = [attributes objectForKey:kGMUserFileSystemFileFlagsKey];
  688. if (flags) {
  689. stbuf->st_flags = [flags longValue];
  690. } else {
  691. // Just in case they tried to use NSFileImmutable or NSFileAppendOnly
  692. NSNumber* immutableFlag = [attributes objectForKey:NSFileImmutable];
  693. if (immutableFlag && [immutableFlag boolValue]) {
  694. stbuf->st_flags |= UF_IMMUTABLE;
  695. }
  696. NSNumber* appendFlag = [attributes objectForKey:NSFileAppendOnly];
  697. if (appendFlag && [appendFlag boolValue]) {
  698. stbuf->st_flags |= UF_APPEND;
  699. }
  700. }
  701. // NOTE: We default atime,ctime to mtime if it is provided.
  702. NSDate* mdate = [attributes objectForKey:NSFileModificationDate];
  703. if (mdate) {
  704. const double seconds_dp = [mdate timeIntervalSince1970];
  705. const time_t t_sec = (time_t) seconds_dp;
  706. const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond);
  707. const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0;
  708. stbuf->st_mtimespec.tv_sec = t_sec;
  709. stbuf->st_mtimespec.tv_nsec = t_nsec;
  710. stbuf->st_atimespec = stbuf->st_mtimespec; // Default to mtime
  711. stbuf->st_ctimespec = stbuf->st_mtimespec; // Default to mtime
  712. }
  713. NSDate* adate = [attributes objectForKey:kGMUserFileSystemFileAccessDateKey];
  714. if (adate) {
  715. const double seconds_dp = [adate timeIntervalSince1970];
  716. const time_t t_sec = (time_t) seconds_dp;
  717. const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond);
  718. const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0;
  719. stbuf->st_atimespec.tv_sec = t_sec;
  720. stbuf->st_atimespec.tv_nsec = t_nsec;
  721. }
  722. NSDate* cdate = [attributes objectForKey:kGMUserFileSystemFileChangeDateKey];
  723. if (cdate) {
  724. const double seconds_dp = [cdate timeIntervalSince1970];
  725. const time_t t_sec = (time_t) seconds_dp;
  726. const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond);
  727. const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0;
  728. stbuf->st_ctimespec.tv_sec = t_sec;
  729. stbuf->st_ctimespec.tv_nsec = t_nsec;
  730. }
  731. #if __DARWIN_64_BIT_INO_T
  732. NSDate* bdate = [attributes objectForKey:NSFileCreationDate];
  733. if (bdate) {
  734. const double seconds_dp = [bdate timeIntervalSince1970];
  735. const time_t t_sec = (time_t) seconds_dp;
  736. const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond);
  737. const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0;
  738. stbuf->st_birthtimespec.tv_sec = t_sec;
  739. stbuf->st_birthtimespec.tv_nsec = t_nsec;
  740. }
  741. #endif
  742. // Size for regular files.
  743. // TODO: Revisit size for directories.
  744. if (![fileType isEqualToString:NSFileTypeDirectory]) {
  745. NSNumber* size = [attributes objectForKey:NSFileSize];
  746. if (size) {
  747. stbuf->st_size = [size longLongValue];
  748. }
  749. }
  750. // Set the number of blocks used so that Finder will display size on disk
  751. // properly. The man page says that this is in terms of 512 byte blocks.
  752. if (stbuf->st_size > 0) {
  753. stbuf->st_blocks = stbuf->st_size / 512;
  754. if (stbuf->st_size % 512) {
  755. ++(stbuf->st_blocks);
  756. }
  757. }
  758. return YES;
  759. }
  760. #pragma mark Moving an Item
  761. - (BOOL)moveItemAtPath:(NSString *)source
  762. toPath:(NSString *)destination
  763. error:(NSError **)error {
  764. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  765. NSString* traceinfo =
  766. [NSString stringWithFormat:@"%@ -> %@", source, destination];
  767. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  768. }
  769. if ([[internal_ delegate] respondsToSelector:@selector(moveItemAtPath:toPath:error:)]) {
  770. return [[internal_ delegate] moveItemAtPath:source toPath:destination error:error];
  771. }
  772. *error = [GMUserFileSystem errorWithCode:EACCES];
  773. return NO;
  774. }
  775. #pragma mark Removing an Item
  776. - (BOOL)removeDirectoryAtPath:(NSString *)path error:(NSError **)error {
  777. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  778. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
  779. }
  780. if ([[internal_ delegate] respondsToSelector:@selector(removeDirectoryAtPath:error:)]) {
  781. return [[internal_ delegate] removeDirectoryAtPath:path error:error];
  782. }
  783. return [self removeItemAtPath:path error:error];
  784. }
  785. - (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error {
  786. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  787. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
  788. }
  789. if ([[internal_ delegate] respondsToSelector:@selector(removeItemAtPath:error:)]) {
  790. return [[internal_ delegate] removeItemAtPath:path error:error];
  791. }
  792. *error = [GMUserFileSystem errorWithCode:EACCES];
  793. return NO;
  794. }
  795. #pragma mark Creating an Item
  796. - (BOOL)createDirectoryAtPath:(NSString *)path
  797. attributes:(NSDictionary *)attributes
  798. error:(NSError **)error {
  799. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  800. NSMutableString* traceinfo =
  801. [NSMutableString stringWithFormat:@"%@ [%@]", path, attributes];
  802. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  803. }
  804. if ([[internal_ delegate] respondsToSelector:@selector(createDirectoryAtPath:attributes:error:)]) {
  805. return [[internal_ delegate] createDirectoryAtPath:path attributes:attributes error:error];
  806. }
  807. *error = [GMUserFileSystem errorWithCode:EACCES];
  808. return NO;
  809. }
  810. - (BOOL)createFileAtPath:(NSString *)path
  811. attributes:(NSDictionary *)attributes
  812. userData:(id *)userData
  813. error:(NSError **)error {
  814. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  815. NSString* traceinfo = [NSString stringWithFormat:@"%@ [%@]", path, attributes];
  816. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  817. }
  818. if ([[internal_ delegate] respondsToSelector:@selector(createFileAtPath:attributes:userData:error:)]) {
  819. return [[internal_ delegate] createFileAtPath:path attributes:attributes
  820. userData:userData error:error];
  821. } else if ([[internal_ delegate] respondsToSelector:@selector(createFileAtPath:attributes:fileDelegate:error:)]) {
  822. // NOTE: For backward compatibility with version 1.7 and prior.
  823. return [[internal_ delegate] createFileAtPath:path attributes:attributes
  824. fileDelegate:userData error:error];
  825. }
  826. *error = [GMUserFileSystem errorWithCode:EACCES];
  827. return NO;
  828. }
  829. #pragma mark Linking an Item
  830. - (BOOL)linkItemAtPath:(NSString *)path
  831. toPath:(NSString *)otherPath
  832. error:(NSError **)error {
  833. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  834. NSString* traceinfo = [NSString stringWithFormat:@"%@ -> %@", path, otherPath];
  835. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  836. }
  837. if ([[internal_ delegate] respondsToSelector:@selector(linkItemAtPath:toPath:error:)]) {
  838. return [[internal_ delegate] linkItemAtPath:path toPath:otherPath error:error];
  839. }
  840. *error = [GMUserFileSystem errorWithCode:ENOTSUP]; // Note: error not in man page.
  841. return NO;
  842. }
  843. #pragma mark Symbolic Links
  844. - (BOOL)createSymbolicLinkAtPath:(NSString *)path
  845. withDestinationPath:(NSString *)otherPath
  846. error:(NSError **)error {
  847. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  848. NSString* traceinfo = [NSString stringWithFormat:@"%@ -> %@", path, otherPath];
  849. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  850. }
  851. if ([[internal_ delegate] respondsToSelector:@selector(createSymbolicLinkAtPath:withDestinationPath:error:)]) {
  852. return [[internal_ delegate] createSymbolicLinkAtPath:path
  853. withDestinationPath:otherPath
  854. error:error];
  855. }
  856. *error = [GMUserFileSystem errorWithCode:ENOTSUP]; // Note: error not in man page.
  857. return NO;
  858. }
  859. - (NSString *)destinationOfSymbolicLinkAtPath:(NSString *)path
  860. error:(NSError **)error {
  861. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  862. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
  863. }
  864. if ([[internal_ delegate] respondsToSelector:@selector(destinationOfSymbolicLinkAtPath:error:)]) {
  865. return [[internal_ delegate] destinationOfSymbolicLinkAtPath:path error:error];
  866. }
  867. *error = [GMUserFileSystem errorWithCode:ENOENT];
  868. return nil;
  869. }
  870. #pragma mark File Contents
  871. // NOTE: Only call this if the delegate does indeed support this method.
  872. - (NSData *)contentsAtPath:(NSString *)path {
  873. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  874. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
  875. }
  876. id delegate = [internal_ delegate];
  877. return [delegate contentsAtPath:path];
  878. }
  879. - (BOOL)openFileAtPath:(NSString *)path
  880. mode:(int)mode
  881. userData:(id *)userData
  882. error:(NSError **)error {
  883. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  884. NSString* traceinfo = [NSString stringWithFormat:@"%@, mode=0x%x", path, mode];
  885. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  886. }
  887. id delegate = [internal_ delegate];
  888. if ([delegate respondsToSelector:@selector(contentsAtPath:)]) {
  889. NSData* data = [self contentsAtPath:path];
  890. if (data != nil) {
  891. *userData = [GMDataBackedFileDelegate fileDelegateWithData:data];
  892. return YES;
  893. }
  894. } else if ([delegate respondsToSelector:@selector(openFileAtPath:mode:userData:error:)]) {
  895. if ([delegate openFileAtPath:path
  896. mode:mode
  897. userData:userData
  898. error:error]) {
  899. return YES; // They handled it.
  900. }
  901. } else if ([delegate respondsToSelector:@selector(openFileAtPath:mode:fileDelegate:error:)]) {
  902. if ([delegate openFileAtPath:path
  903. mode:mode
  904. fileDelegate:userData
  905. error:error]) {
  906. // NOTE: For backward compatibility with version 1.7 and prior.
  907. return YES; // They handled it.
  908. }
  909. }
  910. // Still unable to open the file; maybe it is an Icon\r or AppleDouble?
  911. if ([internal_ shouldCheckForResource]) {
  912. NSData* data = nil; // Synthesized data that we provide a file delegate for.
  913. // Is it an Icon\r file that we handle?
  914. if ([self isDirectoryIconAtPath:path dirPath:nil]) {
  915. data = [NSData data]; // The Icon\r file is empty.
  916. }
  917. // (Tiger Only): Maybe it is an AppleDouble file that we handle?
  918. if ([internal_ isTiger]) {
  919. NSString* realPath;
  920. if ([self isAppleDoubleAtPath:path realPath:&realPath]) {
  921. data = [self appleDoubleContentsAtPath:realPath];
  922. }
  923. }
  924. if (data != nil) {
  925. if ((mode & O_ACCMODE) == O_RDONLY) {
  926. *userData = [GMDataBackedFileDelegate fileDelegateWithData:data];
  927. } else {
  928. NSMutableData* mutableData = [NSMutableData dataWithData:data];
  929. *userData =
  930. [GMMutableDataBackedFileDelegate fileDelegateWithData:mutableData];
  931. }
  932. return YES; // Handled by a synthesized file delegate.
  933. }
  934. }
  935. if (*error == nil) {
  936. *error = [GMUserFileSystem errorWithCode:ENOENT];
  937. }
  938. return NO;
  939. }
  940. - (void)releaseFileAtPath:(NSString *)path userData:(id)userData {
  941. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  942. NSString* traceinfo =
  943. [NSString stringWithFormat:@"%@, userData=%p", path, userData];
  944. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  945. }
  946. if (userData != nil &&
  947. [userData isKindOfClass:[GMDataBackedFileDelegate class]]) {
  948. return; // Don't report releaseFileAtPath for internal file.
  949. }
  950. if ([[internal_ delegate] respondsToSelector:@selector(releaseFileAtPath:userData:)]) {
  951. [[internal_ delegate] releaseFileAtPath:path userData:userData];
  952. } else if ([[internal_ delegate] respondsToSelector:@selector(releaseFileAtPath:fileDelegate:)]) {
  953. // NOTE: For backward compatibility with version 1.7 and prior.
  954. [[internal_ delegate] releaseFileAtPath:path fileDelegate:userData];
  955. }
  956. }
  957. - (int)readFileAtPath:(NSString *)path
  958. userData:(id)userData
  959. buffer:(char *)buffer
  960. size:(size_t)size
  961. offset:(off_t)offset
  962. error:(NSError **)error {
  963. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  964. NSString* traceinfo =
  965. [NSString stringWithFormat:@"%@, userData=%p, offset=%lld, size=%d",
  966. path, userData, offset, size];
  967. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  968. }
  969. if (userData != nil &&
  970. [userData respondsToSelector:@selector(readToBuffer:size:offset:error:)]) {
  971. return [userData readToBuffer:buffer size:size offset:offset error:error];
  972. } else if ([[internal_ delegate] respondsToSelector:@selector(readFileAtPath:userData:buffer:size:offset:error:)]) {
  973. return [[internal_ delegate] readFileAtPath:path
  974. userData:userData
  975. buffer:buffer
  976. size:size
  977. offset:offset
  978. error:error];
  979. } else if ([[internal_ delegate] respondsToSelector:@selector(readFileAtPath:fileDelegate:buffer:size:offset:error:)]) {
  980. // NOTE: For backward compatibility with version 1.7 and prior.
  981. return [[internal_ delegate] readFileAtPath:path
  982. fileDelegate:userData
  983. buffer:buffer
  984. size:size
  985. offset:offset
  986. error:error];
  987. }
  988. *error = [GMUserFileSystem errorWithCode:EACCES];
  989. return -1;
  990. }
  991. - (int)writeFileAtPath:(NSString *)path
  992. userData:(id)userData
  993. buffer:(const char *)buffer
  994. size:(size_t)size
  995. offset:(off_t)offset
  996. error:(NSError **)error {
  997. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  998. NSString* traceinfo =
  999. [NSString stringWithFormat:@"%@, userData=%p, offset=%lld, size=%d",
  1000. path, userData, offset, size];
  1001. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  1002. }
  1003. if (userData != nil &&
  1004. [userData respondsToSelector:@selector(writeFromBuffer:size:offset:error:)]) {
  1005. return [userData writeFromBuffer:buffer size:size offset:offset error:error];
  1006. } else if ([[internal_ delegate] respondsToSelector:@selector(writeFileAtPath:userData:buffer:size:offset:error:)]) {
  1007. return [[internal_ delegate] writeFileAtPath:path
  1008. userData:userData
  1009. buffer:buffer
  1010. size:size
  1011. offset:offset
  1012. error:error];
  1013. } else if ([[internal_ delegate] respondsToSelector:@selector(writeFileAtPath:fileDelegate:buffer:size:offset:error:)]) {
  1014. // NOTE: For backward compatibility with version 1.7 and prior.
  1015. return [[internal_ delegate] writeFileAtPath:path
  1016. fileDelegate:userData
  1017. buffer:buffer
  1018. size:size
  1019. offset:offset
  1020. error:error];
  1021. }
  1022. *error = [GMUserFileSystem errorWithCode:EACCES];
  1023. return -1;
  1024. }
  1025. // NOTE: For backward compatibility with version 1.7 and prior.
  1026. - (BOOL)truncateFileAtPath:(NSString *)path
  1027. fileDelegate:(id)fileDelegate
  1028. offset:(off_t)offset
  1029. error:(NSError **)error
  1030. handled:(BOOL*)handled {
  1031. if (fileDelegate != nil &&
  1032. [fileDelegate respondsToSelector:@selector(truncateToOffset:error:)]) {
  1033. *handled = YES;
  1034. return [fileDelegate truncateToOffset:offset error:error];
  1035. } else if ([[internal_ delegate] respondsToSelector:@selector(truncateFileAtPath:offset:error:)]) {
  1036. *handled = YES;
  1037. return [[internal_ delegate] truncateFileAtPath:path
  1038. offset:offset
  1039. error:error];
  1040. }
  1041. *handled = NO;
  1042. return NO;
  1043. }
  1044. - (BOOL)exchangeDataOfItemAtPath:(NSString *)path1
  1045. withItemAtPath:(NSString *)path2
  1046. error:(NSError **)error {
  1047. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  1048. NSString* traceinfo = [NSString stringWithFormat:@"%@ <-> %@", path1, path2];
  1049. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  1050. }
  1051. if ([[internal_ delegate] respondsToSelector:@selector(exchangeDataOfItemAtPath:withItemAtPath:error:)]) {
  1052. return [[internal_ delegate] exchangeDataOfItemAtPath:path1
  1053. withItemAtPath:path2
  1054. error:error];
  1055. }
  1056. *error = [GMUserFileSystem errorWithCode:ENOSYS];
  1057. return NO;
  1058. }
  1059. #pragma mark Directory Contents
  1060. - (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error {
  1061. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  1062. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
  1063. }
  1064. NSArray* contents = nil;
  1065. if ([[internal_ delegate] respondsToSelector:@selector(contentsOfDirectoryAtPath:error:)]) {
  1066. contents = [[internal_ delegate] contentsOfDirectoryAtPath:path error:error];
  1067. } else if ([path isEqualToString:@"/"]) {
  1068. contents = [NSArray array]; // Give them an empty root directory for free.
  1069. }
  1070. if (contents != nil &&
  1071. [internal_ isTiger] &&
  1072. [internal_ shouldCheckForResource]) {
  1073. // Note: Tiger (10.4) requires that the ._ file are explicitly listed in
  1074. // the directory contents if you want a custom icon to show up. If they
  1075. // don't provide their own ._ file and they have a custom icon, then we'll
  1076. // add the ._ file to the directory contents.
  1077. NSMutableSet* fullContents = [NSMutableSet setWithArray:contents];
  1078. for (int i = 0; i < [contents count]; ++i) {
  1079. NSString* name = [contents objectAtIndex:i];
  1080. if ([name hasPrefix:@"._"]) {
  1081. continue; // Skip over any AppleDouble that they provide.
  1082. }
  1083. NSString* doubleName = [NSString stringWithFormat:@"._%@", name];
  1084. if ([fullContents containsObject:doubleName]) {
  1085. continue; // They provided their own AppleDouble for 'name'.
  1086. }
  1087. NSString* pathPlusName = [path stringByAppendingPathComponent:name];
  1088. if ([self hasCustomIconAtPath:pathPlusName]) {
  1089. [fullContents addObject:doubleName];
  1090. }
  1091. }
  1092. if ([self hasCustomIconAtPath:path]) {
  1093. [fullContents addObject:@"Icon\r"];
  1094. [fullContents addObject:@"._Icon\r"];
  1095. }
  1096. contents = [fullContents allObjects];
  1097. }
  1098. return contents;
  1099. }
  1100. #pragma mark Getting and Setting Attributes
  1101. - (BOOL)supportsAttributesOfItemAtPath {
  1102. id delegate = [internal_ delegate];
  1103. return [delegate respondsToSelector:@selector(attributesOfItemAtPath:userData:error:)] ||
  1104. [delegate respondsToSelector:@selector(attributesOfItemAtPath:error:)];
  1105. }
  1106. - (NSDictionary *)attributesOfItemAtPath:(NSString *)path
  1107. userData:userData
  1108. error:(NSError **)error {
  1109. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  1110. NSString* traceinfo =
  1111. [NSString stringWithFormat:@"%@, userData=%p", path, userData];
  1112. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  1113. }
  1114. id delegate = [internal_ delegate];
  1115. if ([delegate respondsToSelector:@selector(attributesOfItemAtPath:userData:error:)]) {
  1116. return [delegate attributesOfItemAtPath:path userData:userData error:error];
  1117. } else if ([delegate respondsToSelector:@selector(attributesOfItemAtPath:error:)]) {
  1118. return [delegate attributesOfItemAtPath:path error:error];
  1119. }
  1120. return nil;
  1121. }
  1122. // Get attributesOfItemAtPath from the delegate with default values.
  1123. - (NSDictionary *)defaultAttributesOfItemAtPath:(NSString *)path
  1124. userData:userData
  1125. error:(NSError **)error {
  1126. // Set up default item attributes.
  1127. NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
  1128. BOOL isReadOnly = [internal_ isReadOnly];
  1129. [attributes setObject:[NSNumber numberWithLong:(isReadOnly ? 0555 : 0775)]
  1130. forKey:NSFilePosixPermissions];
  1131. [attributes setObject:[NSNumber numberWithLong:1]
  1132. forKey:NSFileReferenceCount]; // 1 means "don't know"
  1133. if ([path isEqualToString:@"/"]) {
  1134. [attributes setObject:NSFileTypeDirectory forKey:NSFileType];
  1135. } else {
  1136. [attributes setObject:NSFileTypeRegular forKey:NSFileType];
  1137. }
  1138. id delegate = [internal_ delegate];
  1139. BOOL isAppleDouble = NO; // May only be set to YES on Tiger.
  1140. BOOL isDirectoryIcon = NO;
  1141. // The delegate can override any of the above defaults by implementing the
  1142. // attributesOfItemAtPath: selector and returning a custom dictionary.
  1143. NSDictionary* customAttribs = nil;
  1144. BOOL supportsAttributesSelector = [self supportsAttributesOfItemAtPath];
  1145. if (supportsAttributesSelector) {
  1146. customAttribs = [self attributesOfItemAtPath:path
  1147. userData:userData
  1148. error:error];
  1149. }
  1150. // Maybe this is the root directory? If so, we'll claim it always exists.
  1151. if (!customAttribs && [path isEqualToString:@"/"]) {
  1152. return attributes; // The root directory always exists.
  1153. }
  1154. // Maybe check to see if this is a special file that we should handle. If they
  1155. // wanted to handle it, then they would have given us back customAttribs.
  1156. if (!customAttribs && [internal_ shouldCheckForResource]) {
  1157. // (Tiger-Only): If this is an AppleDouble file then we update the path to
  1158. // be the original representative of that double file; i.e. /._baz -> /baz.
  1159. if ([internal_ isTiger]) {
  1160. isAppleDouble = [self isAppleDoubleAtPath:path realPath:&path];
  1161. }
  1162. // If the maybe-fixed-up path is a directoryIcon, we'll modify the path to
  1163. // refer to the parent directory and note that we are a directory icon.
  1164. isDirectoryIcon = [self isDirectoryIconAtPath:path dirPath:&path];
  1165. // Maybe we'll try again to get custom attribs on the real path.
  1166. if (supportsAttributesSelector && (isAppleDouble || isDirectoryIcon)) {
  1167. customAttribs = [self attributesOfItemAtPath:path
  1168. userData:userData
  1169. error:error];
  1170. }
  1171. }
  1172. if (customAttribs) {
  1173. [attributes addEntriesFromDictionary:customAttribs];
  1174. } else if (supportsAttributesSelector) {
  1175. // They explicitly support attributesOfItemAtPath: and returned nil.
  1176. if (!(*error)) {
  1177. *error = [GMUserFileSystem errorWithCode:ENOENT];
  1178. }
  1179. return nil;
  1180. }
  1181. // If this is a directory Icon\r then it is an empty file and we're done.
  1182. if (isDirectoryIcon && !isAppleDouble) {
  1183. if ([self hasCustomIconAtPath:path]) {
  1184. [attributes setObject:NSFileTypeRegular forKey:NSFileType];
  1185. [attributes setObject:[NSNumber numberWithLongLong:0] forKey:NSFileSize];
  1186. return attributes;
  1187. }
  1188. *error = [GMUserFileSystem errorWithCode:ENOENT];
  1189. return nil;
  1190. }
  1191. // If this is a ._ then we'll need to compute its size and we're done. This
  1192. // will never be true on post-Tiger.
  1193. if (isAppleDouble) {
  1194. NSData* data = [self appleDoubleContentsAtPath:path];
  1195. if (data != nil) {
  1196. [attributes setObject:NSFileTypeRegular forKey:NSFileType];
  1197. [attributes setObject:[NSNumber numberWithLongLong:[data length]]
  1198. forKey:NSFileSize];
  1199. return attributes;
  1200. }
  1201. *error = [GMUserFileSystem errorWithCode:ENOENT];
  1202. return nil;
  1203. }
  1204. // If they don't supply a size and it is a file then we try to compute it.
  1205. if (![attributes objectForKey:NSFileSize] &&
  1206. ![[attributes objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory] &&
  1207. [delegate respondsToSelector:@selector(contentsAtPath:)]) {
  1208. NSData* data = [self contentsAtPath:path];
  1209. if (data == nil) {
  1210. *error = [GMUserFileSystem errorWithCode:ENOENT];
  1211. return nil;
  1212. }
  1213. [attributes setObject:[NSNumber numberWithLongLong:[data length]]
  1214. forKey:NSFileSize];
  1215. }
  1216. return attributes;
  1217. }
  1218. - (NSDictionary *)extendedTimesOfItemAtPath:(NSString *)path
  1219. userData:(id)userData
  1220. error:(NSError **)error {
  1221. if (![self supportsAttributesOfItemAtPath]) {
  1222. *error = [GMUserFileSystem errorWithCode:ENOSYS];
  1223. return nil;
  1224. }
  1225. return [self attributesOfItemAtPath:path
  1226. userData:userData
  1227. error:error];
  1228. }
  1229. - (NSDictionary *)attributesOfFileSystemForPath:(NSString *)path
  1230. error:(NSError **)error {
  1231. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  1232. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
  1233. }
  1234. NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
  1235. NSNumber* defaultSize = [NSNumber numberWithLongLong:(2LL * 1024 * 1024 * 1024)];
  1236. [attributes setObject:defaultSize forKey:NSFileSystemSize];
  1237. [attributes setObject:defaultSize forKey:NSFileSystemFreeSize];
  1238. [attributes setObject:defaultSize forKey:NSFileSystemNodes];
  1239. [attributes setObject:defaultSize forKey:NSFileSystemFreeNodes];
  1240. // TODO: NSFileSystemNumber? Or does fuse do that for us?
  1241. // The delegate can override any of the above defaults by implementing the
  1242. // attributesOfFileSystemForPath selector and returning a custom dictionary.
  1243. if ([[internal_ delegate] respondsToSelector:@selector(attributesOfFileSystemForPath:error:)]) {
  1244. *error = nil;
  1245. NSDictionary* customAttribs =
  1246. [[internal_ delegate] attributesOfFileSystemForPath:path error:error];
  1247. if (!customAttribs) {
  1248. if (!(*error)) {
  1249. *error = [GMUserFileSystem errorWithCode:ENODEV];
  1250. }
  1251. return nil;
  1252. }
  1253. [attributes addEntriesFromDictionary:customAttribs];
  1254. }
  1255. return attributes;
  1256. }
  1257. - (BOOL)setAttributes:(NSDictionary *)attributes
  1258. ofItemAtPath:(NSString *)path
  1259. userData:(id)userData
  1260. error:(NSError **)error {
  1261. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  1262. NSString* traceinfo =
  1263. [NSString stringWithFormat:@"%@, userData=%p, attributes=%@",
  1264. path, userData, attributes];
  1265. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  1266. }
  1267. // NOTE: For backward compatibility with version 1.7 and prior.
  1268. if ([attributes objectForKey:NSFileSize] != nil) {
  1269. BOOL handled = NO; // Did they have a delegate method that handles truncation?
  1270. NSNumber* offsetNumber = [attributes objectForKey:NSFileSize];
  1271. off_t offset = [offsetNumber longLongValue];
  1272. BOOL ret = [self truncateFileAtPath:path
  1273. fileDelegate:userData
  1274. offset:offset
  1275. error:error
  1276. handled:&handled];
  1277. if (handled && (!ret || [attributes count] == 1)) {
  1278. // Either the truncate call failed, or we only had NSFileSize, so we are done.
  1279. return ret;
  1280. }
  1281. }
  1282. if ([[internal_ delegate] respondsToSelector:@selector(setAttributes:ofItemAtPath:userData:error:)]) {
  1283. return [[internal_ delegate] setAttributes:attributes ofItemAtPath:path userData:userData error:error];
  1284. } else if ([[internal_ delegate] respondsToSelector:@selector(setAttributes:ofItemAtPath:error:)]) {
  1285. return [[internal_ delegate] setAttributes:attributes ofItemAtPath:path error:error];
  1286. }
  1287. *error = [GMUserFileSystem errorWithCode:ENODEV];
  1288. return NO;
  1289. }
  1290. - (BOOL)setAttributes:(NSDictionary *)attributes
  1291. ofFileSystemAtPath:(NSString *)path
  1292. error:(NSError **)error {
  1293. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  1294. NSString* traceinfo =
  1295. [NSString stringWithFormat:@"%@, attributes=%@", path, attributes];
  1296. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  1297. }
  1298. if ([[internal_ delegate] respondsToSelector:@selector(setAttributes:ofFileSystemAtPath:error:)]) {
  1299. return [[internal_ delegate] setAttributes:attributes ofFileSystemAtPath:path error:error];
  1300. }
  1301. *error = [GMUserFileSystem errorWithCode:ENOSYS];
  1302. return NO;
  1303. }
  1304. #pragma mark Extended Attributes
  1305. - (NSArray *)extendedAttributesOfItemAtPath:path error:(NSError **)error {
  1306. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  1307. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(path));
  1308. }
  1309. if ([[internal_ delegate] respondsToSelector:@selector(extendedAttributesOfItemAtPath:error:)]) {
  1310. return [[internal_ delegate] extendedAttributesOfItemAtPath:path error:error];
  1311. }
  1312. *error = [GMUserFileSystem errorWithCode:ENOTSUP];
  1313. return nil;
  1314. }
  1315. - (NSData *)valueOfExtendedAttribute:(NSString *)name
  1316. ofItemAtPath:(NSString *)path
  1317. position:(off_t)position
  1318. error:(NSError **)error {
  1319. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  1320. NSString* traceinfo =
  1321. [NSString stringWithFormat:@"%@, name=%@, position=%lld", path, name, position];
  1322. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  1323. }
  1324. id delegate = [internal_ delegate];
  1325. NSData* data = nil;
  1326. BOOL xattrSupported = NO;
  1327. if ([delegate respondsToSelector:@selector(valueOfExtendedAttribute:ofItemAtPath:position:error:)]) {
  1328. xattrSupported = YES;
  1329. data = [delegate valueOfExtendedAttribute:name
  1330. ofItemAtPath:path
  1331. position:position
  1332. error:error];
  1333. } else if ([delegate respondsToSelector:@selector(valueOfExtendedAttribute:ofItemAtPath:error:)]) {
  1334. // NOTE: For backward compatibility with version 1.5 and prior.
  1335. xattrSupported = YES;
  1336. data = [delegate valueOfExtendedAttribute:name
  1337. ofItemAtPath:path
  1338. error:error];
  1339. }
  1340. // On 10.5+ we might supply FinderInfo/ResourceFork as xattr for them.
  1341. if (!data && [internal_ shouldCheckForResource] && ![internal_ isTiger]) {
  1342. if ([name isEqualToString:@"com.apple.FinderInfo"]) {
  1343. NSDictionary* finderAttributes = [self finderAttributesAtPath:path];
  1344. data = [self finderDataForAttributes:finderAttributes];
  1345. } else if ([name isEqualToString:@"com.apple.ResourceFork"]) {
  1346. [self isDirectoryIconAtPath:path dirPath:&path]; // Maybe update path.
  1347. NSDictionary* attributes = [self resourceAttributesAtPath:path];
  1348. data = [self resourceDataForAttributes:attributes];
  1349. }
  1350. if (data != nil && position > 0) {
  1351. // We have all the data, but they are only requesting a subrange.
  1352. size_t length = [data length];
  1353. if (position > length) {
  1354. *error = [GMUserFileSystem errorWithCode:ERANGE];
  1355. return nil;
  1356. }
  1357. data = [data subdataWithRange:NSMakeRange(position, length - position)];
  1358. }
  1359. }
  1360. if (data == nil && *error == nil) {
  1361. *error = [GMUserFileSystem errorWithCode:xattrSupported ? ENOATTR : ENOTSUP];
  1362. }
  1363. return data;
  1364. }
  1365. - (BOOL)setExtendedAttribute:(NSString *)name
  1366. ofItemAtPath:(NSString *)path
  1367. value:(NSData *)value
  1368. position:(off_t)position
  1369. options:(int)options
  1370. error:(NSError **)error {
  1371. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  1372. NSString* traceinfo =
  1373. [NSString stringWithFormat:@"%@, name=%@, position=%lld, options=0x%x",
  1374. path, name, position, options];
  1375. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  1376. }
  1377. id delegate = [internal_ delegate];
  1378. if ([delegate respondsToSelector:@selector(setExtendedAttribute:ofItemAtPath:value:position:options:error:)]) {
  1379. return [delegate setExtendedAttribute:name
  1380. ofItemAtPath:path
  1381. value:value
  1382. position:position
  1383. options:options
  1384. error:error];
  1385. } else if ([delegate respondsToSelector:@selector(setExtendedAttribute:ofItemAtPath:value:flags:error:)]) {
  1386. // NOTE: For backward compatibility with version 1.5 and prior.
  1387. return [delegate setExtendedAttribute:name
  1388. ofItemAtPath:path
  1389. value:value
  1390. flags:options
  1391. error:error];
  1392. }
  1393. *error = [GMUserFileSystem errorWithCode:ENOTSUP];
  1394. return NO;
  1395. }
  1396. - (BOOL)removeExtendedAttribute:(NSString *)name
  1397. ofItemAtPath:(NSString *)path
  1398. error:(NSError **)error {
  1399. if (MACFUSE_OBJC_DELEGATE_ENTRY_ENABLED()) {
  1400. NSString* traceinfo =
  1401. [NSString stringWithFormat:@"%@, name=%@", path, name];
  1402. MACFUSE_OBJC_DELEGATE_ENTRY(DTRACE_STRING(traceinfo));
  1403. }
  1404. id delegate = [internal_ delegate];
  1405. if ([delegate respondsToSelector:@selector(setExtendedAttribute:ofItemAtPath:value:flags:error:)]) {
  1406. return [delegate removeExtendedAttribute:name
  1407. ofItemAtPath:path
  1408. error:error];
  1409. }
  1410. *error = [GMUserFileSystem errorWithCode:ENOTSUP];
  1411. return NO;
  1412. }
  1413. #pragma mark FUSE Operations
  1414. #define MAYBE_USE_ERROR(var, error) \
  1415. if ((error) != nil && \
  1416. [[(error) domain] isEqualToString:NSPOSIXErrorDomain]) { \
  1417. int code = [(error) code]; \
  1418. if (code != 0) { \
  1419. (var) = -code; \
  1420. } \
  1421. }
  1422. static int fusefm_statfs(const char* path, struct statvfs* stbuf) {
  1423. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1424. int ret = -ENOENT;
  1425. @try {
  1426. memset(stbuf, 0, sizeof(struct statvfs));
  1427. NSError* error = nil;
  1428. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1429. if ([fs fillStatvfsBuffer:stbuf
  1430. forPath:[NSString stringWithUTF8String:path]
  1431. error:&error]) {
  1432. ret = 0;
  1433. } else {
  1434. MAYBE_USE_ERROR(ret, error);
  1435. }
  1436. }
  1437. @catch (id exception) { }
  1438. [pool release];
  1439. return ret;
  1440. }
  1441. static int fusefm_fgetattr(const char *path, struct stat *stbuf,
  1442. struct fuse_file_info *fi) {
  1443. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1444. int ret = -ENOENT;
  1445. @try {
  1446. memset(stbuf, 0, sizeof(struct stat));
  1447. NSError* error = nil;
  1448. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1449. id userData = fi ? (id)(uintptr_t)fi->fh : nil;
  1450. if ([fs fillStatBuffer:stbuf
  1451. forPath:[NSString stringWithUTF8String:path]
  1452. userData:userData
  1453. error:&error]) {
  1454. ret = 0;
  1455. } else {
  1456. MAYBE_USE_ERROR(ret, error);
  1457. }
  1458. }
  1459. @catch (id exception) { }
  1460. [pool release];
  1461. return ret;
  1462. }
  1463. static int fusefm_getattr(const char *path, struct stat *stbuf) {
  1464. return fusefm_fgetattr(path, stbuf, nil);
  1465. }
  1466. static int fusefm_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
  1467. off_t offset, struct fuse_file_info *fi) {
  1468. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1469. int ret = -ENOENT;
  1470. @try {
  1471. NSError* error = nil;
  1472. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1473. NSArray *contents =
  1474. [fs contentsOfDirectoryAtPath:[NSString stringWithUTF8String:path]
  1475. error:&error];
  1476. if (contents) {
  1477. ret = 0;
  1478. filler(buf, ".", NULL, 0);
  1479. filler(buf, "..", NULL, 0);
  1480. for (int i = 0, count = [contents count]; i < count; i++) {
  1481. filler(buf, [[contents objectAtIndex:i] UTF8String], NULL, 0);
  1482. }
  1483. } else {
  1484. MAYBE_USE_ERROR(ret, error);
  1485. }
  1486. }
  1487. @catch (id exception) { }
  1488. [pool release];
  1489. return ret;
  1490. }
  1491. static int fusefm_create(const char* path, mode_t mode, struct fuse_file_info* fi) {
  1492. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1493. int ret = -EACCES;
  1494. @try {
  1495. NSError* error = nil;
  1496. id userData = nil;
  1497. unsigned long perm = mode & ALLPERMS;
  1498. NSDictionary* attribs =
  1499. [NSDictionary dictionaryWithObject:[NSNumber numberWithLong:perm]
  1500. forKey:NSFilePosixPermissions];
  1501. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1502. if ([fs createFileAtPath:[NSString stringWithUTF8String:path]
  1503. attributes:attribs
  1504. userData:&userData
  1505. error:&error]) {
  1506. ret = 0;
  1507. if (userData != nil) {
  1508. [userData retain];
  1509. fi->fh = (uintptr_t)userData;
  1510. }
  1511. } else {
  1512. MAYBE_USE_ERROR(ret, error);
  1513. }
  1514. }
  1515. @catch (id exception) { }
  1516. [pool release];
  1517. return ret;
  1518. }
  1519. static int fusefm_open(const char *path, struct fuse_file_info *fi) {
  1520. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1521. int ret = -ENOENT; // TODO: Default to 0 (success) since a file-system does
  1522. // not necessarily need to implement open?
  1523. @try {
  1524. id userData = nil;
  1525. NSError* error = nil;
  1526. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1527. if ([fs openFileAtPath:[NSString stringWithUTF8String:path]
  1528. mode:fi->flags
  1529. userData:&userData
  1530. error:&error]) {
  1531. ret = 0;
  1532. if (userData != nil) {
  1533. [userData retain];
  1534. fi->fh = (uintptr_t)userData;
  1535. }
  1536. } else {
  1537. MAYBE_USE_ERROR(ret, error);
  1538. }
  1539. }
  1540. @catch (id exception) { }
  1541. [pool release];
  1542. return ret;
  1543. }
  1544. static int fusefm_release(const char *path, struct fuse_file_info *fi) {
  1545. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1546. @try {
  1547. id userData = (id)(uintptr_t)fi->fh;
  1548. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1549. [fs releaseFileAtPath:[NSString stringWithUTF8String:path] userData:userData];
  1550. if (userData) {
  1551. [userData release];
  1552. }
  1553. }
  1554. @catch (id exception) { }
  1555. [pool release];
  1556. return 0;
  1557. }
  1558. static int fusefm_fsync(const char* path, int isdatasync,
  1559. struct fuse_file_info* fi) {
  1560. // TODO: Support fsync?
  1561. return 0;
  1562. }
  1563. static int fusefm_write(const char* path, const char* buf, size_t size,
  1564. off_t offset, struct fuse_file_info* fi) {
  1565. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1566. int ret = -EIO;
  1567. @try {
  1568. NSError* error = nil;
  1569. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1570. ret = [fs writeFileAtPath:[NSString stringWithUTF8String:path]
  1571. userData:(id)(uintptr_t)fi->fh
  1572. buffer:buf
  1573. size:size
  1574. offset:offset
  1575. error:&error];
  1576. MAYBE_USE_ERROR(ret, error);
  1577. }
  1578. @catch (id exception) { }
  1579. [pool release];
  1580. return ret;
  1581. }
  1582. static int fusefm_read(const char *path, char *buf, size_t size, off_t offset,
  1583. struct fuse_file_info *fi) {
  1584. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1585. int ret = -EIO;
  1586. @try {
  1587. NSError* error = nil;
  1588. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1589. ret = [fs readFileAtPath:[NSString stringWithUTF8String:path]
  1590. userData:(id)(uintptr_t)fi->fh
  1591. buffer:buf
  1592. size:size
  1593. offset:offset
  1594. error:&error];
  1595. MAYBE_USE_ERROR(ret, error);
  1596. }
  1597. @catch (id exception) { }
  1598. [pool release];
  1599. return ret;
  1600. }
  1601. static int fusefm_readlink(const char *path, char *buf, size_t size)
  1602. {
  1603. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1604. int ret = -ENOENT;
  1605. @try {
  1606. NSString* linkPath = [NSString stringWithUTF8String:path];
  1607. NSError* error = nil;
  1608. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1609. NSString *pathContent = [fs destinationOfSymbolicLinkAtPath:linkPath
  1610. error:&error];
  1611. if (pathContent != nil) {
  1612. ret = 0;
  1613. [pathContent getFileSystemRepresentation:buf maxLength:size];
  1614. } else {
  1615. MAYBE_USE_ERROR(ret, error);
  1616. }
  1617. }
  1618. @catch (id exception) { }
  1619. [pool release];
  1620. return ret;
  1621. }
  1622. static int fusefm_getxattr(const char *path, const char *name, char *value,
  1623. size_t size, uint32_t position) {
  1624. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1625. int ret = -ENOATTR;
  1626. @try {
  1627. NSError* error = nil;
  1628. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1629. NSData *data = [fs valueOfExtendedAttribute:[NSString stringWithUTF8String:name]
  1630. ofItemAtPath:[NSString stringWithUTF8String:path]
  1631. position:position
  1632. error:&error];
  1633. if (data != nil) {
  1634. ret = [data length]; // default to returning size of buffer.
  1635. if (value) {
  1636. if (size > [data length]) {
  1637. size = [data length];
  1638. }
  1639. [data getBytes:value length:size];
  1640. ret = size; // bytes read
  1641. }
  1642. } else {
  1643. MAYBE_USE_ERROR(ret, error);
  1644. }
  1645. }
  1646. @catch (id exception) { }
  1647. [pool release];
  1648. return ret;
  1649. }
  1650. static int fusefm_setxattr(const char *path, const char *name, const char *value,
  1651. size_t size, int options, uint32_t position) {
  1652. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1653. int ret = -EPERM;
  1654. @try {
  1655. NSError* error = nil;
  1656. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1657. if ([fs setExtendedAttribute:[NSString stringWithUTF8String:name]
  1658. ofItemAtPath:[NSString stringWithUTF8String:path]
  1659. value:[NSData dataWithBytes:value length:size]
  1660. position:position
  1661. options:options
  1662. error:&error]) {
  1663. ret = 0;
  1664. } else {
  1665. MAYBE_USE_ERROR(ret, error);
  1666. }
  1667. }
  1668. @catch (id exception) { }
  1669. [pool release];
  1670. return ret;
  1671. }
  1672. static int fusefm_removexattr(const char *path, const char *name) {
  1673. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1674. int ret = -ENOATTR;
  1675. @try {
  1676. NSError* error = nil;
  1677. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1678. if ([fs removeExtendedAttribute:[NSString stringWithUTF8String:name]
  1679. ofItemAtPath:[NSString stringWithUTF8String:path]
  1680. error:&error]) {
  1681. ret = 0;
  1682. } else {
  1683. MAYBE_USE_ERROR(ret, error);
  1684. }
  1685. }
  1686. @catch (id exception) { }
  1687. [pool release];
  1688. return ret;
  1689. }
  1690. static int fusefm_listxattr(const char *path, char *list, size_t size)
  1691. {
  1692. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1693. int ret = -ENOTSUP;
  1694. @try {
  1695. NSError* error = nil;
  1696. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1697. NSArray* attributeNames =
  1698. [fs extendedAttributesOfItemAtPath:[NSString stringWithUTF8String:path]
  1699. error:&error];
  1700. if (attributeNames != nil) {
  1701. char zero = 0;
  1702. NSMutableData* data = [NSMutableData dataWithCapacity:size];
  1703. for (int i = 0, count = [attributeNames count]; i < count; i++) {
  1704. [data appendData:[[attributeNames objectAtIndex:i] dataUsingEncoding:NSUTF8StringEncoding]];
  1705. [data appendBytes:&zero length:1];
  1706. }
  1707. ret = [data length]; // default to returning size of buffer.
  1708. if (list) {
  1709. if (size > [data length]) {
  1710. size = [data length];
  1711. }
  1712. [data getBytes:list length:size];
  1713. }
  1714. } else {
  1715. MAYBE_USE_ERROR(ret, error);
  1716. }
  1717. }
  1718. @catch (id exception) { }
  1719. [pool release];
  1720. return ret;
  1721. }
  1722. static int fusefm_rename(const char* path, const char* toPath) {
  1723. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1724. int ret = -EACCES;
  1725. @try {
  1726. NSString* source = [NSString stringWithUTF8String:path];
  1727. NSString* destination = [NSString stringWithUTF8String:toPath];
  1728. NSError* error = nil;
  1729. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1730. if ([fs moveItemAtPath:source toPath:destination error:&error]) {
  1731. ret = 0; // Success!
  1732. } else {
  1733. MAYBE_USE_ERROR(ret, error);
  1734. }
  1735. }
  1736. @catch (id exception) { }
  1737. [pool release];
  1738. return ret;
  1739. }
  1740. static int fusefm_mkdir(const char* path, mode_t mode) {
  1741. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1742. int ret = -EACCES;
  1743. @try {
  1744. NSError* error = nil;
  1745. unsigned long perm = mode & ALLPERMS;
  1746. NSDictionary* attribs =
  1747. [NSDictionary dictionaryWithObject:[NSNumber numberWithLong:perm]
  1748. forKey:NSFilePosixPermissions];
  1749. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1750. if ([fs createDirectoryAtPath:[NSString stringWithUTF8String:path]
  1751. attributes:attribs
  1752. error:(NSError **)error]) {
  1753. ret = 0; // Success!
  1754. } else {
  1755. if (error != nil) {
  1756. ret = -[error code];
  1757. }
  1758. }
  1759. }
  1760. @catch (id exception) { }
  1761. [pool release];
  1762. return ret;
  1763. }
  1764. static int fusefm_unlink(const char* path) {
  1765. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1766. int ret = -EACCES;
  1767. @try {
  1768. NSError* error = nil;
  1769. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1770. if ([fs removeItemAtPath:[NSString stringWithUTF8String:path]
  1771. error:&error]) {
  1772. ret = 0; // Success!
  1773. } else {
  1774. MAYBE_USE_ERROR(ret, error);
  1775. }
  1776. }
  1777. @catch (id exception) { }
  1778. [pool release];
  1779. return ret;
  1780. }
  1781. static int fusefm_rmdir(const char* path) {
  1782. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1783. int ret = -EACCES;
  1784. @try {
  1785. NSError* error = nil;
  1786. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1787. if ([fs removeDirectoryAtPath:[NSString stringWithUTF8String:path]
  1788. error:&error]) {
  1789. ret = 0; // Success!
  1790. } else {
  1791. MAYBE_USE_ERROR(ret, error);
  1792. }
  1793. }
  1794. @catch (id exception) { }
  1795. [pool release];
  1796. return ret;
  1797. }
  1798. static int fusefm_symlink(const char* path1, const char* path2) {
  1799. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1800. int ret = -EACCES;
  1801. @try {
  1802. NSError* error = nil;
  1803. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1804. if ([fs createSymbolicLinkAtPath:[NSString stringWithUTF8String:path2]
  1805. withDestinationPath:[NSString stringWithUTF8String:path1]
  1806. error:&error]) {
  1807. ret = 0; // Success!
  1808. } else {
  1809. MAYBE_USE_ERROR(ret, error);
  1810. }
  1811. }
  1812. @catch (id exception) { }
  1813. [pool release];
  1814. return ret;
  1815. }
  1816. static int fusefm_link(const char* path1, const char* path2) {
  1817. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1818. int ret = -EACCES;
  1819. @try {
  1820. NSError* error = nil;
  1821. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1822. if ([fs linkItemAtPath:[NSString stringWithUTF8String:path1]
  1823. toPath:[NSString stringWithUTF8String:path2]
  1824. error:&error]) {
  1825. ret = 0; // Success!
  1826. } else {
  1827. MAYBE_USE_ERROR(ret, error);
  1828. }
  1829. }
  1830. @catch (id exception) { }
  1831. [pool release];
  1832. return ret;
  1833. }
  1834. static void* fusefm_init(struct fuse_conn_info* conn) {
  1835. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1836. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1837. [fs retain];
  1838. @try {
  1839. [fs fuseInit];
  1840. }
  1841. @catch (id exception) { }
  1842. if ([fs enableExtendedTimes]) {
  1843. FUSE_ENABLE_XTIMES(conn);
  1844. }
  1845. #if 0 // TODO: Remove #if 0 if/when setvolname is supported.
  1846. if ([fs enableSetVolumeName]) {
  1847. FUSE_ENABLE_SETVOLNAME(conn);
  1848. }
  1849. #endif
  1850. [pool release];
  1851. return fs;
  1852. }
  1853. static void fusefm_destroy(void* private_data) {
  1854. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1855. GMUserFileSystem* fs = (GMUserFileSystem *)private_data;
  1856. @try {
  1857. [fs fuseDestroy];
  1858. }
  1859. @catch (id exception) { }
  1860. [fs release];
  1861. [pool release];
  1862. }
  1863. static int fusefm_setvolname(const char* name) {
  1864. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1865. int ret = -ENOSYS;
  1866. @try {
  1867. NSError* error = nil;
  1868. NSDictionary* attribs =
  1869. [NSDictionary dictionaryWithObject:[NSString stringWithUTF8String:name]
  1870. forKey:kGMUserFileSystemVolumeNameKey];
  1871. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1872. if ([fs setAttributes:attribs ofFileSystemAtPath:@"/" error:&error]) {
  1873. ret = 0;
  1874. } else {
  1875. MAYBE_USE_ERROR(ret, error);
  1876. }
  1877. }
  1878. @catch (id exception) { }
  1879. [pool release];
  1880. return ret;
  1881. }
  1882. static int fusefm_exchange(const char* p1, const char* p2, unsigned long opts) {
  1883. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1884. int ret = -ENOSYS;
  1885. @try {
  1886. NSError* error = nil;
  1887. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1888. if ([fs exchangeDataOfItemAtPath:[NSString stringWithUTF8String:p1]
  1889. withItemAtPath:[NSString stringWithUTF8String:p2]
  1890. error:&error]) {
  1891. ret = 0;
  1892. } else {
  1893. MAYBE_USE_ERROR(ret, error);
  1894. }
  1895. }
  1896. @catch (id exception) { }
  1897. [pool release];
  1898. return ret;
  1899. }
  1900. static int fusefm_getxtimes(const char* path, struct timespec* bkuptime,
  1901. struct timespec* crtime) {
  1902. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1903. int ret = -ENOENT;
  1904. @try {
  1905. NSError* error = nil;
  1906. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  1907. NSDictionary* attribs =
  1908. [fs extendedTimesOfItemAtPath:[NSString stringWithUTF8String:path]
  1909. userData:nil // TODO: Maybe this should support FH?
  1910. error:&error];
  1911. if (attribs) {
  1912. ret = 0;
  1913. NSDate* creationDate = [attribs objectForKey:NSFileCreationDate];
  1914. if (creationDate) {
  1915. const double seconds_dp = [creationDate timeIntervalSince1970];
  1916. const time_t t_sec = (time_t) seconds_dp;
  1917. const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond);
  1918. const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0;
  1919. crtime->tv_sec = t_sec;
  1920. crtime->tv_nsec = t_nsec;
  1921. } else {
  1922. memset(crtime, 0, sizeof(crtime));
  1923. }
  1924. NSDate* backupDate = [attribs objectForKey:kGMUserFileSystemFileBackupDateKey];
  1925. if (backupDate) {
  1926. const double seconds_dp = [backupDate timeIntervalSince1970];
  1927. const time_t t_sec = (time_t) seconds_dp;
  1928. const double nanoseconds_dp = ((seconds_dp - t_sec) * kNanoSecondsPerSecond);
  1929. const long t_nsec = (nanoseconds_dp > 0 ) ? nanoseconds_dp : 0;
  1930. bkuptime->tv_sec = t_sec;
  1931. bkuptime->tv_nsec = t_nsec;
  1932. } else {
  1933. memset(bkuptime, 0, sizeof(bkuptime));
  1934. }
  1935. } else {
  1936. MAYBE_USE_ERROR(ret, error);
  1937. }
  1938. }
  1939. @catch (id exception) { }
  1940. [pool release];
  1941. return ret;
  1942. }
  1943. static NSDate* dateWithTimespec(const struct timespec* spec) {
  1944. const NSTimeInterval time_ns = spec->tv_nsec;
  1945. const NSTimeInterval time_sec = spec->tv_sec + (time_ns / kNanoSecondsPerSecond);
  1946. return [NSDate dateWithTimeIntervalSince1970:time_sec];
  1947. }
  1948. static NSDictionary* dictionaryWithAttributes(const struct setattr_x* attrs) {
  1949. NSMutableDictionary* dict = [NSMutableDictionary dictionary];
  1950. if (SETATTR_WANTS_MODE(attrs)) {
  1951. unsigned long perm = attrs->mode & ALLPERMS;
  1952. [dict setObject:[NSNumber numberWithLong:perm]
  1953. forKey:NSFilePosixPermissions];
  1954. }
  1955. if (SETATTR_WANTS_UID(attrs)) {
  1956. [dict setObject:[NSNumber numberWithLong:attrs->uid]
  1957. forKey:NSFileOwnerAccountID];
  1958. }
  1959. if (SETATTR_WANTS_GID(attrs)) {
  1960. [dict setObject:[NSNumber numberWithLong:attrs->gid]
  1961. forKey:NSFileGroupOwnerAccountID];
  1962. }
  1963. if (SETATTR_WANTS_SIZE(attrs)) {
  1964. [dict setObject:[NSNumber numberWithLongLong:attrs->size]
  1965. forKey:NSFileSize];
  1966. }
  1967. if (SETATTR_WANTS_ACCTIME(attrs)) {
  1968. [dict setObject:dateWithTimespec(&(attrs->acctime))
  1969. forKey:kGMUserFileSystemFileAccessDateKey];
  1970. }
  1971. if (SETATTR_WANTS_MODTIME(attrs)) {
  1972. [dict setObject:dateWithTimespec(&(attrs->modtime))
  1973. forKey:NSFileModificationDate];
  1974. }
  1975. if (SETATTR_WANTS_CRTIME(attrs)) {
  1976. [dict setObject:dateWithTimespec(&(attrs->crtime))
  1977. forKey:NSFileCreationDate];
  1978. }
  1979. if (SETATTR_WANTS_CHGTIME(attrs)) {
  1980. [dict setObject:dateWithTimespec(&(attrs->chgtime))
  1981. forKey:kGMUserFileSystemFileChangeDateKey];
  1982. }
  1983. if (SETATTR_WANTS_BKUPTIME(attrs)) {
  1984. [dict setObject:dateWithTimespec(&(attrs->bkuptime))
  1985. forKey:kGMUserFileSystemFileBackupDateKey];
  1986. }
  1987. if (SETATTR_WANTS_FLAGS(attrs)) {
  1988. [dict setObject:[NSNumber numberWithLong:attrs->flags]
  1989. forKey:kGMUserFileSystemFileFlagsKey];
  1990. }
  1991. return dict;
  1992. }
  1993. static int fusefm_fsetattr_x(const char* path, struct setattr_x* attrs,
  1994. struct fuse_file_info* fi) {
  1995. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  1996. int ret = 0; // NOTE: Return success by default.
  1997. @try {
  1998. NSError* error = nil;
  1999. NSDictionary* attribs = dictionaryWithAttributes(attrs);
  2000. GMUserFileSystem* fs = [GMUserFileSystem currentFS];
  2001. if ([fs setAttributes:attribs
  2002. ofItemAtPath:[NSString stringWithUTF8String:path]
  2003. userData:(fi ? (id)(uintptr_t)fi->fh : nil)
  2004. error:&error]) {
  2005. ret = 0;
  2006. } else {
  2007. MAYBE_USE_ERROR(ret, error);
  2008. }
  2009. }
  2010. @catch (id exception) { }
  2011. [pool release];
  2012. return ret;
  2013. }
  2014. static int fusefm_setattr_x(const char* path, struct setattr_x* attrs) {
  2015. return fusefm_fsetattr_x(path, attrs, nil);
  2016. }
  2017. #undef MAYBE_USE_ERROR
  2018. static struct fuse_operations fusefm_oper = {
  2019. .init = fusefm_init,
  2020. .destroy = fusefm_destroy,
  2021. .statfs = fusefm_statfs,
  2022. .getattr = fusefm_getattr,
  2023. .fgetattr = fusefm_fgetattr,
  2024. .readdir = fusefm_readdir,
  2025. .open = fusefm_open,
  2026. .release = fusefm_release,
  2027. .read = fusefm_read,
  2028. .readlink = fusefm_readlink,
  2029. .write = fusefm_write,
  2030. .create = fusefm_create,
  2031. .getxattr = fusefm_getxattr,
  2032. .setxattr = fusefm_setxattr,
  2033. .removexattr = fusefm_removexattr,
  2034. .listxattr = fusefm_listxattr,
  2035. .mkdir = fusefm_mkdir,
  2036. .unlink = fusefm_unlink,
  2037. .rmdir = fusefm_rmdir,
  2038. .symlink = fusefm_symlink,
  2039. .rename = fusefm_rename,
  2040. .link = fusefm_link,
  2041. .fsync = fusefm_fsync,
  2042. .setvolname = fusefm_setvolname,
  2043. .exchange = fusefm_exchange,
  2044. .getxtimes = fusefm_getxtimes,
  2045. .setattr_x = fusefm_setattr_x,
  2046. .fsetattr_x = fusefm_fsetattr_x,
  2047. };
  2048. #pragma mark Internal Mount
  2049. - (void)postMountError:(NSError *)error {
  2050. assert([internal_ status] == GMUserFileSystem_MOUNTING);
  2051. [internal_ setStatus:GMUserFileSystem_FAILURE];
  2052. NSDictionary* userInfo =
  2053. [NSDictionary dictionaryWithObjectsAndKeys:
  2054. [internal_ mountPath], kGMUserFileSystemMountPathKey,
  2055. error, kGMUserFileSystemErrorKey,
  2056. nil, nil];
  2057. NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  2058. [center postNotificationName:kGMUserFileSystemMountFailed object:self
  2059. userInfo:userInfo];
  2060. }
  2061. // The stat field member we use to check for a dead file system.
  2062. #if __DARWIN_64_BIT_INO_T
  2063. #define DEAD_FS_FIELD f_fssubtype
  2064. #else
  2065. #define DEAD_FS_FIELD f_reserved1
  2066. #endif
  2067. - (void)mount:(NSDictionary *)args {
  2068. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  2069. assert([internal_ status] == GMUserFileSystem_NOT_MOUNTED);
  2070. [internal_ setStatus:GMUserFileSystem_MOUNTING];
  2071. NSArray* options = [args objectForKey:@"options"];
  2072. BOOL isThreadSafe = [internal_ isThreadSafe];
  2073. BOOL shouldForeground = [[args objectForKey:@"shouldForeground"] boolValue];
  2074. // Maybe there is a dead fuse FS stuck on our mount point?
  2075. struct statfs statfs_buf;
  2076. memset(&statfs_buf, 0, sizeof(statfs_buf));
  2077. int rc = statfs([[internal_ mountPath] UTF8String], &statfs_buf);
  2078. if (rc == 0) {
  2079. if (statfs_buf.DEAD_FS_FIELD == (short)(-1)) {
  2080. // We use a special indicator value from MacFUSE in the f_fssubtype field
  2081. // to indicate that the currently mounted filesystem is dead. It probably
  2082. // crashed and was never unmounted.
  2083. // NOTE: If we ever drop 10.4 support, then we can use statfs64 and get
  2084. // the f_fssubtype field properly here. Until then, it is in f_reserved1.
  2085. rc = unmount([[internal_ mountPath] UTF8String], 0);
  2086. if (rc != 0) {
  2087. NSString* description = @"Unable to unmount an existing 'dead' filesystem.";
  2088. NSDictionary* userInfo =
  2089. [NSDictionary dictionaryWithObjectsAndKeys:
  2090. description, NSLocalizedDescriptionKey,
  2091. [GMUserFileSystem errorWithCode:errno], NSUnderlyingErrorKey,
  2092. nil, nil];
  2093. NSError* error = [NSError errorWithDomain:kGMUserFileSystemErrorDomain
  2094. code:GMUserFileSystem_ERROR_UNMOUNT_DEADFS
  2095. userInfo:userInfo];
  2096. [self postMountError:error];
  2097. [pool release];
  2098. return;
  2099. }
  2100. if ([[internal_ mountPath] hasPrefix:@"/Volumes/"]) {
  2101. // Directories for mounts in @"/Volumes/..." are removed automatically
  2102. // when an unmount occurs. This is an asynchronous process, so we need
  2103. // to wait until the directory is removed before proceeding. Otherwise,
  2104. // it may be removed after we try to create the mount directory and the
  2105. // mount attempt will fail.
  2106. BOOL isDirectoryRemoved = NO;
  2107. static const int kWaitForDeadFSTimeoutSeconds = 5;
  2108. struct stat stat_buf;
  2109. for (int i = 0; i < 2 * kWaitForDeadFSTimeoutSeconds; ++i) {
  2110. usleep(500000); // .5 seconds
  2111. rc = stat([[internal_ mountPath] UTF8String], &stat_buf);
  2112. if (rc != 0 && errno == ENOENT) {
  2113. isDirectoryRemoved = YES;
  2114. break;
  2115. }
  2116. }
  2117. if (!isDirectoryRemoved) {
  2118. NSString* description =
  2119. @"Gave up waiting for directory under /Volumes to be removed after "
  2120. "cleaning up a dead file system mount.";
  2121. NSDictionary* userInfo =
  2122. [NSDictionary dictionaryWithObjectsAndKeys:
  2123. description, NSLocalizedDescriptionKey,
  2124. nil, nil];
  2125. NSError* error = [NSError errorWithDomain:kGMUserFileSystemErrorDomain
  2126. code:GMUserFileSystem_ERROR_UNMOUNT_DEADFS_RMDIR
  2127. userInfo:userInfo];
  2128. [self postMountError:error];
  2129. [pool release];
  2130. return;
  2131. }
  2132. }
  2133. }
  2134. }
  2135. // Check and create mount path as necessary.
  2136. struct stat stat_buf;
  2137. memset(&stat_buf, 0, sizeof(stat_buf));
  2138. rc = stat([[internal_ mountPath] UTF8String], &stat_buf);
  2139. if (rc == 0) {
  2140. if (!(stat_buf.st_mode & S_IFDIR)) {
  2141. [self postMountError:[GMUserFileSystem errorWithCode:ENOTDIR]];
  2142. [pool release];
  2143. return;
  2144. }
  2145. } else {
  2146. switch (errno) {
  2147. case ENOTDIR: {
  2148. [self postMountError:[GMUserFileSystem errorWithCode:ENOTDIR]];
  2149. [pool release];
  2150. return;
  2151. }
  2152. case ENOENT: {
  2153. // The mount directory does not exists; we'll create as a courtesy.
  2154. rc = mkdir([[internal_ mountPath] UTF8String], 0775);
  2155. if (rc != 0) {
  2156. NSDictionary* userInfo =
  2157. [NSDictionary dictionaryWithObjectsAndKeys:
  2158. @"Unable to create directory for mount point.", NSLocalizedDescriptionKey,
  2159. [GMUserFileSystem errorWithCode:errno], NSUnderlyingErrorKey,
  2160. nil, nil];
  2161. NSError* error = [NSError errorWithDomain:kGMUserFileSystemErrorDomain
  2162. code:GMUserFileSystem_ERROR_MOUNT_MKDIR
  2163. userInfo:userInfo];
  2164. [self postMountError:error];
  2165. [pool release];
  2166. return;
  2167. }
  2168. break;
  2169. }
  2170. }
  2171. }
  2172. // Trigger initialization of NSFileManager. This is rather lame, but if we
  2173. // don't call directoryContents before we mount our FUSE filesystem and
  2174. // the filesystem uses NSFileManager we may deadlock. It seems that the
  2175. // NSFileManager class will do lazy init and will query all mounted
  2176. // filesystems. This leads to deadlock when we re-enter our mounted fuse fs.
  2177. // Once initialized it seems to work fine.
  2178. NSFileManager* fileManager = [NSFileManager defaultManager];
  2179. [fileManager directoryContentsAtPath:@"/Volumes"];
  2180. NSMutableArray* arguments =
  2181. [NSMutableArray arrayWithObject:[[NSBundle mainBundle] executablePath]];
  2182. if (!isThreadSafe) {
  2183. [arguments addObject:@"-s"]; // Force single-threaded mode.
  2184. }
  2185. if (shouldForeground) {
  2186. [arguments addObject:@"-f"]; // Forground rather than daemonize.
  2187. }
  2188. for (int i = 0; i < [options count]; ++i) {
  2189. NSString* option = [options objectAtIndex:i];
  2190. if ([option length] > 0) {
  2191. [arguments addObject:[NSString stringWithFormat:@"-o%@",option]];
  2192. }
  2193. }
  2194. [arguments addObject:[internal_ mountPath]];
  2195. [args release]; // We don't need packaged up args any more.
  2196. // Start Fuse Main
  2197. int argc = [arguments count];
  2198. const char* argv[argc];
  2199. for (int i = 0, count = [arguments count]; i < count; i++) {
  2200. NSString* argument = [arguments objectAtIndex:i];
  2201. argv[i] = strdup([argument UTF8String]); // We'll just leak this for now.
  2202. }
  2203. if ([[internal_ delegate] respondsToSelector:@selector(willMount)]) {
  2204. [[internal_ delegate] willMount];
  2205. }
  2206. [pool release];
  2207. int ret = fuse_main(argc, (char **)argv, &fusefm_oper, self);
  2208. pool = [[NSAutoreleasePool alloc] init];
  2209. if ([internal_ status] == GMUserFileSystem_MOUNTING) {
  2210. // If we returned from fuse_main while we still think we are
  2211. // mounting then an error must have occurred during mount.
  2212. NSString* description = [NSString stringWithFormat:@
  2213. "Internal fuse error (rc=%d) while attempting to mount the file system. "
  2214. "For now, the best way to diagnose is to look for error messages using "
  2215. "Console.", ret];
  2216. NSDictionary* userInfo =
  2217. [NSDictionary dictionaryWithObjectsAndKeys:
  2218. description, NSLocalizedDescriptionKey,
  2219. nil, nil];
  2220. NSError* error = [NSError errorWithDomain:kGMUserFileSystemErrorDomain
  2221. code:GMUserFileSystem_ERROR_MOUNT_FUSE_MAIN_INTERNAL
  2222. userInfo:userInfo];
  2223. [self postMountError:error];
  2224. } else {
  2225. [internal_ setStatus:GMUserFileSystem_NOT_MOUNTED];
  2226. }
  2227. [pool release];
  2228. }
  2229. @end