diff --git a/Headers/Foundation/NSData.h b/Headers/Foundation/NSData.h index 1a563bb83..9feddab66 100644 --- a/Headers/Foundation/NSData.h +++ b/Headers/Foundation/NSData.h @@ -39,6 +39,15 @@ extern "C" { @class NSURL; #endif +enum { + NSDataReadingMappedIfSafe = 1UL << 0, // Suggests using memory mapping for the file if it can be done securely + NSDataReadingUncached = 1UL << 1, // Suggests avoiding file system caching for the read operation +#if OS_API_VERSION(MAC_OS_X_VERSION_10_7,GS_API_LATEST) + NSDataReadingMappedAlways = 1UL << 3, // Strongly requests memory mapping the file; overrides MappedIfSafe if both are set +#endif +}; +typedef NSUInteger NSDataReadingOptions; + #if OS_API_VERSION(MAC_OS_X_VERSION_10_6,GS_API_LATEST) enum { NSDataSearchBackwards = (1UL << 0), @@ -95,9 +104,15 @@ GS_EXPORT_CLASS length: (NSUInteger)bufferSize freeWhenDone: (BOOL)shouldFree; #endif ++ (instancetype)dataWithContentsOfFile: (NSString *)path + options: (NSDataReadingOptions)readOptionsMask + error: (NSError **)errorPtr; + (instancetype) dataWithContentsOfFile: (NSString*)path; + (instancetype) dataWithContentsOfMappedFile: (NSString*)path; #if OS_API_VERSION(GS_API_MACOSX, GS_API_LATEST) ++ (instancetype) dataWithContentsOfURL: (NSURL *)url + options: (NSDataReadingOptions)readOptionsMask + error: (NSError **)errorPtr; + (instancetype) dataWithContentsOfURL: (NSURL*)url; #endif + (instancetype) dataWithData: (NSData*)data; @@ -127,9 +142,15 @@ GS_EXPORT_CLASS freeWhenDone: (BOOL)shouldFree; #endif - (instancetype) initWithContentsOfFile: (NSString*)path; +- (instancetype) initWithContentsOfFile:(NSString *) path + options:(NSDataReadingOptions) readOptionsMask + error:(NSError **) errorPtr; - (instancetype) initWithContentsOfMappedFile: (NSString*)path; #if OS_API_VERSION(GS_API_MACOSX, GS_API_LATEST) - (instancetype) initWithContentsOfURL: (NSURL*)url; +- (instancetype) initWithContentsOfURL: (NSURL *)url + options: (NSDataReadingOptions)readOptionsMask + error: (NSError **)errorPtr; #endif - (instancetype) initWithData: (NSData*)data; diff --git a/Source/NSData.m b/Source/NSData.m index 08c891cf0..0727a410b 100644 --- a/Source/NSData.m +++ b/Source/NSData.m @@ -84,6 +84,10 @@ #import "Foundation/NSRange.h" #import "Foundation/NSURL.h" #import "Foundation/NSValue.h" +#import "Foundation/NSDictionary.h" +#import "Foundation/NSString.h" +#import "Foundation/NSError.h" +#import "Foundation/FoundationErrors.h" #import "GSPrivate.h" #include @@ -227,8 +231,9 @@ return dIndex; } +// NSError must not be nil static BOOL -readContentsOfFile(NSString *path, void **buf, off_t *len, NSZone *zone) +readContentsOfFile(NSString *path, void **buf, off_t *len, NSZone *zone, NSError **error) { NSFileManager *mgr = [NSFileManager defaultManager]; NSDictionary *att; @@ -237,7 +242,7 @@ void *tmp = 0; int c; off_t fileLength; - + #ifdef __ANDROID__ // Android: try using asset manager if path is in main bundle resources AAsset *asset = [NSBundle assetForPath: path withMode: AASSET_MODE_BUFFER]; @@ -248,8 +253,14 @@ tmp = NSZoneMalloc(zone, fileLength); if (tmp == 0) { - NSLog(@"Malloc failed for file (%@) of length %jd - %@", path, - (intmax_t)fileLength, [NSError _last]); + NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + @"Memory allocation failed", NSLocalizedDescriptionKey, + path, @"NSFilePath", + [NSError _last], NSUnderlyingErrorKey, + nil]; + *error = [NSError errorWithDomain: NSCocoaErrorDomain + code: NSFileReadUnknownError + userInfo: userInfo]; AAsset_close(asset); goto failure; } @@ -259,10 +270,14 @@ if (result < 0) { - NSWarnFLog(@"read of file (%@) contents failed - %@", path, - [NSError errorWithDomain: NSPOSIXErrorDomain - code: result - userInfo: nil]); + NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + @"Read from Android asset failed", NSLocalizedDescriptionKey, + path, @"NSFilePath", + [NSError errorWithDomain: NSPOSIXErrorDomain code: result userInfo: nil], NSUnderlyingErrorKey, + nil]; + *error = [NSError errorWithDomain: NSCocoaErrorDomain + code: NSFileReadUnknownError + userInfo: userInfo]; goto failure; } @@ -275,19 +290,49 @@ thePath = [path fileSystemRepresentation]; if (thePath == 0) { - NSWarnFLog(@"Open (%@) attempt failed - bad path", path); + NSDictionary *userInfo; + + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + @"The path is invalid", NSLocalizedDescriptionKey, + path, @"NSFilePath", + nil]; + *error = [NSError errorWithDomain: NSCocoaErrorDomain + code: NSFileReadInvalidFileNameError + userInfo: userInfo]; + return NO; } att = [mgr fileAttributesAtPath: path traverseLink: YES]; if (nil == att) { - return NO; // No such file ... fail quietly + NSDictionary *userInfo; + NSString *description; + + description = [NSString stringWithFormat: @"The file '%@' couldn't be opened because there is no such file.", path]; + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + description, NSLocalizedDescriptionKey, + path, @"NSFilePath", + nil]; + *error = [NSError errorWithDomain: NSCocoaErrorDomain + code: NSFileReadNoSuchFileError + userInfo: userInfo]; + + return NO; } if ([att fileType] != NSFileTypeRegular) { - NSWarnFLog(@"Open (%@) attempt failed - not a regular file", path); + NSDictionary *userInfo; + + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + @"Open attempt failed - not a regular file", NSLocalizedDescriptionKey, + path, @"NSFilePath", + nil]; + *error = [NSError errorWithDomain: NSCocoaErrorDomain + code: NSFileReadUnknownError + userInfo: userInfo]; + return NO; } @@ -297,9 +342,19 @@ theFile = fopen(thePath, "rb"); #endif - if (theFile == 0) /* We failed to open the file. */ + if (theFile == 0) { - NSDebugFLog(@"Open (%@) attempt failed - %@", path, [NSError _last]); + NSDictionary *userInfo; + + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + @"Open attempt failed", NSLocalizedDescriptionKey, + path, @"NSFilePath", + [NSError _last], NSUnderlyingErrorKey, + nil]; + *error = [NSError errorWithDomain: NSCocoaErrorDomain + code: NSFileReadUnknownError + userInfo: userInfo]; + goto failure; } @@ -309,11 +364,18 @@ c = fseeko(theFile, 0, SEEK_END); if (c != 0) { - NSWarnFLog(@"Seek to end of file (%@) failed - %@", path, - [NSError _last]); + NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + @"Seek to end of file failed", NSLocalizedDescriptionKey, + path, @"NSFilePath", + [NSError _last], NSUnderlyingErrorKey, + nil]; + *error = [NSError errorWithDomain: NSCocoaErrorDomain + code: NSFileReadUnknownError + userInfo: userInfo]; goto failure; } + /* * Determine the length of the file (having seeked to the end of the * file) by calling ftello(). @@ -321,7 +383,14 @@ fileLength = ftello(theFile); if (fileLength == (off_t)-1) { - NSWarnFLog(@"Ftell on %@ failed - %@", path, [NSError _last]); + NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + @"ftello failed", NSLocalizedDescriptionKey, + path, @"NSFilePath", + [NSError _last], NSUnderlyingErrorKey, + nil]; + *error = [NSError errorWithDomain: NSCocoaErrorDomain + code: NSFileReadUnknownError + userInfo: userInfo]; goto failure; } if (fileLength >= 2147483647) @@ -336,8 +405,14 @@ c = fseeko(theFile, 0, SEEK_SET); if (c != 0) { - NSWarnFLog(@"Fseek to start of file (%@) failed - %@", path, - [NSError _last]); + NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + @"Seek to start of file failed", NSLocalizedDescriptionKey, + path, @"NSFilePath", + [NSError _last], NSUnderlyingErrorKey, + nil]; + *error = [NSError errorWithDomain: NSCocoaErrorDomain + code: NSFileReadUnknownError + userInfo: userInfo]; goto failure; } @@ -346,11 +421,6 @@ { unsigned char buf[BUFSIZ]; - /* - * Special case ... a file of length zero may be a named pipe or some - * file in the /proc filesystem, which will return us data if we read - * from it ... so we try reading as much as we can. - */ while ((c = fread(buf, 1, BUFSIZ, theFile)) != 0) { if (tmp == 0) @@ -363,8 +433,14 @@ } if (tmp == 0) { - NSLog(@"Malloc failed for file (%@) of length %jd - %@", path, - (intmax_t)fileLength + c, [NSError _last]); + NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + @"Memory allocation failed", NSLocalizedDescriptionKey, + path, @"NSFilePath", + [NSError _last], NSUnderlyingErrorKey, + nil]; + *error = [NSError errorWithDomain: NSCocoaErrorDomain + code: NSFileReadUnknownError + userInfo: userInfo]; goto failure; } memcpy(tmp + fileLength, buf, c); @@ -378,8 +454,14 @@ tmp = NSZoneMalloc(zone, fileLength); if (tmp == 0) { - NSLog(@"Malloc failed for file (%@) of length %jd - %@", path, - (intmax_t)fileLength, [NSError _last]); + NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + @"Memory allocation failed", NSLocalizedDescriptionKey, + path, @"NSFilePath", + [NSError _last], NSUnderlyingErrorKey, + nil]; + *error = [NSError errorWithDomain: NSCocoaErrorDomain + code: NSFileReadUnknownError + userInfo: userInfo]; goto failure; } @@ -396,8 +478,14 @@ } if (ferror(theFile)) { - NSWarnFLog(@"read of file (%@) contents failed - %@", path, - [NSError _last]); + NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + @"File read failed", NSLocalizedDescriptionKey, + path, @"NSFilePath", + [NSError _last], NSUnderlyingErrorKey, + nil]; + *error = [NSError errorWithDomain: NSCocoaErrorDomain + code: NSFileReadCorruptFileError + userInfo: userInfo]; goto failure; } @@ -420,6 +508,7 @@ return NO; } + /* * NB, The start of the NSMutableDataMalloc instance variables must be * identical to that of NSDataMalloc in order to inherit its methods. @@ -615,6 +704,17 @@ + (id) dataWithContentsOfFile: (NSString*)path return AUTORELEASE(d); } ++ (id) dataWithContentsOfFile: (NSString*)path + options: (NSDataReadingOptions)readOptionsMask + error: (NSError **)errorPtr +{ + NSData *d; + + d = [dataMalloc allocWithZone: NSDefaultMallocZone()]; + d = [d initWithContentsOfFile: path options: readOptionsMask error: errorPtr]; + return AUTORELEASE(d); +} + /** * Returns a data object encapsulating the contents of the specified * file mapped directly into memory. @@ -654,6 +754,25 @@ + (id) dataWithContentsOfURL: (NSURL*)url return d; } ++ (id) dataWithContentsOfURL: (NSURL*)url + options: (NSDataReadingOptions)readOptionsMask + error: (NSError **)errorPtr +{ + NSData *d; + + if ([url isFileURL]) + { + d = [dataMalloc allocWithZone: NSDefaultMallocZone()]; + d = AUTORELEASE([d initWithContentsOfFile: [url path] options: readOptionsMask error: errorPtr]); + } + else + { + BOOL useCache = (readOptionsMask & NSDataReadingUncached) != NSDataReadingUncached; + d = [url resourceDataUsingCache: useCache]; + } + return d; +} + /** * Returns an autoreleased instance initialised by copying the contents of data. */ @@ -918,19 +1037,21 @@ - (instancetype) initWithBytesNoCopy: (void*)bytes return nil; } -/** - * Initialises the receiver with the contents of the specified file.
- * Returns the resulting object.
- * Returns nil if the file does not exist or can not be read for some reason. - */ -- (id) initWithContentsOfFile: (NSString*)path +- (instancetype) initWithContentsOfFile:(NSString *) path + options:(NSDataReadingOptions) readOptionsMask + error:(NSError **) errorPtr { - void *fileBytes; - off_t fileLength; + void *fileBytes; + off_t fileLength; + NSError *error = nil; - if (readContentsOfFile(path, &fileBytes, &fileLength, [self zone]) == NO) + if (readContentsOfFile(path, &fileBytes, &fileLength, [self zone], &error) == NO) { DESTROY(self); + if (errorPtr) + { + *errorPtr = error; + } return nil; } self = [self initWithBytesNoCopy: fileBytes @@ -939,6 +1060,16 @@ - (id) initWithContentsOfFile: (NSString*)path return self; } +/** + * Initialises the receiver with the contents of the specified file.
+ * Returns the resulting object.
+ * Returns nil if the file does not exist or can not be read for some reason. + */ +- (id) initWithContentsOfFile: (NSString*)path +{ + return [self initWithContentsOfFile: path options: 0 error: nil]; +} + /** * Initialize with data pointing to contents of file at path. Bytes are * only "swapped in" as needed. File should not be moved or deleted for @@ -961,14 +1092,31 @@ - (id) initWithContentsOfMappedFile: (NSString *)path * retrieved immediately in a blocking manner. */ - (id) initWithContentsOfURL: (NSURL*)url +{ + return [self initWithContentsOfURL: url options: 0 error: nil]; +} + +/** + * Initialize with data pointing to contents of URL, which will be + * retrieved immediately in a blocking manner. + * The readOptionsMark is used for additional directions, such as + * disabling of the url content cache. + */ +- (id) initWithContentsOfURL: (NSURL *)url + options: (NSDataReadingOptions)readOptionsMask + error: (NSError **)errorPtr { if ([url isFileURL]) { - return [self initWithContentsOfFile: [url path]]; + return [self initWithContentsOfFile: [url path] options: readOptionsMask error: errorPtr]; } else { - NSData *data = [url resourceDataUsingCache: YES]; + NSData *data; + BOOL useCache; + + useCache = (NSDataReadingUncached & readOptionsMask) != NSDataReadingUncached; + data = [url resourceDataUsingCache: useCache]; return [self initWithData: data]; } } @@ -2364,6 +2512,17 @@ + (id) dataWithContentsOfFile: (NSString*)path return AUTORELEASE(d); } ++ (id) dataWithContentsOfFile: (NSString*)path + options: (NSDataReadingOptions)readOptionsMask + error: (NSError **)errorPtr +{ + NSMutableData *d; + + d = [mutableDataMalloc allocWithZone: NSDefaultMallocZone()]; + d = [d initWithContentsOfFile: path options: readOptionsMask error: errorPtr]; + return AUTORELEASE(d); +} + + (id) dataWithContentsOfMappedFile: (NSString*)path { NSMutableData *d; @@ -2374,6 +2533,13 @@ + (id) dataWithContentsOfMappedFile: (NSString*)path } + (id) dataWithContentsOfURL: (NSURL*)url +{ + return [NSMutableData dataWithContentsOfURL: url options:0 error: nil]; +} + ++ (id) dataWithContentsOfURL: (NSURL*)url + options: (NSDataReadingOptions)readOptionsMask + error: (NSError **)errorPtr { NSMutableData *d; @@ -2381,11 +2547,12 @@ + (id) dataWithContentsOfURL: (NSURL*)url if ([url isFileURL]) { - d = [d initWithContentsOfFile: [url path]]; + d = [d initWithContentsOfFile: [url path] options: readOptionsMask error: errorPtr]; } - else + else { - NSData *data = [url resourceDataUsingCache: YES]; + BOOL useCache = (readOptionsMask & NSDataReadingUncached) != NSDataReadingUncached; + NSData *data = [url resourceDataUsingCache: useCache]; d = [d initWithBytes: [data bytes] length: [data length]]; } return AUTORELEASE(d); diff --git a/Tests/base/NSData/errors.m b/Tests/base/NSData/errors.m new file mode 100644 index 000000000..398638c22 --- /dev/null +++ b/Tests/base/NSData/errors.m @@ -0,0 +1,30 @@ +#import "Testing.h" +#import "ObjectTesting.h" +#import + +int main() +{ + NSError *error; + NSString *path; + NSData *data; + + START_SET("NSData file/url loading") + + // Try loading from a nonexistent path to trigger error + path = @"/tmp/nonexistent_file.txt"; + data = [NSData dataWithContentsOfFile: path options:0 error:&error]; + PASS(data == nil && error != nil, "+dataWithContentsOfFile:options:error: sets error on failure"); + + data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:path] + options:0 + error:&error]; + PASS(data == nil && error != nil, "+dataWithContentsOfURL:options:error: sets error on failure"); + + // Try loading with invalid utf-8 path + data = [NSData dataWithContentsOfFile: @"\xC3\x28" options:0 error:&error]; + PASS(data == nil && error != nil, "+dataWithContentsOfURL:options:error: sets error when path is invalid"); + + END_SET("NSData file/url loading") + + return 0; +}