Commits

Bill Garrison committed 00152dc

Dropped -maximumExtendedAttributeSize method. Can't reliably determine what this value is and who cares. Use pathconf(2) if you really need to know. Experimentally, maximum extended attribute size is 2 GB on Mac OS 10.9/iOS 7, but only 128KB on 10.8.

Comments (0)

Files changed (4)

NSURL+SOExtendedAttributes.h

  
  ** Compatibility **
  
- SOExtendedAttributes is compatible with Mac OS X 10.6+ and iOS 5. The clang compiler is required. The source file `NSURL+SOExtendedAttributes.m` must be compiled with ARC enabled. For an alternate Cocoa implementation compatible with Mac OS X 10.4 and greater, see [UKXattrMetadataStore](http://zathras.de/angelweb/sourcecode.htm).
+ SOExtendedAttributes is compatible with Mac OS X 10.7+ and iOS 5. The clang compiler is required. The source file `NSURL+SOExtendedAttributes.m` must be compiled with ARC enabled. For an alternate Cocoa implementation compatible with Mac OS X 10.4 and greater, see [UKXattrMetadataStore](http://zathras.de/angelweb/sourcecode.htm).
  
  ** Symbolic links **
  
  ** Error Reporting **
  
  SOExtendedAttributes reports errors under the domain `SOExtendedAttributesErrorDomain`. When multiple errors occur on getting or setting extended attributes in a batch, those errors are collected in an NSArray and reported via error's -userInfo dictionary under `SOUnderlyingErrorsKey`.
+ 
  */
 
 extern NSString * const iCloudDoNotBackupAttributeName;
 @interface NSURL (SOExtendedAttributes)
 
 /**
-The maximum size in bytes for an extended attribute on the receiver.
+ Retrieves the extended attribute data with the given name.
  
- Uses pathconf(2) to determine the maximum number of bytes that an extended attribute can hold. This is a computed and approximate value because the system does not report a maximum extended attribute size directly; instead, it reports the number of bits by the file system object to used to hold the maximum extended attribute size.
+ @param name The name of the extended attribute. Throws `NSInvalidArgumentException` if name is nil or empty.
+ @param outError If an error occurs, upon return contains an NSError object that describes the problem. Pass NULL if you're not interested in error reporting.
  
- The reported maximum extended attribute size for HFS+ on Mac OS X 10.8 is 128KB (131072 bytes), but experimentally, it is actually 50 bytes less: 131022 bytes.
+ @return The retrieved extended attribute data, or nil if there was an error.
  
- @return The maximum size in bytes for an extended attribute on the receiver.
+ @since 1.0.7
  */
-- (NSUInteger) maximumExtendedAttributesSize;
+- (NSData *) dataForExtendedAttribute:(NSString *)name error:(NSError * __autoreleasing *)outError;
+
+/**
+ Sets an extended attribute with the given name and data value. 
+ 
+ The data value is used directly, without any further transformation.
+ 
+ @param data The extended attribute data to be written. If nil, returns YES immediately.
+ @param name The name of the extended attribute. Throws `NSInvalidArgumentException` if name is nil or empty.
+ @param outError If an error occurs, upon return contains an NSError object that describes the problem. Pass NULL if you're not interested in error reporting.
+ 
+ @return YES if the extended attribute was set. If NO, error will be reported via outError parameter.
+ 
+ @since 1.0.7
+ */
+- (BOOL) setExtendedAttributeData:(NSData *)data name:(NSString *)name error:(NSError * __autoreleasing *)outError;
+
 
 /** @name Accessing attributes in batches */
 
 @end
 
 /*
- Copyright (c) 2012-2013, Standard Orbit Software, LLC. All rights reserved.
+ Copyright (c) 2012-2014, Standard Orbit Software, LLC. All rights reserved.
  
  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
  

NSURL+SOExtendedAttributes.m

 /*
  NSURL+SOExtendedAttributes
  
- Copyright 2012 Standard Orbit Software, LLC. All rights reserved.
+ Copyright 2012-2014 Standard Orbit Software, LLC. All rights reserved.
  License at the bottom of the file.
  */
 
 
 @implementation NSURL (SOExtendedAttributes)
 
-
-- (NSUInteger) maximumExtendedAttributesSize
-{
-    /* Taken from Bombich Software's mods for rsync 3.0.6; see <http://www.bombich.com/software/opensource/rsync_3.0.6-bombich_20121219.diff> */
-    
-    long numberOfBits;
-    
-    numberOfBits = pathconf ([[self path] fileSystemRepresentation], _PC_XATTR_SIZE_BITS);
-    NSUInteger maximumSize = 0;
-    // Determine the maximum size allowed for non-resource-fork xattrs
-    if (numberOfBits > 0) {
-        if (numberOfBits == 18 || numberOfBits > 31)
-            maximumSize = 131022; // 128KB - 50 bytes; determined experimentally in testing under 10.8
-        else
-            maximumSize = exp2 (numberOfBits) - 1;
-    }
-
-    return maximumSize;
-}
-
 - (NSArray *) namesOfExtendedAttributesWithError:(NSError * __autoreleasing *)outError
 {
     if (![self isFileURL]) [NSException raise:NSInternalInconsistencyException format:@"%s only valid on file URLs", __PRETTY_FUNCTION__];
     return attributeNames;
 }
 
+- (NSData *) dataForExtendedAttribute:(NSString *)name error:(NSError * __autoreleasing *)outError
+{
+    if (!name || [name isEqualToString:@""]) {
+        [NSException raise:NSInvalidArgumentException format:@"extended attribute name cannot be empty"];
+    }
+    
+    /* First query for the size of the attribute value, then pull it into an NSData is possible */
+    
+    const char *itemPath = [[self path] fileSystemRepresentation];
+    void *valueDataBuffer = NULL;
+    ssize_t dataSize = getxattr (itemPath, [name UTF8String], NULL, SIZE_MAX, 0, xattrDefaultOptions);
+    if (dataSize != -1) {
+        valueDataBuffer = calloc(1, dataSize);
+        dataSize = getxattr (itemPath, [name UTF8String], valueDataBuffer, dataSize, 0, xattrDefaultOptions );
+    }
+    
+    /* */
+    
+    if (dataSize == -1) {
+        /* Clean up memory */
+        if (valueDataBuffer) {
+            free(valueDataBuffer); valueDataBuffer = NULL;
+        }
+        
+        if (outError) {
+            NSError *posixError = SOPOSIXErrorForURL(self);
+            NSMutableDictionary *augmentedErrorInfo = [[posixError userInfo] mutableCopy];
+            augmentedErrorInfo[SOExtendedAttributeNameKey] = name;
+            *outError = [NSError errorWithDomain:[posixError domain] code:[posixError code] userInfo:augmentedErrorInfo];
+        }
+        
+    }
+    
+    return [[NSData alloc] initWithBytesNoCopy:valueDataBuffer length:dataSize freeWhenDone:YES];
+}
+
+- (BOOL) setExtendedAttributeData:(NSData *)data name:(NSString *)name error:(NSError * __autoreleasing *)outError
+{
+    if (!name || [name isEqualToString:@""]) {
+        [NSException raise:NSInvalidArgumentException format:@"extended attribute name cannot be empty"];
+    }
+    
+    /* Set data as extended attribute value */
+    
+    int err = setxattr ( [[self path] fileSystemRepresentation], [name UTF8String], [data bytes], [data length], 0, XATTR_NOFOLLOW);
+    if (err != 0)
+    {
+        if (outError) {
+            NSError *posixError = SOPOSIXErrorForURL(self);
+            NSMutableDictionary *augmentedErrorInfo = [[posixError userInfo] mutableCopy];
+            augmentedErrorInfo[SOExtendedAttributeNameKey] = name;
+            *outError = [NSError errorWithDomain:[posixError domain] code:[posixError code] userInfo:augmentedErrorInfo];
+        }
+    }
+    
+    return (err == 0);
+}
+
 #pragma mark -
 #pragma mark Batch Attributes
 
             else {
                 
                 /* Set data as extended attribute value */
-                
-                int err = setxattr ( [[self path] fileSystemRepresentation], [name UTF8String], [data bytes], [data length], 0, XATTR_NOFOLLOW);
-                if (err != 0)
-                {
-                    if (collectedErrors) {
-                        NSError *posixError = SOPOSIXErrorForURL(self);
-                        NSMutableDictionary *augmentedErrorInfo = [[posixError userInfo] mutableCopy];
-                        [augmentedErrorInfo setObject:name forKey:SOExtendedAttributeNameKey];
-                        error = [NSError errorWithDomain:[posixError domain] code:[posixError code] userInfo:augmentedErrorInfo];
-                        [collectedErrors addObject:error];
-                    }
+                NSError *xattrError = nil;
+                BOOL didSet = [self setExtendedAttributeData:data name:name error:&xattrError];
+                if (!didSet && xattrError && collectedErrors) {
+                    [collectedErrors addObject:xattrError];
                 }
             }
         }
     
     id retrievedValue = nil;
     
-    
-    /* Get the size of the attribute value and pull it into an NSData is possible */
-    
-    const char *itemPath = [[self path] fileSystemRepresentation];
-    void *valueDataBuffer = NULL;
-    ssize_t dataSize = getxattr (itemPath, [name UTF8String], NULL, SIZE_MAX, 0, xattrDefaultOptions);
-    if (dataSize != -1) {
-        valueDataBuffer = calloc(1, dataSize);
-        dataSize = getxattr (itemPath, [name UTF8String], valueDataBuffer, dataSize, 0, xattrDefaultOptions );
-    }
-    
-    /* */
-    
-    if (dataSize == -1) {
-        /* Clean up memory */
-        if (valueDataBuffer) {
-            free(valueDataBuffer); valueDataBuffer = NULL;
-        }
-        
-        if (outError) {
-            NSError *posixError = SOPOSIXErrorForURL(self);
-            NSMutableDictionary *augmentedErrorInfo = [[posixError userInfo] mutableCopy];
-            [augmentedErrorInfo setObject:name forKey:SOExtendedAttributeNameKey];
-            *outError = [NSError errorWithDomain:[posixError domain] code:[posixError code] userInfo:augmentedErrorInfo];
-        }
-        
-    } else {
-        /* Translate from encoded binary plist */
-        if (valueDataBuffer) {
-            NSData *data = [NSData dataWithBytesNoCopy:valueDataBuffer length:dataSize freeWhenDone:YES];
-            retrievedValue = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:outError];
-        }
+    NSData *data = [self dataForExtendedAttribute:name error:outError];
+    if (data) {
+        retrievedValue = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:outError];
     }
     
     return retrievedValue;
 @end
 
 /*
- Copyright (c) 2012, Standard Orbit Software, LLC. All rights reserved.
+ Copyright (c) 2012-2014, Standard Orbit Software, LLC. All rights reserved.
  
  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
  

SOExtendedAttributes.xcodeproj/project.pbxproj

 /* Begin PBXFileReference section */
 		F212A53314CDC35900B65EA9 /* SOExtendedAttributes.UnitTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SOExtendedAttributes.UnitTests.octest; sourceTree = BUILT_PRODUCTS_DIR; };
 		F212A53614CDC35900B65EA9 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; };
-		F212A53B14CDC35900B65EA9 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
-		F212A53C14CDC35900B65EA9 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
-		F212A53D14CDC35900B65EA9 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
 		F212A54C14CDC3D100B65EA9 /* NSURL+SOExtendedAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+SOExtendedAttributes.h"; sourceTree = "<group>"; };
 		F212A54D14CDC3D100B65EA9 /* NSURL+SOExtendedAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+SOExtendedAttributes.m"; sourceTree = "<group>"; };
 		F212A55014CDC58800B65EA9 /* SOExtendedAttributes_UnitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SOExtendedAttributes_UnitTests.m; path = UnitTests/SOExtendedAttributes_UnitTests.m; sourceTree = SOURCE_ROOT; };
 		F212A53A14CDC35900B65EA9 /* Other Frameworks */ = {
 			isa = PBXGroup;
 			children = (
-				F212A53B14CDC35900B65EA9 /* AppKit.framework */,
-				F212A53C14CDC35900B65EA9 /* CoreData.framework */,
-				F212A53D14CDC35900B65EA9 /* Foundation.framework */,
 			);
 			name = "Other Frameworks";
 			sourceTree = "<group>";

UnitTests/SOExtendedAttributes_UnitTests.m

 #include <sys/xattr.h>
 
 @interface SOExtendedAttributes_UnitTests : SenTestCase
+@end
+
+@implementation SOExtendedAttributes_UnitTests
 {
     NSURL *targetURL;
 }
-@end
 
-@implementation SOExtendedAttributes_UnitTests
-
-
-#pragma mark
 #pragma mark Fixture
 
 - (BOOL) createTestURLForTest:(SEL)testSelector
     [super tearDown];
 }
 
-#pragma mark - Size Tests
-
-- (void) testMaximumExtendedAttributeSize
-{
-    /* 
-     Per the man page for pathconf(2), the maximum extended attribute size should be 128KB. It isn't on 10.8, but it's pretty darn close.
-     Your mileage may vary on iOS or future OS X versions.
-     */
-    NSUInteger experimentallyDeterminedMaximumSizeOnOSXMountainLion = ((128 * 1024) - 50);
-
-    STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file");
-    
-    STAssertTrue ([targetURL maximumExtendedAttributesSize] == experimentallyDeterminedMaximumSizeOnOSXMountainLion , @"Expected larger extended attributes");
-}
-
-- (void) testMaximumExtendedAttribute
-{
-    /* 
-     Experimentally determined on 10.8 that the system's reported maximum is not the actual maximum.
-     The actual maximum extended attribute size is the report value - 50 bytes. WTF...
-     */
-    
-    STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file");
-
-    /* Create random data of maximum extended attribute size */
-    NSUInteger maxAttribSize = [targetURL maximumExtendedAttributesSize];
-    int8_t *randomBytes = malloc(maxAttribSize);
-    for (int i = 0; i < maxAttribSize; i++) {
-        randomBytes[i] = arc4random() & 0xFF;
-    }
-    
-    NSData *testData = [NSData dataWithBytesNoCopy:randomBytes length:maxAttribSize freeWhenDone:YES];
-    NSError *error = nil;
-    BOOL added = [targetURL setExtendedAttributeValue:testData forName:@"largestPossibleAttribute" error:&error];
-    STAssertTrue (added, @"expected to have added extended attribute");
-    
-
-    /* Verify that we can also retrieve the maximum sized attribute value intact. */
-    
-    NSData *retrievedAttribute = [targetURL valueOfExtendedAttributeWithName:@"largestPossibleAttribute" error:&error];
-    STAssertNotNil (retrievedAttribute, @"expected to retrieve extended attribute");
-    STAssertTrue ([retrievedAttribute isEqualToData:testData], @"retrieved attribute doesn't equal the source attribute");
-}
-
-
-
 #pragma mark - Error Reporting Tests
 
 - (void) testCollectedErrrors