Skip to content

Commit c170577

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(). - FileHandle: Add private _checkFileHandle() to validate the file handle to simplify the validation in the public API calls.
1 parent 3411deb commit c170577

File tree

2 files changed

+105
-122
lines changed

2 files changed

+105
-122
lines changed

Foundation/FileHandle.swift

Lines changed: 73 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,41 @@ import CoreFoundation
1212
open class FileHandle : NSObject, NSSecureCoding {
1313
#if os(Windows)
1414
private var _handle: HANDLE
15+
16+
@available(windows, unavailable, message: "cannot perform non-owning handle to fd conversion")
17+
open var fileDescriptor: Int32 {
18+
NSUnsupported()
19+
}
20+
21+
private func _checkFileHandle() {
22+
precondition(_handle != INVALID_HANDLE_VALUE, "Invalid file handle")
23+
}
24+
1525
#else
1626
private var _fd: Int32
17-
#endif
18-
private var _closeOnDealloc: Bool
1927

20-
@available(windows, unavailable,
21-
message: "cannot perform non-owning handle to fd conversion")
2228
open var fileDescriptor: Int32 {
23-
#if os(Windows)
24-
return -1
25-
#else
2629
return _fd
27-
#endif
2830
}
2931

32+
private func _checkFileHandle() {
33+
precondition(_fd >= 0, "Bad file descriptor")
34+
}
35+
#endif
36+
37+
private var _closeOnDealloc: Bool
38+
39+
3040
open var readabilityHandler: ((FileHandle) -> Void)? = {
3141
(FileHandle) -> Void in NSUnimplemented()
3242
}
43+
3344
open var writeabilityHandler: ((FileHandle) -> Void)? = {
3445
(FileHandle) -> Void in NSUnimplemented()
3546
}
3647

3748
open var availableData: Data {
49+
_checkFileHandle()
3850
do {
3951
let readResult = try _readDataOfLength(Int.max, untilEOF: false)
4052
return readResult.toData()
@@ -44,10 +56,12 @@ open class FileHandle : NSObject, NSSecureCoding {
4456
}
4557

4658
open func readDataToEndOfFile() -> Data {
59+
_checkFileHandle()
4760
return readData(ofLength: Int.max)
4861
}
4962

5063
open func readData(ofLength length: Int) -> Data {
64+
_checkFileHandle()
5165
do {
5266
let readResult = try _readDataOfLength(length, untilEOF: true)
5367
return readResult.toData()
@@ -58,7 +72,6 @@ open class FileHandle : NSObject, NSSecureCoding {
5872

5973
internal func _readDataOfLength(_ length: Int, untilEOF: Bool, options: NSData.ReadingOptions = []) throws -> NSData.NSDataReadResult {
6074
#if os(Windows)
61-
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
6275

6376
if length == 0 && !untilEOF {
6477
// Nothing requested, return empty response
@@ -131,7 +144,6 @@ open class FileHandle : NSObject, NSSecureCoding {
131144
free(buffer)
132145
}
133146
#else
134-
precondition(_fd >= 0, "Bad file descriptor")
135147
if length == 0 && !untilEOF {
136148
// Nothing requested, return empty response
137149
return NSData.NSDataReadResult(bytes: nil, length: 0, deallocator: nil)
@@ -202,78 +214,94 @@ open class FileHandle : NSObject, NSSecureCoding {
202214
}
203215

204216
open func write(_ data: Data) {
205-
#if os(Windows)
206-
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
207-
data.enumerateBytes() { (bytes, range, stop) in
208-
do {
209-
try NSData.write(toHandle: self._handle, path: nil,
210-
buf: UnsafeRawPointer(bytes.baseAddress!),
211-
length: bytes.count)
212-
} catch {
213-
fatalError("Write failure")
214-
}
215-
}
216-
#else
217-
guard _fd >= 0 else { return }
217+
_checkFileHandle()
218218
data.enumerateBytes() { (bytes, range, stop) in
219219
do {
220-
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)
221221
} catch {
222-
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)
234+
}
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)
223253
}
254+
bytesRemaining -= bytesWritten
224255
}
225256
#endif
226257
}
227258

228259
// TODO: Error handling.
229260

230261
open var offsetInFile: UInt64 {
262+
_checkFileHandle()
231263
#if os(Windows)
232-
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
233264
var liPointer: LARGE_INTEGER = LARGE_INTEGER(QuadPart: 0)
234265
if SetFilePointerEx(_handle, LARGE_INTEGER(QuadPart: 0),
235266
&liPointer, DWORD(FILE_CURRENT)) == FALSE {
236267
fatalError("SetFilePointerEx failed")
237268
}
238269
return UInt64(liPointer.QuadPart)
239270
#else
240-
precondition(_fd >= 0, "Bad file descriptor")
241271
return UInt64(lseek(_fd, 0, SEEK_CUR))
242272
#endif
243273
}
244274

245275
@discardableResult
246276
open func seekToEndOfFile() -> UInt64 {
277+
_checkFileHandle()
247278
#if os(Windows)
248-
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
249279
var liPointer: LARGE_INTEGER = LARGE_INTEGER(QuadPart: 0)
250280
if SetFilePointerEx(_handle, LARGE_INTEGER(QuadPart: 0),
251281
&liPointer, DWORD(FILE_END)) == FALSE {
252282
fatalError("SetFilePointerEx failed")
253283
}
254284
return UInt64(liPointer.QuadPart)
255285
#else
256-
precondition(_fd >= 0, "Bad file descriptor")
257286
return UInt64(lseek(_fd, 0, SEEK_END))
258287
#endif
259288
}
260289

261290
open func seek(toFileOffset offset: UInt64) {
291+
_checkFileHandle()
262292
#if os(Windows)
263-
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
264293
if SetFilePointerEx(_handle, LARGE_INTEGER(QuadPart: LONGLONG(offset)),
265294
nil, DWORD(FILE_BEGIN)) == FALSE {
266295
fatalError("SetFilePointerEx failed")
267296
}
268297
#else
269-
precondition(_fd >= 0, "Bad file descriptor")
270298
lseek(_fd, off_t(offset), SEEK_SET)
271299
#endif
272300
}
273301

274302
open func truncateFile(atOffset offset: UInt64) {
303+
_checkFileHandle()
275304
#if os(Windows)
276-
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
277305
if SetFilePointerEx(_handle, LARGE_INTEGER(QuadPart: LONGLONG(offset)),
278306
nil, DWORD(FILE_BEGIN)) == FALSE {
279307
fatalError("SetFilePointerEx failed")
@@ -282,21 +310,29 @@ open class FileHandle : NSObject, NSSecureCoding {
282310
fatalError("SetEndOfFile failed")
283311
}
284312
#else
285-
precondition(_fd >= 0, "Bad file descriptor")
286313
if lseek(_fd, off_t(offset), SEEK_SET) < 0 { fatalError("lseek() failed.") }
287314
if ftruncate(_fd, off_t(offset)) < 0 { fatalError("ftruncate() failed.") }
288315
#endif
289316
}
290317

291318
open func synchronizeFile() {
319+
_checkFileHandle()
320+
do {
321+
try _synchronizeFile()
322+
} catch {
323+
fatalError("synchronizeFile failed: \(error)")
324+
}
325+
}
326+
327+
internal func _synchronizeFile() throws {
292328
#if os(Windows)
293-
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
294329
if FlushFileBuffers(_handle) == FALSE {
295-
fatalError("FlushFileBuffers failed: \(GetLastError())")
330+
throw _NSErrorWithErrno(Int32(GetLastError()), reading: false)
296331
}
297332
#else
298-
precondition(_fd >= 0, "Bad file descriptor")
299-
fsync(_fd)
333+
guard fsync(_fd) == 0 else {
334+
throw _NSErrorWithErrno(errno, reading: false)
335+
}
300336
#endif
301337
}
302338

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)