Skip to content

NSData: Refactor write(toFile:options) using FileHandle methods. #1876

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 32 additions & 32 deletions Foundation/FileHandle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import CoreFoundation
#if canImport(Darwin)
import Darwin
fileprivate let _read = Darwin.read(_:_:_:)
fileprivate let _write = Darwin.write(_:_:_:)
fileprivate let _close = Darwin.close(_:)
#elseif canImport(Glibc)
import Glibc
fileprivate let _read = Glibc.read(_:_:_:)
fileprivate let _write = Glibc.write(_:_:_:)
fileprivate let _close = Glibc.close(_:)
#endif

Expand Down Expand Up @@ -217,6 +219,34 @@ open class FileHandle : NSObject, NSSecureCoding {
#endif
}

internal func _writeBytes(buf: UnsafeRawPointer, length: Int) throws {
#if os(Windows)
var bytesRemaining = length
while bytesRemaining > 0 {
var bytesWritten: DWORD = 0
if WriteFile(handle, buf.advanced(by: length - bytesRemaining), DWORD(bytesRemaining), &bytesWritten, nil) == FALSE {
throw _NSErrorWithErrno(Int32(GetLastError()), reading: false, path: nil)
}
if bytesWritten == 0 {
throw _NSErrorWithErrno(Int32(GetLastError()), reading: false, path: nil)
}
bytesRemaining -= Int(bytesWritten)
}
#else
var bytesRemaining = length
while bytesRemaining > 0 {
var bytesWritten = 0
repeat {
bytesWritten = _write(_fd, buf.advanced(by: length - bytesRemaining), bytesRemaining)
} while (bytesWritten < 0 && errno == EINTR)
if bytesWritten <= 0 {
throw _NSErrorWithErrno(errno, reading: false, path: nil)
}
bytesRemaining -= bytesWritten
}
#endif
}

#if os(Windows)
public init(handle: HANDLE, closeOnDealloc closeopt: Bool) {
_handle = handle
Expand Down Expand Up @@ -314,13 +344,7 @@ open class FileHandle : NSObject, NSSecureCoding {

for region in data.regions {
try region.withUnsafeBytes { (bytes) in
#if os(Windows)
try NSData.write(toHandle: self._handle, path: nil,
buf: UnsafeRawPointer(bytes.baseAddress!),
length: bytes.count)
#else
try NSData.write(toFileDescriptor: self._fd, path: nil, buf: UnsafeRawPointer(bytes.baseAddress!), length: bytes.count)
#endif
try _writeBytes(buf: UnsafeRawPointer(bytes.baseAddress!), length: bytes.count)
}
}
}
Expand Down Expand Up @@ -446,31 +470,7 @@ open class FileHandle : NSObject, NSSecureCoding {

@available(swift, deprecated: 100000, renamed: "write(contentsOf:)")
open func write(_ data: Data) {
#if os(Windows)
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
for region in data.regions {
region.withUnsafeBytes { (bytes) in
do {
try NSData.write(toHandle: self._handle, path: nil,
buf: UnsafeRawPointer(bytes.baseAddress!),
length: bytes.count)
} catch {
fatalError("Write failure")
}
}
}
#else
guard _fd >= 0 else { return }
for region in data.regions {
region.withUnsafeBytes { (bytes) in
do {
try NSData.write(toFileDescriptor: self._fd, path: nil, buf: UnsafeRawPointer(bytes.baseAddress!), length: bytes.count)
} catch {
fatalError("Write failure")
}
}
}
#endif
try! write(contentsOf: data)
}

@available(swift, deprecated: 100000, renamed: "offset()")
Expand Down
5 changes: 5 additions & 0 deletions Foundation/FileManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,11 @@ open class FileManager : NSObject {
return statInfo
}

internal func _permissionsOfItem(atPath path: String) throws -> Int {
let fileInfo = try _lstatFile(atPath: path)
return Int(fileInfo.st_mode & 0o777)
}

/* -contentsEqualAtPath:andPath: does not take into account data stored in the resource fork or filesystem extended attributes.
*/
open func contentsEqual(atPath path1: String, andPath path2: String) -> Bool {
Expand Down
116 changes: 31 additions & 85 deletions Foundation/NSData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -433,100 +433,46 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
return result
}

internal func makeTemporaryFile(inDirectory dirPath: String) throws -> (Int32, String) {
let template = dirPath._nsObject.appendingPathComponent("tmp.XXXXXX")
let maxLength = Int(PATH_MAX) + 1
var buf = [Int8](repeating: 0, count: maxLength)
let _ = template._nsObject.getFileSystemRepresentation(&buf, maxLength: maxLength)
let fd = mkstemp(&buf)
if fd == -1 {
throw _NSErrorWithErrno(errno, reading: false, path: dirPath)
}
let pathResult = FileManager.default.string(withFileSystemRepresentation:buf, length: Int(strlen(buf)))
return (fd, pathResult)
}

internal class func write(toFileDescriptor fd: Int32, path: String? = nil, buf: UnsafeRawPointer, length: Int) throws {
var bytesRemaining = length
while bytesRemaining > 0 {
var bytesWritten : Int
repeat {
#if os(macOS) || os(iOS)
bytesWritten = Darwin.write(fd, buf.advanced(by: length - bytesRemaining), bytesRemaining)
#elseif os(Linux) || os(Android) || CYGWIN
bytesWritten = Glibc.write(fd, buf.advanced(by: length - bytesRemaining), bytesRemaining)
#endif
} while (bytesWritten < 0 && errno == EINTR)
if bytesWritten <= 0 {
throw _NSErrorWithErrno(errno, reading: false, path: path)
} else {
bytesRemaining -= bytesWritten
}
}
}

/// Writes the data object's bytes to the file specified by a given path.
open func write(toFile path: String, options writeOptionsMask: WritingOptions = []) throws {
let fm = FileManager.default
try fm._fileSystemRepresentation(withPath: path, { pathFsRep in
var fd : Int32
var mode : mode_t? = nil
let useAuxiliaryFile = writeOptionsMask.contains(.atomic)
var auxFilePath : String? = nil
if useAuxiliaryFile {
// Preserve permissions.
var info = stat()
if lstat(pathFsRep, &info) == 0 {
let mode = mode_t(info.st_mode)
} else if errno != ENOENT && errno != ENAMETOOLONG {
throw _NSErrorWithErrno(errno, reading: false, path: path)
}
let (newFD, path) = try self.makeTemporaryFile(inDirectory: path._nsObject.deletingLastPathComponent)
fd = newFD
auxFilePath = path
fchmod(fd, 0o666)
} else {
var flags = O_WRONLY | O_CREAT | O_TRUNC
if writeOptionsMask.contains(.withoutOverwriting) {
flags |= O_EXCL
}
fd = _CFOpenFileWithMode(path, flags, 0o666)
}
if fd == -1 {
throw _NSErrorWithErrno(errno, reading: false, path: path)
}
defer {
close(fd)
}

func doWrite(_ fh: FileHandle) throws {
try self.enumerateByteRangesUsingBlockRethrows { (buf, range, stop) in
if range.length > 0 {
do {
try NSData.write(toFileDescriptor: fd, path: path, buf: buf, length: range.length)
if fsync(fd) < 0 {
throw _NSErrorWithErrno(errno, reading: false, path: path)
}
} catch {
if let auxFilePath = auxFilePath {
try? FileManager.default.removeItem(atPath: auxFilePath)
}
throw error
}
try fh._writeBytes(buf: buf, length: range.length)
}
}
if let auxFilePath = auxFilePath {
try fm._fileSystemRepresentation(withPath: auxFilePath, { auxFilePathFsRep in
if rename(auxFilePathFsRep, pathFsRep) != 0 {
let savedErrno = errno
try? FileManager.default.removeItem(atPath: auxFilePath)
throw _NSErrorWithErrno(savedErrno, reading: false, path: path)
}
if let mode = mode {
chmod(pathFsRep, mode)
}
})
try fh.synchronize()
}

let fm = FileManager.default
// The destination file path may not exist so provide a default file permissions of RW user only
let permissions = (try? fm._permissionsOfItem(atPath: path)) ?? 0o600

if writeOptionsMask.contains(.atomic) {
let (newFD, auxFilePath) = try _NSCreateTemporaryFile(path)
let fh = FileHandle(fileDescriptor: newFD, closeOnDealloc: true)
do {
try doWrite(fh)
try _NSCleanupTemporaryFile(auxFilePath, path)
try fm.setAttributes([.posixPermissions: NSNumber(value: permissions)], ofItemAtPath: path)
} catch {
let savedErrno = errno
try? fm.removeItem(atPath: auxFilePath)
throw _NSErrorWithErrno(savedErrno, reading: false, path: path)
}
})
} else {
var flags = O_WRONLY | O_CREAT | O_TRUNC
if writeOptionsMask.contains(.withoutOverwriting) {
flags |= O_EXCL
}

guard let fh = FileHandle(path: path, flags: flags, createMode: permissions) else {
throw _NSErrorWithErrno(errno, reading: false, path: path)
}
try doWrite(fh)
}
}

/// Writes the data object's bytes to the file specified by a given path.
Expand Down