blob: 19e21c08c2e08de2b8170d2dbab3d43fb94e2f17 [file] [log] [blame]
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#import <Foundation/Foundation.h>
#import <sys/fcntl.h>
#include "swift/Runtime/Config.h"
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
static int __NSFileProtectionClassForOptions(NSUInteger options) {
int result;
switch (options & NSDataWritingFileProtectionMask) {
case NSDataWritingFileProtectionComplete: // Class A
result = 1;
break;
case NSDataWritingFileProtectionCompleteUnlessOpen: // Class B
result = 2;
break;
case NSDataWritingFileProtectionCompleteUntilFirstUserAuthentication: // Class C
result = 3;
break;
case NSDataWritingFileProtectionNone: // Class D
result = 4;
break;
default:
result = 0;
break;
}
return result;
}
#endif
static int32_t _NSOpenFileDescriptor(const char *path, NSInteger flags, int protectionClass, NSInteger mode) {
int fd = -1;
if (protectionClass != 0) {
fd = open_dprotected_np(path, flags, protectionClass, 0, mode);
} else {
fd = open(path, flags, mode);
}
return fd;
}
static NSInteger _NSWriteToFileDescriptor(int32_t fd, const void *buffer, NSUInteger length) {
size_t preferredChunkSize = (size_t)length;
size_t numBytesRemaining = (size_t)length;
while (numBytesRemaining > 0UL) {
size_t numBytesRequested = (preferredChunkSize < (1LL << 31)) ? preferredChunkSize : ((1LL << 31) - 1);
if (numBytesRequested > numBytesRemaining) numBytesRequested = numBytesRemaining;
ssize_t numBytesWritten;
do {
numBytesWritten = write(fd, buffer, numBytesRequested);
} while (numBytesWritten < 0L && errno == EINTR);
if (numBytesWritten < 0L) {
return -1;
} else if (numBytesWritten == 0L) {
break;
} else {
numBytesRemaining -= numBytesWritten;
if ((size_t)numBytesWritten < numBytesRequested) break;
buffer = (char *)buffer + numBytesWritten;
}
}
return length - numBytesRemaining;
}
static NSError *_NSErrorWithFilePath(NSInteger code, id pathOrURL) {
NSString *key = [pathOrURL isKindOfClass:[NSURL self]] ? NSURLErrorKey : NSFilePathErrorKey;
return [NSError errorWithDomain:NSCocoaErrorDomain code:code userInfo:[NSDictionary dictionaryWithObjectsAndKeys:pathOrURL, key, nil]];
}
static NSError *_NSErrorWithFilePathAndErrno(NSInteger posixErrno, id pathOrURL, BOOL reading) {
NSInteger code;
if (reading) {
switch (posixErrno) {
case EFBIG: code = NSFileReadTooLargeError; break;
case ENOENT: code = NSFileReadNoSuchFileError; break;
case EPERM: // fallthrough
case EACCES: code = NSFileReadNoPermissionError; break;
case ENAMETOOLONG: code = NSFileReadInvalidFileNameError; break;
default: code = NSFileReadUnknownError; break;
}
} else {
switch (posixErrno) {
case ENOENT: code = NSFileNoSuchFileError; break;
case EPERM: // fallthrough
case EACCES: code = NSFileWriteNoPermissionError; break;
case ENAMETOOLONG: code = NSFileWriteInvalidFileNameError; break;
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
case EDQUOT:
#endif
case ENOSPC: code = NSFileWriteOutOfSpaceError; break;
case EROFS: code = NSFileWriteVolumeReadOnlyError; break;
case EEXIST: code = NSFileWriteFileExistsError; break;
default: code = NSFileWriteUnknownError; break;
}
}
NSString *key = [pathOrURL isKindOfClass:[NSURL self]] ? NSURLErrorKey : NSFilePathErrorKey;
NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:pathOrURL, key, [NSError errorWithDomain:NSPOSIXErrorDomain code:posixErrno userInfo:nil], NSUnderlyingErrorKey, nil];
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:code userInfo:userInfo];
[userInfo release];
return error;
}
SWIFT_RUNTIME_STDLIB_INTERNAL
BOOL __NSDataWriteToURL(NSData * _Nonnull data NS_RELEASES_ARGUMENT, NSURL * _Nonnull url NS_RELEASES_ARGUMENT, NSDataWritingOptions writingOptions, NSError **errorPtr) {
assert((writingOptions & NSDataWritingAtomic) == 0);
NSString *path = url.path;
char cpath[1026];
if (![path getFileSystemRepresentation:cpath maxLength:1024]) {
if (errorPtr) *errorPtr = _NSErrorWithFilePath(NSFileWriteInvalidFileNameError, path);
SWIFT_CC_PLUSONE_GUARD([data release]);
SWIFT_CC_PLUSONE_GUARD([url release]);
return NO;
}
int protectionClass = 0;
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
protectionClass = __NSFileProtectionClassForOptions(writingOptions);
#endif
int flags = O_WRONLY|O_CREAT|O_TRUNC;
if (writingOptions & NSDataWritingWithoutOverwriting) {
flags |= O_EXCL;
}
int32_t fd = _NSOpenFileDescriptor(cpath, flags, protectionClass, 0666);
if (fd < 0) {
if (errorPtr) *errorPtr = _NSErrorWithFilePathAndErrno(errno, path, NO);
SWIFT_CC_PLUSONE_GUARD([url release]);
SWIFT_CC_PLUSONE_GUARD([data release]);
return NO;
}
__block BOOL writingFailed = NO;
__block int32_t saveerr = 0;
NSUInteger dataLength = [data length];
[data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
NSUInteger length = byteRange.length;
BOOL success = NO;
if (length > 0) {
NSInteger writtenLength = _NSWriteToFileDescriptor(fd, bytes, length);
success = writtenLength > 0 && (NSUInteger)writtenLength == length;
} else {
success = YES; // Writing nothing always succeeds.
}
if (!success) {
saveerr = errno;
writingFailed = YES;
*stop = YES;
}
}];
if (dataLength && !writingFailed) {
if (fsync(fd) < 0) {
writingFailed = YES;
saveerr = errno;
}
}
if (writingFailed) {
close(fd);
errno = (saveerr);
if (errorPtr) {
*errorPtr = _NSErrorWithFilePathAndErrno(errno, path, NO);
}
SWIFT_CC_PLUSONE_GUARD([url release]);
SWIFT_CC_PLUSONE_GUARD([data release]);
return NO;
}
close(fd);
SWIFT_CC_PLUSONE_GUARD([url release]);
SWIFT_CC_PLUSONE_GUARD([data release]);
return YES;
}