Skip to content

Commit bb74cad

Browse files
committed
NSData: Refactor write(toFile:options) using FileHandle methods.
- Remove makeTemporaryFile(inDirectory:) in favour of using _NSCreateTemporaryFile(path). - FileHandle: Add internal _writeBytes(buf:length) method to write out memory buffers. - FileManager: add _permissionsOfItem(atPath:) to only get permissions as it does less work than attributesOfItem(atPath:).
1 parent fcc46b0 commit bb74cad

File tree

3 files changed

+68
-117
lines changed

3 files changed

+68
-117
lines changed

Foundation/FileHandle.swift

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ import CoreFoundation
1414
#if canImport(Darwin)
1515
import Darwin
1616
fileprivate let _read = Darwin.read(_:_:_:)
17+
fileprivate let _write = Darwin.write(_:_:_:)
1718
fileprivate let _close = Darwin.close(_:)
1819
#elseif canImport(Glibc)
1920
import Glibc
2021
fileprivate let _read = Glibc.read(_:_:_:)
22+
fileprivate let _write = Glibc.write(_:_:_:)
2123
fileprivate let _close = Glibc.close(_:)
2224
#endif
2325

@@ -217,6 +219,34 @@ open class FileHandle : NSObject, NSSecureCoding {
217219
#endif
218220
}
219221

222+
internal func _writeBytes(buf: UnsafeRawPointer, length: Int) throws {
223+
#if os(Windows)
224+
var bytesRemaining = length
225+
while bytesRemaining > 0 {
226+
var bytesWritten: DWORD = 0
227+
if WriteFile(handle, buf.advanced(by: length - bytesRemaining), DWORD(bytesRemaining), &bytesWritten, nil) == FALSE {
228+
throw _NSErrorWithErrno(Int32(GetLastError()), reading: false, path: nil)
229+
}
230+
if bytesWritten == 0 {
231+
throw _NSErrorWithErrno(Int32(GetLastError()), reading: false, path: nil)
232+
}
233+
bytesRemaining -= Int(bytesWritten)
234+
}
235+
#else
236+
var bytesRemaining = length
237+
while bytesRemaining > 0 {
238+
var bytesWritten = 0
239+
repeat {
240+
bytesWritten = _write(_fd, buf.advanced(by: length - bytesRemaining), bytesRemaining)
241+
} while (bytesWritten < 0 && errno == EINTR)
242+
if bytesWritten <= 0 {
243+
throw _NSErrorWithErrno(errno, reading: false, path: nil)
244+
}
245+
bytesRemaining -= bytesWritten
246+
}
247+
#endif
248+
}
249+
220250
#if os(Windows)
221251
public init(handle: HANDLE, closeOnDealloc closeopt: Bool) {
222252
_handle = handle
@@ -314,13 +344,7 @@ open class FileHandle : NSObject, NSSecureCoding {
314344

315345
for region in data.regions {
316346
try region.withUnsafeBytes { (bytes) in
317-
#if os(Windows)
318-
try NSData.write(toHandle: self._handle, path: nil,
319-
buf: UnsafeRawPointer(bytes.baseAddress!),
320-
length: bytes.count)
321-
#else
322-
try NSData.write(toFileDescriptor: self._fd, path: nil, buf: UnsafeRawPointer(bytes.baseAddress!), length: bytes.count)
323-
#endif
347+
try _writeBytes(buf: UnsafeRawPointer(bytes.baseAddress!), length: bytes.count)
324348
}
325349
}
326350
}
@@ -446,31 +470,7 @@ open class FileHandle : NSObject, NSSecureCoding {
446470

447471
@available(swift, deprecated: 100000, renamed: "write(contentsOf:)")
448472
open func write(_ data: Data) {
449-
#if os(Windows)
450-
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
451-
for region in data.regions {
452-
region.withUnsafeBytes { (bytes) in
453-
do {
454-
try NSData.write(toHandle: self._handle, path: nil,
455-
buf: UnsafeRawPointer(bytes.baseAddress!),
456-
length: bytes.count)
457-
} catch {
458-
fatalError("Write failure")
459-
}
460-
}
461-
}
462-
#else
463-
guard _fd >= 0 else { return }
464-
for region in data.regions {
465-
region.withUnsafeBytes { (bytes) in
466-
do {
467-
try NSData.write(toFileDescriptor: self._fd, path: nil, buf: UnsafeRawPointer(bytes.baseAddress!), length: bytes.count)
468-
} catch {
469-
fatalError("Write failure")
470-
}
471-
}
472-
}
473-
#endif
473+
try! write(contentsOf: data)
474474
}
475475

476476
@available(swift, deprecated: 100000, renamed: "offset()")

Foundation/FileManager.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1392,6 +1392,11 @@ open class FileManager : NSObject {
13921392
return statInfo
13931393
}
13941394

1395+
internal func _permissionsOfItem(atPath path: String) throws -> Int {
1396+
let fileInfo = try _lstatFile(atPath: path)
1397+
return Int(fileInfo.st_mode & 0o777)
1398+
}
1399+
13951400
/* -contentsEqualAtPath:andPath: does not take into account data stored in the resource fork or filesystem extended attributes.
13961401
*/
13971402
open func contentsEqual(atPath path1: String, andPath path2: String) -> Bool {

Foundation/NSData.swift

Lines changed: 31 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -433,100 +433,46 @@ 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._writeBytes(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.synchronize()
447+
}
448+
449+
let fm = FileManager.default
450+
// The destination file path may not exist so provide a default file permissions of RW user only
451+
let permissions = (try? fm._permissionsOfItem(atPath: path)) ?? 0o600
452+
453+
if writeOptionsMask.contains(.atomic) {
454+
let (newFD, auxFilePath) = try _NSCreateTemporaryFile(path)
455+
let fh = FileHandle(fileDescriptor: newFD, closeOnDealloc: true)
456+
do {
457+
try doWrite(fh)
458+
try _NSCleanupTemporaryFile(auxFilePath, path)
459+
try fm.setAttributes([.posixPermissions: NSNumber(value: permissions)], ofItemAtPath: path)
460+
} catch {
461+
let savedErrno = errno
462+
try? fm.removeItem(atPath: auxFilePath)
463+
throw _NSErrorWithErrno(savedErrno, reading: false, path: path)
528464
}
529-
})
465+
} else {
466+
var flags = O_WRONLY | O_CREAT | O_TRUNC
467+
if writeOptionsMask.contains(.withoutOverwriting) {
468+
flags |= O_EXCL
469+
}
470+
471+
guard let fh = FileHandle(path: path, flags: flags, createMode: permissions) else {
472+
throw _NSErrorWithErrno(errno, reading: false, path: path)
473+
}
474+
try doWrite(fh)
475+
}
530476
}
531477

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

0 commit comments

Comments
 (0)