Add two previously unimplemented methods.

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@28252 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Richard Frith-MacDonald 2009-04-27 08:16:06 +00:00
parent d79eb3cab4
commit 9cc306b2e1
3 changed files with 355 additions and 316 deletions

View file

@ -1,3 +1,10 @@
2009-04-27 Richard Frith-Macdonald <rfm@gnu.org>
* 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 <rfm@gnu.org>
* Source/GSFTPURLHandle.m: Implement full support for multiline

View file

@ -102,12 +102,23 @@ enum {
- (BOOL) isEqualToData: (NSData*)other;
- (NSUInteger) length;
// Storing Data
/**
* <p>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.
* </p>
* <p>On success returns YES, on failure returns NO.
* </p>
*/
- (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 */
/**
* <p>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.
* </p>
* <p>On success returns YES, on failure returns NO.
* </p>
*/
- (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;

View file

@ -839,334 +839,27 @@ failure:
return 0;
}
/**
* <p>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.
* </p>
* <p>On success returns YES, on failure returns NO.
* </p>
*/
- (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