Index: src/OFFileIRIHandler.m ================================================================== --- src/OFFileIRIHandler.m +++ src/OFFileIRIHandler.m @@ -38,10 +38,11 @@ #include #if defined(OF_LINUX) || defined(OF_MACOS) # include #endif #ifdef OF_HAIKU +# include # include #endif #ifdef OF_WINDOWS # include #endif @@ -1613,20 +1614,21 @@ return true; } #ifdef OF_FILE_MANAGER_SUPPORTS_EXTENDED_ATTRIBUTES -- (OFData *)extendedAttributeDataForName: (OFString *)name - ofItemAtIRI: (OFIRI *)IRI +- (void)getExtendedAttributeData: (OFData **)data + andType: (id *)type + forName: (OFString *)name + ofItemAtIRI: (OFIRI *)IRI { void *pool = objc_autoreleasePoolPush(); OFString *path = IRI.fileSystemRepresentation; OFStringEncoding encoding = [OFLocale encoding]; const char *cPath = [path cStringWithEncoding: encoding]; const char *cName = [name cStringWithEncoding: encoding]; void *value = NULL; - OFData *data; # if defined(OF_LINUX) || defined(OF_MACOS) # if defined(OF_LINUX) ssize_t size = lgetxattr(cPath, cName, NULL, 0); # elif defined(OF_MACOS) ssize_t size = getxattr(cPath, cName, NULL, 0, 0, XATTR_NOFOLLOW); @@ -1647,17 +1649,20 @@ # endif @throw [OFGetItemAttributesFailedException exceptionWithIRI: IRI errNo: errno]; - data = [OFData dataWithItemsNoCopy: value - count: size - freeWhenDone: true]; + *data = [OFData dataWithItemsNoCopy: value + count: size + freeWhenDone: true]; value = NULL; } @finally { OFFreeMemory(value); } + + if (type != NULL) + *type = nil; # elif defined(OF_HAIKU) int fd = open(cPath, O_RDONLY); struct attr_info info; if (fd == -1) @@ -1681,28 +1686,30 @@ (size_t)info.size) != (ssize_t)info.size) @throw [OFGetItemAttributesFailedException exceptionWithIRI: IRI errNo: errno]; - data = [OFData dataWithItemsNoCopy: value - count: (size_t)info.size - freeWhenDone: true]; + *data = [OFData dataWithItemsNoCopy: value + count: (size_t)info.size + freeWhenDone: true]; value = NULL; + + if (type != NULL) + *type = [OFNumber numberWithUnsignedLong: info.type]; } @finally { OFFreeMemory(value); close(fd); } # endif - [data retain]; + [*data retain]; objc_autoreleasePoolPop(pool); - - return [data autorelease]; } - (void)setExtendedAttributeData: (OFData *)data + andType: (id)type forName: (OFString *)name ofItemAtIRI: (OFIRI *)IRI { void *pool = objc_autoreleasePoolPush(); OFString *path = IRI.fileSystemRepresentation; @@ -1710,10 +1717,14 @@ const char *cPath = [path cStringWithEncoding: encoding]; const char *cName = [name cStringWithEncoding: encoding]; size_t size = data.count * data.itemSize; # if defined(OF_LINUX) || defined(OF_MACOS) + if (type != nil) + @throw [OFNotImplementedException exceptionWithSelector: _cmd + object: self]; + # if defined(OF_LINUX) if (lsetxattr(cPath, cName, data.items, size, 0) != 0) { # elif defined(OF_MACOS) if (setxattr(cPath, cName, data.items, size, 0, XATTR_NOFOLLOW) != 0) { # endif @@ -1725,11 +1736,19 @@ attributes: [OFDictionary dictionary] failedAttribute: @"" errNo: errNo]; } # elif defined(OF_HAIKU) + unsigned long long typeInt; int fd; + + if (type != nil && ![type isKindOfClass: [OFNumber class]]) + @throw [OFInvalidArgumentException exception]; + + typeInt = (type != nil ? [type unsignedLongLongValue] : 0); + if (typeInt > UINT32_MAX) + @throw [OFInvalidArgumentException exception]; if (size > SSIZE_MAX) @throw [OFOutOfRangeException exception]; if ((fd = open(cPath, O_WRONLY)) == -1) { @@ -1742,12 +1761,12 @@ failedAttribute: @"" errNo: errNo]; } @try { - if (fs_write_attr(fd, cName, B_RAW_TYPE, 0, data.items, size) != - (ssize_t)size) { + if (fs_write_attr(fd, cName, (uint32_t)typeInt, 0, + data.items, size) != (ssize_t)size) { int errNo = errno; /* * TODO: Add an attribute (prefix?) for extended * attributes? Index: src/OFFileManager.h ================================================================== --- src/OFFileManager.h +++ src/OFFileManager.h @@ -698,17 +698,43 @@ * * This method is not available on some systems. * * @param name The name of the extended attribute * @param path The path of the item to return the extended attribute from + * @return The extended attribute data for the specified name of the item at + * the specified IRI * @throw OFGetItemAttributesFailedException Getting the extended attribute * failed * @throw OFNotImplementedException Getting extended attributes is not * implemented for the specified item */ - (OFData *)extendedAttributeDataForName: (OFString *)name ofItemAtPath: (OFString *)path; + +/** + * @brief Gets the extended attribute data and type for the specified name + * of the item at the specified path. + * + * This method is not available on some systems. + * + * @param data A pointer to `OFData *` that gets set to the data of the + * extended attribute + * @param type A pointer to `id` that gets set to the type of the extended + * attribute, if not `NULL`. Gets set to `nil` if the extended + * attribute has no type. The type of the type depends on the + * system. + * @param name The name of the extended attribute + * @param path The path of the item to return the extended attribute from + * @throw OFGetItemAttributesFailedException Getting the extended attribute + * failed + * @throw OFNotImplementedException Getting extended attributes is not + * implemented for the specified item + */ +- (void)getExtendedAttributeData: (OFData *_Nonnull *_Nonnull)data + andType: (id _Nullable *_Nullable)type + forName: (OFString *)name + ofItemAtPath: (OFString *)path; #endif /** * @brief Returns the extended attribute data for the specified name of the * item at the specified IRI. @@ -715,10 +741,12 @@ * * This method is not available for all IRIs. * * @param name The name of the extended attribute * @param IRI The IRI of the item to return the extended attribute from + * @return The extended attribute data for the specified name of the item at + * the specified IRI * @throw OFGetItemAttributesFailedException Getting the extended attribute * failed * @throw OFUnsupportedProtocolException No handler is registered for the IRI's * scheme * @throw OFNotImplementedException Getting extended attributes is not @@ -725,10 +753,36 @@ * implemented for the specified item */ - (OFData *)extendedAttributeDataForName: (OFString *)name ofItemAtIRI: (OFIRI *)IRI; +/** + * @brief Gets the extended attribute data and type for the specified name + * of the item at the specified IRI. + * + * This method is not available for all IRIs. + * + * @param data A pointer to `OFData *` that gets set to the data of the + * extended attribute + * @param type A pointer to `id` that gets set to the type of the extended + * attribute, if not `NULL`. Gets set to `nil` if the extended + * attribute has no type. The type of the type depends on the IRI + * handler. + * @param name The name of the extended attribute + * @param IRI The IRI of the item to return the extended attribute from + * @throw OFGetItemAttributesFailedException Getting the extended attribute + * failed + * @throw OFUnsupportedProtocolException No handler is registered for the IRI's + * scheme + * @throw OFNotImplementedException Getting extended attributes is not + * implemented for the specified item + */ +- (void)getExtendedAttributeData: (OFData *_Nonnull *_Nonnull)data + andType: (id _Nullable *_Nullable)type + forName: (OFString *)name + ofItemAtIRI: (OFIRI *)IRI; + #ifdef OF_FILE_MANAGER_SUPPORTS_EXTENDED_ATTRIBUTES /** * @brief Sets the extended attribute data for the specified name of the item * at the specified path. * @@ -741,10 +795,34 @@ * failed * @throw OFNotImplementedException Setting extended attributes is not * implemented for the specified item */ - (void)setExtendedAttributeData: (OFData *)data + forName: (OFString *)name + ofItemAtPath: (OFString *)path; + +/** + * @brief Sets the extended attribute data for the specified name of the item + * at the specified path. + * + * This method is not available on some systems. + * + * @param data The data for the extended attribute + * @param type The type for the extended attribute. `nil` does not mean to keep + * the existing type, but to set it to no type. The type of the + * type depends on the system. + * @param name The name of the extended attribute + * @param path The path of the item to set the extended attribute on + * @throw OFSetItemAttributesFailedException Setting the extended attribute + * failed + * @throw OFNotImplementedException Setting extended attributes is not + * implemented for the specified item or a + * type was specified and typed extended + * attributes are not supported + */ +- (void)setExtendedAttributeData: (OFData *)data + andType: (nullable id)type forName: (OFString *)name ofItemAtPath: (OFString *)path; #endif /** @@ -762,10 +840,36 @@ * scheme * @throw OFNotImplementedException Setting extended attributes is not * implemented for the specified item */ - (void)setExtendedAttributeData: (OFData *)data + forName: (OFString *)name + ofItemAtIRI: (OFIRI *)IRI; + +/** + * @brief Sets the extended attribute data for the specified name of the item + * at the specified IRI. + * + * This method is not available for all IRIs. + * + * @param data The data for the extended attribute + * @param type The type for the extended attribute. `nil` does not mean to keep + * the existing type, but to set it to no type. The type of the + * type depends on the IRI handler. + * @param name The name of the extended attribute + * @param IRI The IRI of the item to set the extended attribute on + * @throw OFSetItemAttributesFailedException Setting the extended attribute + * failed + * @throw OFUnsupportedProtocolException No handler is registered for the IRI's + * scheme + * @throw OFNotImplementedException Setting extended attributes is not + * implemented for the specified item or a + * type was specified and typed extended + * attributes are not supported + */ +- (void)setExtendedAttributeData: (OFData *)data + andType: (nullable id)type forName: (OFString *)name ofItemAtIRI: (OFIRI *)IRI; #ifdef OF_FILE_MANAGER_SUPPORTS_EXTENDED_ATTRIBUTES /** Index: src/OFFileManager.m ================================================================== --- src/OFFileManager.m +++ src/OFFileManager.m @@ -937,41 +937,101 @@ #endif - (OFData *)extendedAttributeDataForName: (OFString *)name ofItemAtIRI: (OFIRI *)IRI { + OFData *data; + + [self getExtendedAttributeData: &data + andType: NULL + forName: name + ofItemAtIRI: IRI]; + + return data; +} + +- (void)getExtendedAttributeData: (OFData **)data + andType: (id *)type + forName: (OFString *)name + ofItemAtIRI: (OFIRI *)IRI +{ + void *pool = objc_autoreleasePoolPush(); OFIRIHandler *IRIHandler; if (IRI == nil) @throw [OFInvalidArgumentException exception]; if ((IRIHandler = [OFIRIHandler handlerForIRI: IRI]) == nil) @throw [OFUnsupportedProtocolException exceptionWithIRI: IRI]; - return [IRIHandler extendedAttributeDataForName: name ofItemAtIRI: IRI]; + [IRIHandler getExtendedAttributeData: data + andType: type + forName: name + ofItemAtIRI: IRI]; + + [*data retain]; + if (type != NULL) + [*type retain]; + + objc_autoreleasePoolPop(pool); + + [*data autorelease]; + if (type != NULL) + [*type autorelease]; } #ifdef OF_FILE_MANAGER_SUPPORTS_EXTENDED_ATTRIBUTES - (OFData *)extendedAttributeDataForName: (OFString *)name ofItemAtPath: (OFString *)path { + OFData *data; + + [self getExtendedAttributeData: &data + andType: NULL + forName: name + ofItemAtPath: path]; + + return data; +} + +- (void)getExtendedAttributeData: (OFData **)data + andType: (id *)type + forName: (OFString *)name + ofItemAtPath: (OFString *)path +{ void *pool = objc_autoreleasePoolPush(); - OFData *ret; + + [self getExtendedAttributeData: data + andType: type + forName: name + ofItemAtIRI: [OFIRI fileIRIWithPath: path + isDirectory: false]]; - ret = [self - extendedAttributeDataForName: name - ofItemAtIRI: [OFIRI fileIRIWithPath: path - isDirectory: false]]; - ret = [ret retain]; + [*data retain]; + if (type != NULL) + [*type retain]; objc_autoreleasePoolPop(pool); - return [ret autorelease]; + [*data autorelease]; + if (type != NULL) + [*type autorelease]; } #endif - (void)setExtendedAttributeData: (OFData *)data + forName: (OFString *)name + ofItemAtIRI: (OFIRI *)IRI +{ + [self setExtendedAttributeData: data + andType: nil + forName: name + ofItemAtIRI: IRI]; +} + +- (void)setExtendedAttributeData: (OFData *)data + andType: (id)type forName: (OFString *)name ofItemAtIRI: (OFIRI *)IRI { OFIRIHandler *IRIHandler; @@ -980,24 +1040,39 @@ if ((IRIHandler = [OFIRIHandler handlerForIRI: IRI]) == nil) @throw [OFUnsupportedProtocolException exceptionWithIRI: IRI]; [IRIHandler setExtendedAttributeData: data + andType: type forName: name ofItemAtIRI: IRI]; } #ifdef OF_FILE_MANAGER_SUPPORTS_EXTENDED_ATTRIBUTES - (void)setExtendedAttributeData: (OFData *)data forName: (OFString *)name ofItemAtPath: (OFString *)path +{ + [self setExtendedAttributeData: data + andType: nil + forName: name + ofItemAtPath: path]; +} + +- (void)setExtendedAttributeData: (OFData *)data + andType: (id)type + forName: (OFString *)name + ofItemAtPath: (OFString *)path { void *pool = objc_autoreleasePoolPush(); + [self setExtendedAttributeData: data + andType: type forName: name ofItemAtIRI: [OFIRI fileIRIWithPath: path isDirectory: false]]; + objc_autoreleasePoolPop(pool); } #endif - (void)removeExtendedAttributeForName: (OFString *)name Index: src/OFIRIHandler.h ================================================================== --- src/OFIRIHandler.h +++ src/OFIRIHandler.h @@ -303,19 +303,49 @@ * * This method is not available for all IRIs. * * @param name The name of the extended attribute * @param IRI The IRI of the item to return the extended attribute from + * @return The extended attribute data for the specified name of the item at + * the specified IRI * @throw OFGetItemAttributesFailedException Getting the extended attribute * failed * @throw OFUnsupportedProtocolException The handler cannot handle the IRI's * scheme * @throw OFNotImplementedException Getting extended attributes is not * implemented for the specified item */ - (OFData *)extendedAttributeDataForName: (OFString *)name - ofItemAtIRI: (OFIRI *)IRI; + ofItemAtIRI: (OFIRI *)IRI + OF_DEPRECATED(ObjFW, 1, 1, + "Use -[getExtendedAttributeData:andType:forName:ofItemAtIRI:] instead"); + +/** + * @brief Gets the extended attribute data and type for the specified name + * of the item at the specified IRI. + * + * This method is not available for all IRIs. + * + * @param data A pointer to `OFData *` that gets set to the data of the + * extended attribute + * @param type A pointer to `id` that gets set to the type of the extended + * attribute, if not `NULL`. Gets set to `nil` if the extended + * attribute has no type. The type of the type depends on the IRI + * handler. + * @param name The name of the extended attribute + * @param IRI The IRI of the item to return the extended attribute from + * @throw OFGetItemAttributesFailedException Getting the extended attribute + * failed + * @throw OFUnsupportedProtocolException The handler cannot handle the IRI's + * scheme + * @throw OFNotImplementedException Getting extended attributes is not + * implemented for the specified item + */ +- (void)getExtendedAttributeData: (OFData *_Nonnull *_Nonnull)data + andType: (id _Nullable *_Nullable)type + forName: (OFString *)name + ofItemAtIRI: (OFIRI *)IRI; /** * @brief Sets the extended attribute data for the specified name of the item * at the specified IRI. * @@ -330,10 +360,39 @@ * scheme * @throw OFNotImplementedException Setting extended attributes is not * implemented for the specified item */ - (void)setExtendedAttributeData: (OFData *)data + forName: (OFString *)name + ofItemAtIRI: (OFIRI *)IRI + OF_DEPRECATED(ObjFW, 1, 1, + "Use -[setExtendedAttributeData:andType:forName:ofItemAtIRI:] instead"); + +/** + * @brief Sets the extended attribute data and type for the specified name of + * the item at the specified IRI. + * + * This method is not available for all IRIs. + * Not all IRIs support a non-nil type. + * + * @param data The data for the extended attribute + * @param type The type for the extended attribute. `nil` does not mean to keep + * the existing type, but to set it to no type. The type of the + * type depends on the IRI handler. + * @param name The name of the extended attribute + * @param IRI The IRI of the item to set the extended attribute on + * @throw OFSetItemAttributesFailedException Setting the extended attribute + * failed + * @throw OFUnsupportedProtocolException The handler cannot handle the IRI's + * scheme + * @throw OFNotImplementedException Setting extended attributes is not + * implemented for the specified item or a + * type was specified and typed extended + * attributes are not supported + */ +- (void)setExtendedAttributeData: (OFData *)data + andType: (nullable id)type forName: (OFString *)name ofItemAtIRI: (OFIRI *)IRI; /** * @brief Removes the extended attribute for the specified name of the item at Index: src/OFIRIHandler.m ================================================================== --- src/OFIRIHandler.m +++ src/OFIRIHandler.m @@ -220,21 +220,104 @@ } - (OFData *)extendedAttributeDataForName: (OFString *)name ofItemAtIRI: (OFIRI *)IRI { + OFData *data; + + [self getExtendedAttributeData: &data + andType: NULL + forName: name + ofItemAtIRI: IRI]; + + return data; +} + +- (void)getExtendedAttributeData: (OFData **)data + andType: (id *)type + forName: (OFString *)name + ofItemAtIRI: (OFIRI *)IRI +{ + /* + * Only call into -[extendedAttributeDataForName:ofItemAtIRI:] if it + * has been overridden. This is to be backwards-compatible to + * subclasses that predate the introduction of + * -[getExtendedAttributeData:andType:forName:ofItemAtIRI:]. + * Without this check, this would result in an infinite loop. + */ + SEL selector = @selector(extendedAttributeDataForName:ofItemAtIRI:); + + if (class_getMethodImplementation(object_getClass(self), selector) != + class_getMethodImplementation([OFIRIHandler class], selector)) { + /* + * Use -[performSelector:withObject:withObject:] to avoid + * deprecation warning. This entire thing is purely for + * backwards compatibility. + */ + *data = [self performSelector: selector + withObject: name + withObject: IRI]; + + if (type != NULL) + *type = nil; + + return; + } + OF_UNRECOGNIZED_SELECTOR } - (void)setExtendedAttributeData: (OFData *)data forName: (OFString *)name ofItemAtIRI: (OFIRI *)IRI { + [self setExtendedAttributeData: data + andType: nil + forName: name + ofItemAtIRI: IRI]; +} + +- (void)setExtendedAttributeData: (OFData *)data + andType: (id)type + forName: (OFString *)name + ofItemAtIRI: (OFIRI *)IRI +{ + + if (type == nil) { + /* + * Only call into + * -[setExtendedAttributeData:forName:ofItemAtIRI:] if it has + * been overridden. This is to be backwards-compatible to + * subclasses that predate the introduction of + * -[setExtendedAttributeData:andType:forName:ofItemAtIRI:]. + * Without this check, this would result in an infinite loop. + */ + SEL selector = + @selector(setExtendedAttributeData:forName:ofItemAtIRI:); + + if (class_getMethodImplementation(object_getClass(self), + selector) != + class_getMethodImplementation([OFIRIHandler class], + selector)) { + /* + * Use + * -[performSelector:withObject:withObject:withObject:] + * to avoid deprecation warning. This entire thing is + * purely for backwards compatibility. + */ + [self performSelector: selector + withObject: data + withObject: name + withObject: IRI]; + return; + } + } + OF_UNRECOGNIZED_SELECTOR } - (void)removeExtendedAttributeForName: (OFString *)name ofItemAtIRI: (OFIRI *)IRI { OF_UNRECOGNIZED_SELECTOR } @end Index: tests/OFFileManagerTests.m ================================================================== --- tests/OFFileManagerTests.m +++ tests/OFFileManagerTests.m @@ -400,6 +400,54 @@ [_fileManager extendedAttributeDataForName: @"user.test" ofItemAtPath: testFilePath], OFGetItemAttributesFailedException); } #endif + +#ifdef OF_HAIKU +- (void)testGetExtendedAttributeDataAndTypeForNameOfItemAtPath +{ + OFData *data; + id type; + + [_fileManager getExtendedAttributeData: &data + andType: &type + forName: @"BEOS:TYPE" + ofItemAtPath: @"/boot/system/lib/libbe.so"]; + OTAssertEqualObjects(type, + [OFNumber numberWithUnsignedLong: B_MIME_STRING_TYPE]); + OTAssertEqualObjects(data, + [OFData dataWithItems: "application/x-vnd.Be-elfexecutable" + count: 35]); +} + +- (void)testSetExtendedAttributeDataAndTypeForNameOfItemAtPath +{ + OFString *testFilePath = _testFileIRI.fileSystemRepresentation; + OFData *data, *expectedData = [OFData dataWithItems: "foobar" count: 6]; + id type, expectedType = [OFNumber numberWithUnsignedLong: 1234]; + + [_fileManager setExtendedAttributeData: expectedData + andType: expectedType + forName: @"testattribute" + ofItemAtPath: testFilePath]; + + [_fileManager getExtendedAttributeData: &data + andType: &type + forName: @"testattribute" + ofItemAtPath: testFilePath]; + + OTAssertEqualObjects(data, expectedData); + OTAssertEqualObjects(type, expectedType); + + [_fileManager removeExtendedAttributeForName: @"testattribute" + ofItemAtPath: testFilePath]; + + OTAssertThrowsSpecific( + [_fileManager getExtendedAttributeData: &data + andType: &type + forName: @"testattribute" + ofItemAtPath: testFilePath], + OFGetItemAttributesFailedException); +} +#endif @end