From fd2861f3ae01dccad574008e33363b99d1918838 Mon Sep 17 00:00:00 2001 From: rfm Date: Mon, 27 Apr 2009 08:16:06 +0000 Subject: [PATCH] Add two previously unimplemented methods. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@28252 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 7 + Headers/Foundation/NSData.h | 31 +- Source/NSData.m | 633 ++++++++++++++++++------------------ 3 files changed, 355 insertions(+), 316 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5ab27b0a9..c5ba8d54c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2009-04-27 Richard Frith-Macdonald + + * Headers/Foundation/NSData.h: + * Source/NSData.m: + Implement new MacOS-X methods for writing data (no support for + returning NSError objects yet). + 2009-04-26 Richard Frith-Macdonald * Source/GSFTPURLHandle.m: Implement full support for multiline diff --git a/Headers/Foundation/NSData.h b/Headers/Foundation/NSData.h index e1bf611a1..f9359640e 100644 --- a/Headers/Foundation/NSData.h +++ b/Headers/Foundation/NSData.h @@ -102,12 +102,23 @@ enum { - (BOOL) isEqualToData: (NSData*)other; - (NSUInteger) length; -// Storing Data - +/** + *

Writes a copy of the data encapsulated by the receiver to a file + * at path. If the useAuxiliaryFile flag is YES, this writes to a + * temporary file and then renames that to the file at path, thus + * ensuring that path exists and does not contain partially written + * data at any point. + *

+ *

On success returns YES, on failure returns NO. + *

+ */ - (BOOL) writeToFile: (NSString*)path atomically: (BOOL)useAuxiliaryFile; #if OS_API_VERSION(GS_API_MACOSX, GS_API_LATEST) +/** + * Writes a copy of the contents of the receiver to the specified URL. + */ - (BOOL) writeToURL: (NSURL*)anURL atomically: (BOOL)flag; #endif @@ -131,11 +142,23 @@ enum { atIndex: (unsigned int)index; #if OS_API_VERSION(100400,GS_API_LATEST) -/** Not implemented */ +/** + *

Writes a copy of the data encapsulated by the receiver to a file + * at path. If the NSAtomicWrite option is set, this writes to a + * temporary file and then renames that to the file at path, thus + * ensuring that path exists and does not contain partially written + * data at any point. + *

+ *

On success returns YES, on failure returns NO. + *

+ */ - (BOOL) writeToFile: (NSString *)path options: (NSUInteger)writeOptionsMask error: (NSError **)errorPtr; -/** Not implemented */ + +/** + * Writes a copy of the contents of the receiver to the specified URL. + */ - (BOOL) writeToURL: (NSURL *)url options: (NSUInteger)writeOptionsMask error: (NSError **)errorPtr; diff --git a/Source/NSData.m b/Source/NSData.m index a0106683f..f635043b5 100644 --- a/Source/NSData.m +++ b/Source/NSData.m @@ -839,334 +839,27 @@ failure: return 0; } -/** - *

Writes a copy of the data encapsulated by the receiver to a file - * at path. If the useAuxiliaryFile flag is YES, this writes to a - * temporary file and then renames that to the file at path, thus - * ensuring that path exists and does not contain partially written - * data at any point. - *

- *

On success returns YES, on failure returns NO. - *

- */ - (BOOL) writeToFile: (NSString*)path atomically: (BOOL)useAuxiliaryFile { -#if defined(__MINGW32__) - NSUInteger length = [path length]; - unichar wthePath[length + 100]; - unichar wtheRealPath[length + 100]; -#else - char thePath[BUFSIZ*2+8]; - char theRealPath[BUFSIZ*2]; -#endif - int c; - FILE *theFile; - BOOL error_BadPath = YES; - -#if defined(__MINGW32__) - [path getCharacters: wtheRealPath]; - wtheRealPath[length] = L'\0'; - error_BadPath = (length <= 0); -#else - if ([path canBeConvertedToEncoding: [NSString defaultCStringEncoding]]) - { - const char *local_c_path = [path cString]; - - if (local_c_path != 0 && strlen(local_c_path) < (BUFSIZ*2)) - { - strcpy(theRealPath,local_c_path); - error_BadPath = NO; - } - } -#endif - if (error_BadPath) - { - NSWarnMLog(@"Open (%@) attempt failed - bad path",path); - return NO; - } - -#ifdef HAVE_MKSTEMP if (useAuxiliaryFile) { - int desc; - int mask; - - strcpy(thePath, theRealPath); - strcat(thePath, "XXXXXX"); - if ((desc = mkstemp(thePath)) < 0) - { - NSWarnMLog(@"mkstemp (%s) failed - %@", thePath, [NSError _last]); - goto failure; - } - mask = umask(0); - umask(mask); - fchmod(desc, 0644 & ~mask); - if ((theFile = fdopen(desc, "w")) == 0) - { - close(desc); - } + return [self writeToFile: path options: NSAtomicWrite error: 0]; } else { - strcpy(thePath, theRealPath); - theFile = fopen(thePath, "wb"); + return [self writeToFile: path options: 0 error: 0]; } -#else - if (useAuxiliaryFile) - { - /* Use the path name of the destination file as a prefix for the - * mktemp() call so that we can be sure that both files are on - * the same filesystem and the subsequent rename() will work. */ -#if defined(__MINGW32__) - wcscpy(wthePath, wtheRealPath); - wcscat(wthePath, L"XXXXXX"); - if (_wmktemp(wthePath) == 0) - { - NSWarnMLog(@"mktemp (%@) failed - %@", - [NSString stringWithCharacters: wthePath length: wcslen(wthePath)], - [NSError _last]); - goto failure; - } -#else - strcpy(thePath, theRealPath); - strcat(thePath, "XXXXXX"); - if (mktemp(thePath) == 0) - { - NSWarnMLog(@"mktemp (%s) failed - %@", thePath, [NSError _last]); - goto failure; - } -#endif - } - else - { -#if defined(__MINGW32__) - wcscpy(wthePath,wtheRealPath); -#else - strcpy(thePath, theRealPath); -#endif - } - - /* Open the file (whether temp or real) for writing. */ -#if defined(__MINGW32__) - theFile = _wfopen(wthePath, L"wb"); -#else - theFile = fopen(thePath, "wb"); -#endif -#endif - - if (theFile == 0) - { - /* Something went wrong; we weren't - * even able to open the file. */ -#if defined(__MINGW32__) - NSWarnMLog(@"Open (%@) failed - %@", - [NSString stringWithCharacters: wthePath length: wcslen(wthePath)], - [NSError _last]); -#else - NSWarnMLog(@"Open (%s) failed - %@", thePath, [NSError _last]); -#endif - goto failure; - } - - /* Now we try and write the NSData's bytes to the file. Here `c' is - * the number of bytes which were successfully written to the file - * in the fwrite() call. */ - c = fwrite([self bytes], sizeof(char), [self length], theFile); - - if (c < (int)[self length]) /* We failed to write everything for - * some reason. */ - { -#if defined(__MINGW32__) - NSWarnMLog(@"Fwrite (%@) failed - %@", - [NSString stringWithCharacters: wthePath length: wcslen(wthePath)], - [NSError _last]); -#else - NSWarnMLog(@"Fwrite (%s) failed - %@", thePath, [NSError _last]); -#endif - goto failure; - } - - /* We're done, so close everything up. */ - c = fclose(theFile); - - if (c != 0) /* I can't imagine what went wrong - * closing the file, but we got here, - * so we need to deal with it. */ - { -#if defined(__MINGW32__) - NSWarnMLog(@"Fclose (%@) failed - %@", - [NSString stringWithCharacters: wthePath length: wcslen(wthePath)], - [NSError _last]); -#else - NSWarnMLog(@"Fclose (%s) failed - %@", thePath, [NSError _last]); -#endif - goto failure; - } - - /* If we used a temporary file, we still need to rename() it be the - * real file. Also, we need to try to retain the file attributes of - * the original file we are overwriting (if we are) */ - if (useAuxiliaryFile) - { - NSFileManager *mgr = [NSFileManager defaultManager]; - NSMutableDictionary *att = nil; -#if defined(__MINGW32__) - NSUInteger perm; -#endif - - if ([mgr fileExistsAtPath: path]) - { - att = [[mgr fileAttributesAtPath: path - traverseLink: YES] mutableCopy]; - IF_NO_GC(TEST_AUTORELEASE(att)); - } - -#if defined(__MINGW32__) - /* To replace the existing file on windows, it must be writable. - */ - perm = [att filePosixPermissions]; - if (perm != NSNotFound && (perm & 0200) == 0) - { - [mgr changeFileAttributes: [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithUnsignedInt: 0777], NSFilePosixPermissions, - nil] atPath: path]; - } - /* - * The windoze implementation of the POSIX rename() function is buggy - * and doesn't work if the destination file already exists ... so we - * try to use a windoze specific function instead. - */ -#if 0 - if (ReplaceFile(theRealPath, thePath, 0, - REPLACEFILE_IGNORE_MERGE_ERRORS, 0, 0) != 0) - { - c = 0; - } - else - { - c = -1; - } -#else - if (MoveFileExW(wthePath, wtheRealPath, MOVEFILE_REPLACE_EXISTING) != 0) - { - c = 0; - } - /* Windows 9x does not support MoveFileEx */ - else if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) - { - unichar secondaryFile[length + 100]; - - wcscpy(secondaryFile, wthePath); - wcscat(secondaryFile, L"-delete"); - // Delete the intermediate name just in case - DeleteFileW(secondaryFile); - // Move the existing file to the temp name - if (MoveFileW(wtheRealPath, secondaryFile) != 0) - { - if (MoveFileW(wthePath, wtheRealPath) != 0) - { - c = 0; - // Delete the old file if possible - DeleteFileW(secondaryFile); - } - else - { - c = -1; // failure, restore the old file if possible - MoveFileW(secondaryFile, wtheRealPath); - } - } - else - { - c = -1; // failure - } - } - else - { - c = -1; - } -#endif -#else - c = rename(thePath, theRealPath); -#endif - if (c != 0) /* Many things could go wrong, I guess. */ - { -#if defined(__MINGW32__) - NSWarnMLog(@"Rename ('%@' to '%@') failed - %@", - [NSString stringWithCharacters: wthePath - length: wcslen(wthePath)], - [NSString stringWithCharacters: wtheRealPath - length: wcslen(wtheRealPath)], - [NSError _last]); -#else - NSWarnMLog(@"Rename ('%s' to '%s') failed - %@", - thePath, theRealPath, [NSError _last]); -#endif - goto failure; - } - - if (att != nil) - { - /* - * We have created a new file - so we attempt to make it's - * attributes match that of the original. - */ - [att removeObjectForKey: NSFileSize]; - [att removeObjectForKey: NSFileModificationDate]; - [att removeObjectForKey: NSFileReferenceCount]; - [att removeObjectForKey: NSFileSystemNumber]; - [att removeObjectForKey: NSFileSystemFileNumber]; - [att removeObjectForKey: NSFileDeviceIdentifier]; - [att removeObjectForKey: NSFileType]; - if ([mgr changeFileAttributes: att atPath: path] == NO) - { - NSWarnMLog(@"Unable to correctly set all attributes for '%@'", - path); - } - } -#ifndef __MINGW32__ - else if (geteuid() == 0 && [@"root" isEqualToString: NSUserName()] == NO) - { - att = [NSDictionary dictionaryWithObjectsAndKeys: - NSFileOwnerAccountName, NSUserName(), nil]; - if ([mgr changeFileAttributes: att atPath: path] == NO) - { - NSWarnMLog(@"Unable to correctly set ownership for '%@'", path); - } - } -#endif - } - - /* success: */ - return YES; - - /* Just in case the failure action needs to be changed. */ -failure: - /* - * Attempt to tidy up by removing temporary file on failure. - */ - if (useAuxiliaryFile) - { -#if defined(__MINGW32__) - _wunlink(wthePath); -#else - unlink(thePath); -#endif - } - return NO; } -/** - * Writes a copy of the contents of the receiver to the specified URL. - */ - (BOOL) writeToURL: (NSURL*)anURL atomically: (BOOL)flag { - if ([anURL isFileURL] == YES) + if (flag) { - return [self writeToFile: [anURL path] atomically: flag]; + return [self writeToURL: anURL options: NSAtomicWrite error: 0]; } else { - return [anURL setResourceData: self]; + return [self writeToURL: anURL options: 0 error: 0]; } } @@ -1600,6 +1293,312 @@ failure: options: (NSUInteger)writeOptionsMask error: (NSError **)errorPtr { +#if defined(__MINGW32__) + NSUInteger length = [path length]; + unichar wthePath[length + 100]; + unichar wtheRealPath[length + 100]; +#else + char thePath[BUFSIZ*2+8]; + char theRealPath[BUFSIZ*2]; +#endif + int c; + FILE *theFile; + BOOL useAuxiliaryFile = NO; + BOOL error_BadPath = YES; + + if (writeOptionsMask & NSAtomicWrite) + { + useAuxiliaryFile = YES; + } +#if defined(__MINGW32__) + [path getCharacters: wtheRealPath]; + wtheRealPath[length] = L'\0'; + error_BadPath = (length <= 0); +#else + if ([path canBeConvertedToEncoding: [NSString defaultCStringEncoding]]) + { + const char *local_c_path = [path cString]; + + if (local_c_path != 0 && strlen(local_c_path) < (BUFSIZ*2)) + { + strcpy(theRealPath,local_c_path); + error_BadPath = NO; + } + } +#endif + if (error_BadPath) + { + NSWarnMLog(@"Open (%@) attempt failed - bad path",path); + return NO; + } + +#ifdef HAVE_MKSTEMP + if (useAuxiliaryFile) + { + int desc; + int mask; + + strcpy(thePath, theRealPath); + strcat(thePath, "XXXXXX"); + if ((desc = mkstemp(thePath)) < 0) + { + NSWarnMLog(@"mkstemp (%s) failed - %@", thePath, [NSError _last]); + goto failure; + } + mask = umask(0); + umask(mask); + fchmod(desc, 0644 & ~mask); + if ((theFile = fdopen(desc, "w")) == 0) + { + close(desc); + } + } + else + { + strcpy(thePath, theRealPath); + theFile = fopen(thePath, "wb"); + } +#else + if (useAuxiliaryFile) + { + /* Use the path name of the destination file as a prefix for the + * mktemp() call so that we can be sure that both files are on + * the same filesystem and the subsequent rename() will work. */ +#if defined(__MINGW32__) + wcscpy(wthePath, wtheRealPath); + wcscat(wthePath, L"XXXXXX"); + if (_wmktemp(wthePath) == 0) + { + NSWarnMLog(@"mktemp (%@) failed - %@", + [NSString stringWithCharacters: wthePath length: wcslen(wthePath)], + [NSError _last]); + goto failure; + } +#else + strcpy(thePath, theRealPath); + strcat(thePath, "XXXXXX"); + if (mktemp(thePath) == 0) + { + NSWarnMLog(@"mktemp (%s) failed - %@", thePath, [NSError _last]); + goto failure; + } +#endif + } + else + { +#if defined(__MINGW32__) + wcscpy(wthePath,wtheRealPath); +#else + strcpy(thePath, theRealPath); +#endif + } + + /* Open the file (whether temp or real) for writing. */ +#if defined(__MINGW32__) + theFile = _wfopen(wthePath, L"wb"); +#else + theFile = fopen(thePath, "wb"); +#endif +#endif + + if (theFile == 0) + { + /* Something went wrong; we weren't + * even able to open the file. */ +#if defined(__MINGW32__) + NSWarnMLog(@"Open (%@) failed - %@", + [NSString stringWithCharacters: wthePath length: wcslen(wthePath)], + [NSError _last]); +#else + NSWarnMLog(@"Open (%s) failed - %@", thePath, [NSError _last]); +#endif + goto failure; + } + + /* Now we try and write the NSData's bytes to the file. Here `c' is + * the number of bytes which were successfully written to the file + * in the fwrite() call. */ + c = fwrite([self bytes], sizeof(char), [self length], theFile); + + if (c < (int)[self length]) /* We failed to write everything for + * some reason. */ + { +#if defined(__MINGW32__) + NSWarnMLog(@"Fwrite (%@) failed - %@", + [NSString stringWithCharacters: wthePath length: wcslen(wthePath)], + [NSError _last]); +#else + NSWarnMLog(@"Fwrite (%s) failed - %@", thePath, [NSError _last]); +#endif + goto failure; + } + + /* We're done, so close everything up. */ + c = fclose(theFile); + + if (c != 0) /* I can't imagine what went wrong + * closing the file, but we got here, + * so we need to deal with it. */ + { +#if defined(__MINGW32__) + NSWarnMLog(@"Fclose (%@) failed - %@", + [NSString stringWithCharacters: wthePath length: wcslen(wthePath)], + [NSError _last]); +#else + NSWarnMLog(@"Fclose (%s) failed - %@", thePath, [NSError _last]); +#endif + goto failure; + } + + /* If we used a temporary file, we still need to rename() it be the + * real file. Also, we need to try to retain the file attributes of + * the original file we are overwriting (if we are) */ + if (useAuxiliaryFile) + { + NSFileManager *mgr = [NSFileManager defaultManager]; + NSMutableDictionary *att = nil; +#if defined(__MINGW32__) + NSUInteger perm; +#endif + + if ([mgr fileExistsAtPath: path]) + { + att = [[mgr fileAttributesAtPath: path + traverseLink: YES] mutableCopy]; + IF_NO_GC(TEST_AUTORELEASE(att)); + } + +#if defined(__MINGW32__) + /* To replace the existing file on windows, it must be writable. + */ + perm = [att filePosixPermissions]; + if (perm != NSNotFound && (perm & 0200) == 0) + { + [mgr changeFileAttributes: [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithUnsignedInt: 0777], NSFilePosixPermissions, + nil] atPath: path]; + } + /* + * The windoze implementation of the POSIX rename() function is buggy + * and doesn't work if the destination file already exists ... so we + * try to use a windoze specific function instead. + */ +#if 0 + if (ReplaceFile(theRealPath, thePath, 0, + REPLACEFILE_IGNORE_MERGE_ERRORS, 0, 0) != 0) + { + c = 0; + } + else + { + c = -1; + } +#else + if (MoveFileExW(wthePath, wtheRealPath, MOVEFILE_REPLACE_EXISTING) != 0) + { + c = 0; + } + /* Windows 9x does not support MoveFileEx */ + else if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) + { + unichar secondaryFile[length + 100]; + + wcscpy(secondaryFile, wthePath); + wcscat(secondaryFile, L"-delete"); + // Delete the intermediate name just in case + DeleteFileW(secondaryFile); + // Move the existing file to the temp name + if (MoveFileW(wtheRealPath, secondaryFile) != 0) + { + if (MoveFileW(wthePath, wtheRealPath) != 0) + { + c = 0; + // Delete the old file if possible + DeleteFileW(secondaryFile); + } + else + { + c = -1; // failure, restore the old file if possible + MoveFileW(secondaryFile, wtheRealPath); + } + } + else + { + c = -1; // failure + } + } + else + { + c = -1; + } +#endif +#else + c = rename(thePath, theRealPath); +#endif + if (c != 0) /* Many things could go wrong, I guess. */ + { +#if defined(__MINGW32__) + NSWarnMLog(@"Rename ('%@' to '%@') failed - %@", + [NSString stringWithCharacters: wthePath + length: wcslen(wthePath)], + [NSString stringWithCharacters: wtheRealPath + length: wcslen(wtheRealPath)], + [NSError _last]); +#else + NSWarnMLog(@"Rename ('%s' to '%s') failed - %@", + thePath, theRealPath, [NSError _last]); +#endif + goto failure; + } + + if (att != nil) + { + /* + * We have created a new file - so we attempt to make it's + * attributes match that of the original. + */ + [att removeObjectForKey: NSFileSize]; + [att removeObjectForKey: NSFileModificationDate]; + [att removeObjectForKey: NSFileReferenceCount]; + [att removeObjectForKey: NSFileSystemNumber]; + [att removeObjectForKey: NSFileSystemFileNumber]; + [att removeObjectForKey: NSFileDeviceIdentifier]; + [att removeObjectForKey: NSFileType]; + if ([mgr changeFileAttributes: att atPath: path] == NO) + { + NSWarnMLog(@"Unable to correctly set all attributes for '%@'", + path); + } + } +#ifndef __MINGW32__ + else if (geteuid() == 0 && [@"root" isEqualToString: NSUserName()] == NO) + { + att = [NSDictionary dictionaryWithObjectsAndKeys: + NSFileOwnerAccountName, NSUserName(), nil]; + if ([mgr changeFileAttributes: att atPath: path] == NO) + { + NSWarnMLog(@"Unable to correctly set ownership for '%@'", path); + } + } +#endif + } + + /* success: */ + return YES; + + /* Just in case the failure action needs to be changed. */ +failure: + /* + * Attempt to tidy up by removing temporary file on failure. + */ + if (useAuxiliaryFile) + { +#if defined(__MINGW32__) + _wunlink(wthePath); +#else + unlink(thePath); +#endif + } return NO; } @@ -1607,6 +1606,16 @@ failure: options: (NSUInteger)writeOptionsMask error: (NSError **)errorPtr { + if ([url isFileURL] == YES) + { + return [self writeToFile: [url path] + options: writeOptionsMask + error: errorPtr]; + } + else + { + return [url setResourceData: self]; + } return NO; } @end