Skip to content

Commit adc8f34

Browse files
committed
NSData: Refactor write(toFile:options) using FileHandle methods.
- Remove makeTemporaryFile(inDirectory:) in favour of using _NSCreateTemporaryFile(path). - FileHandle: Add internal _write(buf:length) method to write out memory buffers. - FileHandle: Add a throwing _synchronizeFile(), use it in synchronizeFile().
1 parent 4e76279 commit adc8f34

File tree

2 files changed

+77
-100
lines changed

2 files changed

+77
-100
lines changed

Foundation/FileHandle.swift

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -215,23 +215,43 @@ open class FileHandle : NSObject, NSSecureCoding {
215215

216216
open func write(_ data: Data) {
217217
_checkFileHandle()
218-
#if os(Windows)
219-
data.enumerateBytes() { (bytes, range, stop) in
220-
do {
221-
try NSData.write(toHandle: self._handle, path: nil,
222-
buf: UnsafeRawPointer(bytes.baseAddress!),
223-
length: bytes.count)
224-
} catch {
225-
fatalError("Write failure")
226-
}
227-
}
228-
#else
229218
data.enumerateBytes() { (bytes, range, stop) in
230219
do {
231-
try NSData.write(toFileDescriptor: self._fd, path: nil, buf: UnsafeRawPointer(bytes.baseAddress!), length: bytes.count)
220+
try _write(buf: UnsafeRawPointer(bytes.baseAddress!), length: bytes.count)
232221
} catch {
233-
fatalError("Write failure")
222+
fatalError("Write failure: \(error)")
223+
}
224+
}
225+
}
226+
227+
internal func _write(buf: UnsafeRawPointer, length: Int) throws {
228+
#if os(Windows)
229+
var bytesRemaining = length
230+
while bytesRemaining > 0 {
231+
var bytesWritten: DWORD = 0
232+
if WriteFile(handle, buf.advanced(by: length - bytesRemaining), DWORD(bytesRemaining), &bytesWritten, nil) == FALSE {
233+
throw _NSErrorWithErrno(Int32(GetLastError()), reading: false, path: nil)
234234
}
235+
if BytesWritten == 0 {
236+
throw _NSErrorWithErrno(Int32(GetLastError()), reading: false, path: nil)
237+
}
238+
bytesRemaining -= Int(bytesWritten)
239+
}
240+
#else
241+
var bytesRemaining = length
242+
while bytesRemaining > 0 {
243+
var bytesWritten = 0
244+
repeat {
245+
#if canImport(Darwin)
246+
bytesWritten = Darwin.write(_fd, buf.advanced(by: length - bytesRemaining), bytesRemaining)
247+
#elseif canImport(Glibc)
248+
bytesWritten = Glibc.write(_fd, buf.advanced(by: length - bytesRemaining), bytesRemaining)
249+
#endif
250+
} while (bytesWritten < 0 && errno == EINTR)
251+
if bytesWritten <= 0 {
252+
throw _NSErrorWithErrno(errno, reading: false, path: nil)
253+
}
254+
bytesRemaining -= bytesWritten
235255
}
236256
#endif
237257
}
@@ -297,12 +317,22 @@ open class FileHandle : NSObject, NSSecureCoding {
297317

298318
open func synchronizeFile() {
299319
_checkFileHandle()
320+
do {
321+
try _synchronizeFile()
322+
} catch {
323+
fatalError("synchronizeFile failed: \(error)")
324+
}
325+
}
326+
327+
internal func _synchronizeFile() throws {
300328
#if os(Windows)
301329
if FlushFileBuffers(_handle) == FALSE {
302-
fatalError("FlushFileBuffers failed: \(GetLastError())")
330+
throw _NSErrorWithErrno(Int32(GetLastError()), reading: false)
303331
}
304332
#else
305-
fsync(_fd)
333+
guard fsync(_fd) == 0 else {
334+
throw _NSErrorWithErrno(errno, reading: false)
335+
}
306336
#endif
307337
}
308338

Foundation/NSData.swift

Lines changed: 32 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -433,100 +433,47 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
433433
return result
434434
}
435435

436-
internal func makeTemporaryFile(inDirectory dirPath: String) throws -> (Int32, String) {
437-
let template = dirPath._nsObject.appendingPathComponent("tmp.XXXXXX")
438-
let maxLength = Int(PATH_MAX) + 1
439-
var buf = [Int8](repeating: 0, count: maxLength)
440-
let _ = template._nsObject.getFileSystemRepresentation(&buf, maxLength: maxLength)
441-
let fd = mkstemp(&buf)
442-
if fd == -1 {
443-
throw _NSErrorWithErrno(errno, reading: false, path: dirPath)
444-
}
445-
let pathResult = FileManager.default.string(withFileSystemRepresentation:buf, length: Int(strlen(buf)))
446-
return (fd, pathResult)
447-
}
448-
449-
internal class func write(toFileDescriptor fd: Int32, path: String? = nil, buf: UnsafeRawPointer, length: Int) throws {
450-
var bytesRemaining = length
451-
while bytesRemaining > 0 {
452-
var bytesWritten : Int
453-
repeat {
454-
#if os(macOS) || os(iOS)
455-
bytesWritten = Darwin.write(fd, buf.advanced(by: length - bytesRemaining), bytesRemaining)
456-
#elseif os(Linux) || os(Android) || CYGWIN
457-
bytesWritten = Glibc.write(fd, buf.advanced(by: length - bytesRemaining), bytesRemaining)
458-
#endif
459-
} while (bytesWritten < 0 && errno == EINTR)
460-
if bytesWritten <= 0 {
461-
throw _NSErrorWithErrno(errno, reading: false, path: path)
462-
} else {
463-
bytesRemaining -= bytesWritten
464-
}
465-
}
466-
}
467436

468437
/// Writes the data object's bytes to the file specified by a given path.
469438
open func write(toFile path: String, options writeOptionsMask: WritingOptions = []) throws {
470-
let fm = FileManager.default
471-
try fm._fileSystemRepresentation(withPath: path, { pathFsRep in
472-
var fd : Int32
473-
var mode : mode_t? = nil
474-
let useAuxiliaryFile = writeOptionsMask.contains(.atomic)
475-
var auxFilePath : String? = nil
476-
if useAuxiliaryFile {
477-
// Preserve permissions.
478-
var info = stat()
479-
if lstat(pathFsRep, &info) == 0 {
480-
let mode = mode_t(info.st_mode)
481-
} else if errno != ENOENT && errno != ENAMETOOLONG {
482-
throw _NSErrorWithErrno(errno, reading: false, path: path)
483-
}
484-
let (newFD, path) = try self.makeTemporaryFile(inDirectory: path._nsObject.deletingLastPathComponent)
485-
fd = newFD
486-
auxFilePath = path
487-
fchmod(fd, 0o666)
488-
} else {
489-
var flags = O_WRONLY | O_CREAT | O_TRUNC
490-
if writeOptionsMask.contains(.withoutOverwriting) {
491-
flags |= O_EXCL
492-
}
493-
fd = _CFOpenFileWithMode(path, flags, 0o666)
494-
}
495-
if fd == -1 {
496-
throw _NSErrorWithErrno(errno, reading: false, path: path)
497-
}
498-
defer {
499-
close(fd)
500-
}
501439

440+
func doWrite(_ fh: FileHandle) throws {
502441
try self.enumerateByteRangesUsingBlockRethrows { (buf, range, stop) in
503442
if range.length > 0 {
504-
do {
505-
try NSData.write(toFileDescriptor: fd, path: path, buf: buf, length: range.length)
506-
if fsync(fd) < 0 {
507-
throw _NSErrorWithErrno(errno, reading: false, path: path)
508-
}
509-
} catch {
510-
if let auxFilePath = auxFilePath {
511-
try? FileManager.default.removeItem(atPath: auxFilePath)
512-
}
513-
throw error
514-
}
443+
try fh._write(buf: buf, length: range.length)
515444
}
516445
}
517-
if let auxFilePath = auxFilePath {
518-
try fm._fileSystemRepresentation(withPath: auxFilePath, { auxFilePathFsRep in
519-
if rename(auxFilePathFsRep, pathFsRep) != 0 {
520-
let savedErrno = errno
521-
try? FileManager.default.removeItem(atPath: auxFilePath)
522-
throw _NSErrorWithErrno(savedErrno, reading: false, path: path)
523-
}
524-
if let mode = mode {
525-
chmod(pathFsRep, mode)
526-
}
527-
})
446+
try fh._synchronizeFile()
447+
}
448+
449+
let fm = FileManager.default
450+
// The destination file path may not exist so provide a default file mode of RW user only
451+
let permissions = (try? fm.attributesOfItem(atPath: path)[.posixPermissions]) as? NSNumber
452+
let mode = permissions?.intValue ?? 0o600
453+
454+
if writeOptionsMask.contains(.atomic) {
455+
let (newFD, auxFilePath) = try _NSCreateTemporaryFile(path)
456+
let fh = FileHandle(fileDescriptor: newFD, closeOnDealloc: true)
457+
do {
458+
try doWrite(fh)
459+
try _NSCleanupTemporaryFile(auxFilePath, path)
460+
try fm.setAttributes([.posixPermissions: NSNumber(value: mode)], ofItemAtPath: path)
461+
} catch {
462+
let savedErrno = errno
463+
try? fm.removeItem(atPath: auxFilePath)
464+
throw _NSErrorWithErrno(savedErrno, reading: false, path: path)
528465
}
529-
})
466+
} else {
467+
var flags = O_WRONLY | O_CREAT | O_TRUNC
468+
if writeOptionsMask.contains(.withoutOverwriting) {
469+
flags |= O_EXCL
470+
}
471+
472+
guard let fh = FileHandle(path: path, flags: flags, createMode: mode) else {
473+
throw _NSErrorWithErrno(errno, reading: false, path: path)
474+
}
475+
try doWrite(fh)
476+
}
530477
}
531478

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

0 commit comments

Comments
 (0)