| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2016, 2018 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 CoreFoundation |
| import Dispatch |
| |
| // FileHandle has a .read(upToCount:) method. Just invoking read() will cause an ambiguity warning. Use _read instead. |
| // Same with close()/.close(). |
| #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 |
| |
| extension NSError { |
| internal var errnoIfAvailable: Int? { |
| if domain == NSPOSIXErrorDomain { |
| return code |
| } |
| |
| if let underlying = userInfo[NSUnderlyingErrorKey] as? NSError { |
| return underlying.errnoIfAvailable |
| } |
| |
| return nil |
| } |
| } |
| |
| /* On Darwin, FileHandle conforms to NSSecureCoding for use with NSXPCConnection and related facilities only. On swift-corelibs-foundation, it does not conform to that protocol since those facilities are unavailable. */ |
| |
| open class FileHandle : NSObject { |
| #if os(Windows) |
| private var _handle: HANDLE |
| |
| internal var handle: HANDLE { |
| return _handle |
| } |
| |
| @available(Windows, unavailable, message: "Cannot perform non-owning handle to fd conversion") |
| open var fileDescriptor: Int32 { |
| NSUnsupported() |
| } |
| |
| private func _checkFileHandle() { |
| precondition(_handle != INVALID_HANDLE_VALUE, "Invalid file handle") |
| } |
| |
| internal var _isPlatformHandleValid: Bool { |
| return _handle != INVALID_HANDLE_VALUE |
| } |
| #else |
| private var _fd: Int32 |
| |
| open var fileDescriptor: Int32 { |
| return _fd |
| } |
| |
| private func _checkFileHandle() { |
| precondition(_fd >= 0, "Bad file descriptor") |
| } |
| |
| internal var _isPlatformHandleValid: Bool { |
| return fileDescriptor >= 0 |
| } |
| #endif |
| |
| private var _closeOnDealloc: Bool |
| |
| private var currentBackgroundActivityOwner: AnyObject? // Guarded by privateAsyncVariablesLock |
| |
| private var readabilitySource: DispatchSourceProtocol? // Guarded by privateAsyncVariablesLock |
| private var writabilitySource: DispatchSourceProtocol? // Guarded by privateAsyncVariablesLock |
| |
| private var privateAsyncVariablesLock = NSLock() |
| |
| // matches Darwin. |
| private var _queue: DispatchQueue? // Guarded by privateAsyncVariablesLock |
| private var queue: DispatchQueue { |
| privateAsyncVariablesLock.lock() |
| defer { privateAsyncVariablesLock.unlock() } |
| |
| if let queue = _queue { |
| return queue |
| } else { |
| let storage = DispatchQueue(label: "com.apple.NSFileHandle.fd_monitoring") |
| _queue = storage |
| return storage |
| } |
| } |
| private var queueIfExists: DispatchQueue? { |
| privateAsyncVariablesLock.lock() |
| defer { privateAsyncVariablesLock.unlock() } |
| |
| return _queue |
| } |
| |
| private func monitor(forReading reading: Bool, resumed: Bool = true, handler: @escaping (FileHandle, DispatchSourceProtocol) -> Void) -> DispatchSourceProtocol { |
| _checkFileHandle() |
| |
| // Duplicate the file descriptor. |
| // Closing the file descriptor while Dispatch is monitoring it leads to undefined behavior; guard against that. |
| #if os(Windows) |
| var dupHandle: HANDLE? |
| if !DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &dupHandle, |
| /*dwDesiredAccess:*/0, /*bInheritHandle:*/true, DWORD(DUPLICATE_SAME_ACCESS)) { |
| fatalError("DuplicateHandleFailed: \(GetLastError())") |
| } |
| |
| let fd = _open_osfhandle(intptr_t(bitPattern: dupHandle), 0) |
| #else |
| let fd = dup(fileDescriptor) |
| #endif |
| let source: DispatchSourceProtocol |
| if reading { |
| source = DispatchSource.makeReadSource(fileDescriptor: fd, queue: queue) |
| } else { |
| source = DispatchSource.makeWriteSource(fileDescriptor: fd, queue: queue) |
| } |
| |
| let sourceObject = source as AnyObject |
| source.setEventHandler { [weak self, weak sourceObject] in |
| if let me = self, let source = sourceObject as? DispatchSourceProtocol { |
| handler(me, source) |
| } |
| } |
| source.setCancelHandler { |
| _ = _close(fd) |
| } |
| |
| if resumed { |
| source.resume() |
| } |
| |
| return source |
| } |
| |
| private var _readabilityHandler: ((FileHandle) -> Void)? = nil // Guarded by privateAsyncVariablesLock |
| open var readabilityHandler: ((FileHandle) -> Void)? { |
| get { |
| privateAsyncVariablesLock.lock() |
| let handler = _readabilityHandler |
| privateAsyncVariablesLock.unlock() |
| return handler |
| } |
| set { |
| privateAsyncVariablesLock.lock() |
| _readabilityHandler = newValue |
| if let oldSource = readabilitySource { |
| oldSource.cancel() |
| readabilitySource = nil |
| } |
| privateAsyncVariablesLock.unlock() |
| |
| if let handler = newValue { |
| // The handler can be called as part of the creation of the monitoring source, which can then call into FileHandle code again. Make sure we do not hold the lock when this is invoked. |
| let source = monitor(forReading: true, handler: { (fh, _) in handler(fh) }) |
| |
| privateAsyncVariablesLock.lock() |
| readabilitySource = source |
| privateAsyncVariablesLock.unlock() |
| } |
| } |
| } |
| |
| private var _writeabilityHandler: ((FileHandle) -> Void)? = nil // Guarded by privateAsyncVariablesLock |
| open var writeabilityHandler: ((FileHandle) -> Void)? { |
| get { |
| privateAsyncVariablesLock.lock() |
| let handler = _writeabilityHandler |
| privateAsyncVariablesLock.unlock() |
| return handler |
| } |
| set { |
| privateAsyncVariablesLock.lock() |
| _writeabilityHandler = newValue |
| if let oldSource = writabilitySource { |
| oldSource.cancel() |
| writabilitySource = nil |
| } |
| privateAsyncVariablesLock.unlock() |
| |
| if let handler = newValue { |
| // The handler can be called as part of the creation of the monitoring source, which can then call into FileHandle code again. Make sure we do not hold the lock when this is invoked. |
| let source = monitor(forReading: false, handler: { (fh, _) in handler(fh) }) |
| |
| privateAsyncVariablesLock.lock() |
| writabilitySource = source |
| privateAsyncVariablesLock.unlock() |
| } |
| } |
| } |
| |
| open var availableData: Data { |
| _checkFileHandle() |
| do { |
| let readResult = try _readDataOfLength(Int.max, untilEOF: false) |
| return readResult.toData() |
| } catch { |
| fatalError("\(error)") |
| } |
| } |
| |
| internal func _readDataOfLength(_ length: Int, untilEOF: Bool, options: NSData.ReadingOptions = []) throws -> NSData.NSDataReadResult { |
| guard _isPlatformHandleValid else { throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadUnknown.rawValue) } |
| |
| #if os(Windows) |
| if length == 0 && !untilEOF { |
| // Nothing requested, return empty response |
| return NSData.NSDataReadResult(bytes: nil, length: 0, deallocator: nil) |
| } |
| |
| if GetFileType(_handle) == FILE_TYPE_DISK { |
| var fiFileInfo: BY_HANDLE_FILE_INFORMATION = BY_HANDLE_FILE_INFORMATION() |
| if !GetFileInformationByHandle(_handle, &fiFileInfo) { |
| throw _NSErrorWithWindowsError(GetLastError(), reading: true) |
| } |
| |
| if options.contains(.alwaysMapped) { |
| let hMapping: HANDLE = |
| CreateFileMappingA(_handle, nil, DWORD(PAGE_READONLY), 0, 0, nil) |
| if hMapping == HANDLE(bitPattern: 0) { |
| fatalError("CreateFileMappingA failed") |
| } |
| |
| let szFileSize: UInt64 = (UInt64(fiFileInfo.nFileSizeHigh) << 32) | UInt64(fiFileInfo.nFileSizeLow << 0) |
| let szMapSize: UInt64 = Swift.min(UInt64(length), szFileSize) |
| let pData: UnsafeMutableRawPointer = |
| MapViewOfFile(hMapping, DWORD(FILE_MAP_READ), 0, 0, szMapSize) |
| |
| return NSData.NSDataReadResult(bytes: pData, length: Int(szMapSize)) { buffer, length in |
| if !UnmapViewOfFile(buffer) { |
| fatalError("UnmapViewOfFile failed") |
| } |
| if !CloseHandle(hMapping) { |
| fatalError("CloseHandle failed") |
| } |
| } |
| } |
| } |
| |
| let blockSize: Int = 8 * 1024 |
| var allocated: Int = blockSize |
| var buffer: UnsafeMutableRawPointer = malloc(allocated)! |
| var total: Int = 0 |
| |
| while total < length { |
| let remaining = length - total |
| let BytesToRead: DWORD = DWORD(min(blockSize, remaining)) |
| |
| if (allocated - total) < BytesToRead { |
| allocated *= 2 |
| buffer = _CFReallocf(buffer, allocated) |
| } |
| |
| var BytesRead: DWORD = 0 |
| if !ReadFile(_handle, buffer.advanced(by: total), BytesToRead, &BytesRead, nil) { |
| let err = GetLastError() |
| if err == ERROR_BROKEN_PIPE { |
| break |
| } |
| free(buffer) |
| throw _NSErrorWithWindowsError(err, reading: true) |
| } |
| total += Int(BytesRead) |
| if BytesRead == 0 || !untilEOF { |
| break |
| } |
| } |
| |
| if total == 0 { |
| free(buffer) |
| return NSData.NSDataReadResult(bytes: nil, length: 0, deallocator: nil) |
| } |
| |
| buffer = _CFReallocf(buffer, total) |
| let data = buffer.bindMemory(to: UInt8.self, capacity: total) |
| return NSData.NSDataReadResult(bytes: data, length: total) { buffer, length in |
| free(buffer) |
| } |
| #else |
| if length == 0 && !untilEOF { |
| // Nothing requested, return empty response |
| return NSData.NSDataReadResult(bytes: nil, length: 0, deallocator: nil) |
| } |
| |
| var statbuf = stat() |
| if fstat(_fd, &statbuf) < 0 { |
| throw _NSErrorWithErrno(errno, reading: true) |
| } |
| |
| let readBlockSize: Int |
| if statbuf.st_mode & S_IFMT == S_IFREG { |
| // TODO: Should files over a certain size always be mmap()'d? |
| if options.contains(.alwaysMapped) { |
| // Filesizes are often 64bit even on 32bit systems |
| let mapSize = min(length, Int(clamping: statbuf.st_size)) |
| let data = mmap(nil, mapSize, PROT_READ, MAP_PRIVATE, _fd, 0) |
| // Swift does not currently expose MAP_FAILURE |
| if data != UnsafeMutableRawPointer(bitPattern: -1) { |
| return NSData.NSDataReadResult(bytes: data!, length: mapSize) { buffer, length in |
| munmap(buffer, length) |
| } |
| } |
| } |
| |
| if statbuf.st_blksize > 0 { |
| readBlockSize = Int(clamping: statbuf.st_blksize) |
| } else { |
| readBlockSize = 1024 * 8 |
| } |
| } else { |
| /* We get here on sockets, character special files, FIFOs ... */ |
| readBlockSize = 1024 * 8 |
| } |
| var currentAllocationSize = readBlockSize |
| var dynamicBuffer = malloc(currentAllocationSize)! |
| var total = 0 |
| |
| while total < length { |
| let remaining = length - total |
| let amountToRead = min(readBlockSize, remaining) |
| // Make sure there is always at least amountToRead bytes available in the buffer. |
| if (currentAllocationSize - total) < amountToRead { |
| currentAllocationSize *= 2 |
| dynamicBuffer = _CFReallocf(dynamicBuffer, currentAllocationSize) |
| } |
| let amtRead = _read(_fd, dynamicBuffer.advanced(by: total), amountToRead) |
| if amtRead < 0 { |
| free(dynamicBuffer) |
| throw _NSErrorWithErrno(errno, reading: true) |
| } |
| total += amtRead |
| if amtRead == 0 || !untilEOF { // If there is nothing more to read or we shouldn't keep reading then exit |
| break |
| } |
| } |
| |
| if total == 0 { |
| free(dynamicBuffer) |
| return NSData.NSDataReadResult(bytes: nil, length: 0, deallocator: nil) |
| } |
| dynamicBuffer = _CFReallocf(dynamicBuffer, total) |
| let bytePtr = dynamicBuffer.bindMemory(to: UInt8.self, capacity: total) |
| return NSData.NSDataReadResult(bytes: bytePtr, length: total) { buffer, length in |
| free(buffer) |
| } |
| #endif |
| } |
| |
| internal func _readBytes(into buffer: UnsafeMutablePointer<UInt8>, length: Int) throws -> Int { |
| #if os(Windows) |
| var BytesRead: DWORD = 0 |
| let BytesToRead: DWORD = DWORD(length) |
| if !ReadFile(_handle, buffer, BytesToRead, &BytesRead, nil) { |
| throw _NSErrorWithWindowsError(GetLastError(), reading: true) |
| } |
| return Int(BytesRead) |
| #else |
| let amtRead = _read(_fd, buffer, length) |
| if amtRead < 0 { |
| throw _NSErrorWithErrno(errno, reading: true) |
| } |
| return amtRead |
| #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) { |
| throw _NSErrorWithWindowsError(GetLastError(), reading: false) |
| } |
| if bytesWritten == 0 { |
| throw _NSErrorWithWindowsError(GetLastError(), reading: false) |
| } |
| 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) |
| internal init(handle: HANDLE, closeOnDealloc closeopt: Bool) { |
| _handle = handle |
| _closeOnDealloc = closeopt |
| } |
| |
| public init(fileDescriptor fd: Int32, closeOnDealloc closeopt: Bool) { |
| if (closeopt) { |
| var handle: HANDLE? |
| if !DuplicateHandle(GetCurrentProcess(), HANDLE(bitPattern: _get_osfhandle(fd))!, GetCurrentProcess(), &handle, 0, false, DWORD(DUPLICATE_SAME_ACCESS)) { |
| fatalError("DuplicateHandle() failed: \(GetLastError())") |
| } |
| _close(fd) |
| _handle = handle! |
| _closeOnDealloc = true |
| } else { |
| _handle = HANDLE(bitPattern: _get_osfhandle(fd))! |
| _closeOnDealloc = false |
| } |
| } |
| |
| public convenience init(fileDescriptor fd: Int32) { |
| self.init(handle: HANDLE(bitPattern: _get_osfhandle(fd))!, |
| closeOnDealloc: false) |
| } |
| |
| internal convenience init?(path: String, flags: Int32, createMode: Int) { |
| let fd = _CFOpenFileWithMode(path, flags, mode_t(createMode)) |
| guard fd > 0 else { return nil } |
| self.init(fileDescriptor: fd, closeOnDealloc: true) |
| if _handle == INVALID_HANDLE_VALUE { return nil } |
| } |
| #else |
| public init(fileDescriptor fd: Int32, closeOnDealloc closeopt: Bool) { |
| _fd = fd |
| _closeOnDealloc = closeopt |
| } |
| |
| public convenience init(fileDescriptor fd: Int32) { |
| self.init(fileDescriptor: fd, closeOnDealloc: false) |
| } |
| |
| internal init?(path: String, flags: Int32, createMode: Int) { |
| _fd = _CFOpenFileWithMode(path, flags, mode_t(createMode)) |
| _closeOnDealloc = true |
| super.init() |
| if _fd < 0 { |
| return nil |
| } |
| } |
| #endif |
| |
| internal convenience init?(fileSystemRepresentation: UnsafePointer<Int8>, flags: Int32, createMode: Int) { |
| #if os(Windows) |
| var fd: Int32 = -1 |
| if let path = String(cString: fileSystemRepresentation).cString(using: .utf16) { |
| fd = _CFOpenFileWithMode(path, flags, mode_t(createMode)) |
| } |
| #else |
| let fd = _CFOpenFileWithMode(fileSystemRepresentation, flags, mode_t(createMode)) |
| #endif |
| guard fd > 0 else { return nil } |
| self.init(fileDescriptor: fd, closeOnDealloc: true) |
| } |
| |
| deinit { |
| // .close() tries to wait after operations in flight on the handle queue, if one exists, and then close. It does so by sending .sync { … } work to it. |
| // if we try to do that here, we may end up in a situation where: |
| // - the last reference is held by the handle queue; |
| // - the last operation holding onto the handle finishes, and the block is released; |
| // - the handle is released; |
| // - the handle's deinit is invoked; |
| // - deinit tries to .sync { … } to serialize the work on the handle queue, _which we're already on_ |
| // - deadlock! DispatchQueue's deadlock detection triggers and crashes us. |
| // since all operations on the handle queue retain the handle during use, if the handle is being deinited, then there are no more operations on the queue, so this is serial with respect to them anyway. Just close the handle immediately. |
| try? _immediatelyClose() |
| } |
| |
| // MARK: - |
| // MARK: New API. |
| |
| @available(swift 5.0) |
| public func readToEnd() throws -> Data? { |
| guard self != FileHandle._nulldeviceFileHandle else { return nil } |
| |
| return try read(upToCount: Int.max) |
| } |
| |
| @available(swift 5.0) |
| public func read(upToCount count: Int) throws -> Data? { |
| guard self != FileHandle._nulldeviceFileHandle else { return nil } |
| |
| let result = try _readDataOfLength(count, untilEOF: true) |
| return result.length == 0 ? nil : result.toData() |
| } |
| |
| @available(swift 5.0) |
| public func write<T: DataProtocol>(contentsOf data: T) throws { |
| guard self != FileHandle._nulldeviceFileHandle else { return } |
| |
| guard _isPlatformHandleValid else { throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileWriteUnknown.rawValue) } |
| |
| for region in data.regions { |
| try region.withUnsafeBytes { (bytes) in |
| if let baseAddress = bytes.baseAddress, bytes.count > 0 { |
| try _writeBytes(buf: UnsafeRawPointer(baseAddress), length: bytes.count) |
| } |
| } |
| } |
| } |
| |
| @available(swift 5.0) |
| public func offset() throws -> UInt64 { |
| guard self != FileHandle._nulldeviceFileHandle else { return 0 } |
| |
| guard _isPlatformHandleValid else { throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadUnknown.rawValue) } |
| |
| #if os(Windows) |
| var liPointer: LARGE_INTEGER = LARGE_INTEGER(QuadPart: 0) |
| guard SetFilePointerEx(_handle, LARGE_INTEGER(QuadPart: 0), &liPointer, DWORD(FILE_CURRENT)) else { |
| throw _NSErrorWithWindowsError(GetLastError(), reading: true) |
| } |
| return UInt64(liPointer.QuadPart) |
| #else |
| let offset = lseek(_fd, 0, SEEK_CUR) |
| guard offset >= 0 else { throw _NSErrorWithErrno(errno, reading: true) } |
| return UInt64(offset) |
| #endif |
| } |
| |
| @available(swift 5.0) |
| @discardableResult |
| public func seekToEnd() throws -> UInt64 { |
| guard self != FileHandle._nulldeviceFileHandle else { return 0 } |
| |
| guard _isPlatformHandleValid else { throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadUnknown.rawValue) } |
| |
| #if os(Windows) |
| var liPointer: LARGE_INTEGER = LARGE_INTEGER(QuadPart: 0) |
| guard SetFilePointerEx(_handle, LARGE_INTEGER(QuadPart: 0), &liPointer, DWORD(FILE_END)) else { |
| throw _NSErrorWithWindowsError(GetLastError(), reading: true) |
| } |
| return UInt64(liPointer.QuadPart) |
| #else |
| let offset = lseek(_fd, 0, SEEK_END) |
| guard offset >= 0 else { throw _NSErrorWithErrno(errno, reading: true) } |
| return UInt64(offset) |
| #endif |
| } |
| |
| @available(swift 5.0) |
| public func seek(toOffset offset: UInt64) throws { |
| guard self != FileHandle._nulldeviceFileHandle else { return } |
| |
| guard _isPlatformHandleValid else { throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadUnknown.rawValue) } |
| |
| #if os(Windows) |
| guard SetFilePointerEx(_handle, LARGE_INTEGER(QuadPart: LONGLONG(offset)), nil, DWORD(FILE_BEGIN)) else { |
| throw _NSErrorWithWindowsError(GetLastError(), reading: true) |
| } |
| #else |
| guard lseek(_fd, off_t(offset), SEEK_SET) >= 0 else { throw _NSErrorWithErrno(errno, reading: true) } |
| #endif |
| } |
| |
| @available(swift 5.0) |
| public func truncate(toOffset offset: UInt64) throws { |
| guard self != FileHandle._nulldeviceFileHandle else { return } |
| |
| guard _isPlatformHandleValid else { throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileWriteUnknown.rawValue) } |
| |
| #if os(Windows) |
| guard SetFilePointerEx(_handle, LARGE_INTEGER(QuadPart: LONGLONG(offset)), nil, DWORD(FILE_BEGIN)) else { |
| throw _NSErrorWithWindowsError(GetLastError(), reading: false) |
| } |
| guard SetEndOfFile(_handle) else { |
| throw _NSErrorWithWindowsError(GetLastError(), reading: false) |
| } |
| #else |
| guard lseek(_fd, off_t(offset), SEEK_SET) >= 0 else { throw _NSErrorWithErrno(errno, reading: false) } |
| guard ftruncate(_fd, off_t(offset)) >= 0 else { throw _NSErrorWithErrno(errno, reading: false) } |
| #endif |
| } |
| |
| @available(swift 5.0) |
| public func synchronize() throws { |
| guard self != FileHandle._nulldeviceFileHandle else { return } |
| |
| #if os(Windows) |
| guard FlushFileBuffers(_handle) else { |
| throw _NSErrorWithWindowsError(GetLastError(), reading: false) |
| } |
| #else |
| guard fsync(_fd) >= 0 else { throw _NSErrorWithErrno(errno, reading: false) } |
| #endif |
| } |
| |
| private func performOnQueueIfExists(_ block: () throws -> Void) throws { |
| if let queue = queueIfExists { |
| var theError: Swift.Error? |
| queue.sync { |
| do { try block() } catch { theError = error } |
| } |
| if let error = theError { |
| throw error |
| } |
| } else { |
| try block() |
| } |
| } |
| |
| @available(swift 5.0) |
| public func close() throws { |
| try performOnQueueIfExists { |
| try _immediatelyClose() |
| } |
| } |
| |
| private func _immediatelyClose() throws { |
| guard self != FileHandle._nulldeviceFileHandle else { return } |
| guard _isPlatformHandleValid else { return } |
| |
| privateAsyncVariablesLock.lock() |
| writabilitySource?.cancel() |
| readabilitySource?.cancel() |
| _readabilityHandler = nil |
| _writeabilityHandler = nil |
| writabilitySource = nil |
| readabilitySource = nil |
| privateAsyncVariablesLock.unlock() |
| |
| #if os(Windows) |
| guard CloseHandle(_handle) else { |
| throw _NSErrorWithWindowsError(GetLastError(), reading: true) |
| } |
| _handle = INVALID_HANDLE_VALUE |
| #else |
| guard _close(_fd) >= 0 else { |
| throw _NSErrorWithErrno(errno, reading: true) |
| } |
| _fd = -1 |
| #endif |
| } |
| |
| // MARK: - |
| // MARK: To-be-deprecated API. |
| |
| // This matches the effect of API_TO_BE_DEPRECATED in ObjC headers: |
| @available(swift, deprecated: 100000, renamed: "readToEnd()") |
| open func readDataToEndOfFile() -> Data { |
| return try! readToEnd() ?? Data() |
| } |
| |
| @available(swift, deprecated: 100000, renamed: "read(upToCount:)") |
| open func readData(ofLength length: Int) -> Data { |
| return try! read(upToCount: length) ?? Data() |
| } |
| |
| @available(swift, deprecated: 100000, renamed: "write(contentsOf:)") |
| open func write(_ data: Data) { |
| try! write(contentsOf: data) |
| } |
| |
| @available(swift, deprecated: 100000, renamed: "offset()") |
| open var offsetInFile: UInt64 { |
| return try! offset() |
| } |
| |
| @available(swift, deprecated: 100000, renamed: "seekToEnd()") |
| @discardableResult |
| open func seekToEndOfFile() -> UInt64 { |
| return try! seekToEnd() |
| } |
| |
| @available(swift, deprecated: 100000, renamed: "seek(toOffset:)") |
| open func seek(toFileOffset offset: UInt64) { |
| try! seek(toOffset: offset) |
| } |
| |
| @available(swift, deprecated: 100000, renamed: "truncate(toOffset:)") |
| open func truncateFile(atOffset offset: UInt64) { |
| try! truncate(toOffset: offset) |
| } |
| |
| @available(swift, deprecated: 100000, renamed: "synchronize()") |
| open func synchronizeFile() { |
| try! synchronize() |
| } |
| |
| @available(swift, deprecated: 100000, renamed: "close()") |
| open func closeFile() { |
| try! self.close() |
| } |
| } |
| |
| extension FileHandle { |
| |
| internal static var _stdinFileHandle: FileHandle = { |
| return FileHandle(fileDescriptor: STDIN_FILENO, closeOnDealloc: false) |
| }() |
| |
| open class var standardInput: FileHandle { |
| return _stdinFileHandle |
| } |
| |
| internal static var _stdoutFileHandle: FileHandle = { |
| return FileHandle(fileDescriptor: STDOUT_FILENO, closeOnDealloc: false) |
| }() |
| |
| open class var standardOutput: FileHandle { |
| return _stdoutFileHandle |
| } |
| |
| internal static var _stderrFileHandle: FileHandle = { |
| return FileHandle(fileDescriptor: STDERR_FILENO, closeOnDealloc: false) |
| }() |
| |
| open class var standardError: FileHandle { |
| return _stderrFileHandle |
| } |
| |
| internal static var _nulldeviceFileHandle: FileHandle = { |
| class NullDevice: FileHandle { |
| override var availableData: Data { |
| return Data() |
| } |
| |
| override func readDataToEndOfFile() -> Data { |
| return Data() |
| } |
| |
| override func readData(ofLength length: Int) -> Data { |
| return Data() |
| } |
| |
| override func write(_ data: Data) {} |
| |
| override var offsetInFile: UInt64 { |
| return 0 |
| } |
| |
| override func seekToEndOfFile() -> UInt64 { |
| return 0 |
| } |
| |
| override func seek(toFileOffset offset: UInt64) {} |
| |
| override func truncateFile(atOffset offset: UInt64) {} |
| |
| override func synchronizeFile() {} |
| |
| override func closeFile() {} |
| |
| deinit {} |
| } |
| |
| #if os(Windows) |
| return NullDevice(handle: INVALID_HANDLE_VALUE, closeOnDealloc: false) |
| #else |
| return NullDevice(fileDescriptor: -1, closeOnDealloc: false) |
| #endif |
| }() |
| |
| open class var nullDevice: FileHandle { |
| return _nulldeviceFileHandle |
| } |
| |
| public convenience init?(forReadingAtPath path: String) { |
| self.init(path: path, flags: O_RDONLY, createMode: 0) |
| } |
| |
| public convenience init?(forWritingAtPath path: String) { |
| self.init(path: path, flags: O_WRONLY, createMode: 0) |
| } |
| |
| public convenience init?(forUpdatingAtPath path: String) { |
| self.init(path: path, flags: O_RDWR, createMode: 0) |
| } |
| |
| internal static func _openFileDescriptorForURL(_ url : URL, flags: Int32, reading: Bool) throws -> Int32 { |
| let fd = url.withUnsafeFileSystemRepresentation( { (fsRep) -> Int32 in |
| guard let fsRep = fsRep else { return -1 } |
| return _CFOpenFile(fsRep, flags) |
| }) |
| if fd < 0 { |
| throw _NSErrorWithErrno(errno, reading: reading, url: url) |
| } |
| return fd |
| } |
| |
| public convenience init(forReadingFrom url: URL) throws { |
| let fd = try FileHandle._openFileDescriptorForURL(url, flags: O_RDONLY, reading: true) |
| self.init(fileDescriptor: fd, closeOnDealloc: true) |
| } |
| |
| public convenience init(forWritingTo url: URL) throws { |
| let fd = try FileHandle._openFileDescriptorForURL(url, flags: O_WRONLY, reading: false) |
| self.init(fileDescriptor: fd, closeOnDealloc: true) |
| } |
| |
| public convenience init(forUpdating url: URL) throws { |
| let fd = try FileHandle._openFileDescriptorForURL(url, flags: O_RDWR, reading: false) |
| self.init(fileDescriptor: fd, closeOnDealloc: true) |
| } |
| } |
| |
| extension NSExceptionName { |
| public static let fileHandleOperationException = NSExceptionName(rawValue: "NSFileHandleOperationException") |
| } |
| |
| extension Notification.Name { |
| public static let NSFileHandleReadToEndOfFileCompletion = Notification.Name(rawValue: "NSFileHandleReadToEndOfFileCompletionNotification") |
| public static let NSFileHandleConnectionAccepted = Notification.Name(rawValue: "NSFileHandleConnectionAcceptedNotification") |
| public static let NSFileHandleDataAvailable = Notification.Name(rawValue: "NSFileHandleDataAvailableNotification") |
| } |
| |
| extension FileHandle { |
| public static let readCompletionNotification = Notification.Name(rawValue: "NSFileHandleReadCompletionNotification") |
| } |
| |
| public let NSFileHandleNotificationDataItem: String = "NSFileHandleNotificationDataItem" |
| public let NSFileHandleNotificationFileHandleItem: String = "NSFileHandleNotificationFileHandleItem" |
| |
| extension FileHandle { |
| open func readInBackgroundAndNotify() { |
| readInBackgroundAndNotify(forModes: [.default]) |
| } |
| |
| open func readInBackgroundAndNotify(forModes modes: [RunLoop.Mode]?) { |
| _checkFileHandle() |
| |
| privateAsyncVariablesLock.lock() |
| guard currentBackgroundActivityOwner == nil else { fatalError("No two activities can occur at the same time") } |
| let token = NSObject() |
| currentBackgroundActivityOwner = token |
| privateAsyncVariablesLock.unlock() |
| |
| let operation = { (_ data: DispatchData, _ error: Int32) in |
| self.privateAsyncVariablesLock.lock() |
| if self.currentBackgroundActivityOwner === token { |
| self.currentBackgroundActivityOwner = nil |
| } |
| self.privateAsyncVariablesLock.unlock() |
| |
| var userInfo: [String: Any] = [:] |
| if error == 0 { |
| userInfo[NSFileHandleNotificationDataItem] = Data(data) |
| } else { |
| #if os(Windows) |
| // On Windows, reading from a directory results in an |
| // ERROR_ACCESS_DENIED. If we get ERROR_ACCESS_DENIED |
| // and the handle we attempt to read from is a |
| // directory, replace it with |
| // ERROR_DIRECTORY_NOT_SUPPORTED to match POSIX's EISDIR |
| var translatedError = error |
| if error == ERROR_ACCESS_DENIED { |
| var fileInfo = BY_HANDLE_FILE_INFORMATION() |
| GetFileInformationByHandle(self.handle, &fileInfo) |
| if fileInfo.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY) == DWORD(FILE_ATTRIBUTE_DIRECTORY) { |
| translatedError = ERROR_DIRECTORY_NOT_SUPPORTED |
| } |
| } |
| userInfo["NSFileHandleError"] = Int(translatedError) |
| #else |
| userInfo["NSFileHandleError"] = Int(error) |
| #endif |
| } |
| |
| DispatchQueue.main.async { |
| NotificationQueue.default.enqueue(Notification(name: FileHandle.readCompletionNotification, object: self, userInfo: userInfo), postingStyle: .asap, coalesceMask: .none, forModes: modes) |
| } |
| } |
| |
| #if os(Windows) |
| DispatchIO.read(fromHandle: handle, maxLength: 1024 * 1024, runningHandlerOn: queue) { (data, error) in |
| operation(data, error) |
| } |
| #else |
| DispatchIO.read(fromFileDescriptor: fileDescriptor, maxLength: 1024 * 1024, runningHandlerOn: queue) { (data, error) in |
| operation(data, error) |
| } |
| #endif |
| } |
| |
| open func readToEndOfFileInBackgroundAndNotify() { |
| readToEndOfFileInBackgroundAndNotify(forModes: [.default]) |
| } |
| |
| open func readToEndOfFileInBackgroundAndNotify(forModes modes: [RunLoop.Mode]?) { |
| privateAsyncVariablesLock.lock() |
| guard currentBackgroundActivityOwner == nil else { fatalError("No two activities can occur at the same time") } |
| |
| let token = NSObject() |
| currentBackgroundActivityOwner = token |
| privateAsyncVariablesLock.unlock() |
| |
| queue.async { |
| let data: Data? |
| let error: Int? |
| |
| do { |
| data = try self.readToEnd() |
| error = nil |
| } catch let thrown { |
| data = nil |
| if let thrown = thrown as? NSError { |
| error = thrown.errnoIfAvailable |
| } else { |
| error = nil |
| } |
| } |
| |
| DispatchQueue.main.async { |
| self.privateAsyncVariablesLock.lock() |
| if token === self.currentBackgroundActivityOwner { |
| self.currentBackgroundActivityOwner = nil |
| } |
| self.privateAsyncVariablesLock.unlock() |
| |
| var userInfo: [String: Any] = [:] |
| if let data = data { |
| userInfo[NSFileHandleNotificationDataItem] = data |
| } |
| if let error = error { |
| userInfo["NSFileHandleError"] = error |
| } |
| |
| NotificationQueue.default.enqueue(Notification(name: .NSFileHandleReadToEndOfFileCompletion, object: self, userInfo: userInfo), postingStyle: .asap, coalesceMask: .none, forModes: modes) |
| } |
| } |
| } |
| |
| open func acceptConnectionInBackgroundAndNotify() { |
| acceptConnectionInBackgroundAndNotify(forModes: [.default]) |
| } |
| |
| @available(Windows, unavailable, message: "A SOCKET cannot be treated as a fd") |
| open func acceptConnectionInBackgroundAndNotify(forModes modes: [RunLoop.Mode]?) { |
| #if os(Windows) |
| NSUnsupported() |
| #else |
| let owner = monitor(forReading: true, resumed: false) { (handle, source) in |
| var notification = Notification(name: .NSFileHandleConnectionAccepted, object: handle, userInfo: [:]) |
| let userInfo: [AnyHashable : Any] |
| |
| let acceptedFD = accept(handle.fileDescriptor, nil, nil) |
| if acceptedFD >= 0 { |
| userInfo = [NSFileHandleNotificationFileHandleItem: FileHandle(fileDescriptor: acceptedFD)] |
| } else { |
| userInfo = ["NSFileHandleError": NSNumber(value: errno)] |
| } |
| notification.userInfo = userInfo |
| |
| DispatchQueue.main.async { |
| handle.privateAsyncVariablesLock.lock() |
| handle.currentBackgroundActivityOwner = nil |
| handle.privateAsyncVariablesLock.unlock() |
| |
| NotificationQueue.default.enqueue(Notification(name: .NSFileHandleConnectionAccepted, object: handle, userInfo: [:]), postingStyle: .asap, coalesceMask: .none, forModes: modes) |
| } |
| } |
| |
| privateAsyncVariablesLock.lock() |
| guard currentBackgroundActivityOwner == nil else { fatalError("No two activities can occur at the same time") } |
| currentBackgroundActivityOwner = owner as AnyObject |
| privateAsyncVariablesLock.unlock() |
| |
| owner.resume() |
| #endif |
| } |
| |
| open func waitForDataInBackgroundAndNotify() { |
| waitForDataInBackgroundAndNotify(forModes: [.default]) |
| } |
| |
| open func waitForDataInBackgroundAndNotify(forModes modes: [RunLoop.Mode]?) { |
| let owner = monitor(forReading: true, resumed: false) { (handle, source) in |
| source.cancel() |
| DispatchQueue.main.async { |
| handle.privateAsyncVariablesLock.lock() |
| handle.currentBackgroundActivityOwner = nil |
| handle.privateAsyncVariablesLock.unlock() |
| |
| NotificationQueue.default.enqueue(Notification(name: .NSFileHandleDataAvailable, object: handle, userInfo: [:]), postingStyle: .asap, coalesceMask: .none, forModes: modes) |
| } |
| } |
| |
| privateAsyncVariablesLock.lock() |
| guard currentBackgroundActivityOwner == nil else { fatalError("No two activities can occur at the same time") } |
| currentBackgroundActivityOwner = owner as AnyObject |
| privateAsyncVariablesLock.unlock() |
| |
| owner.resume() |
| } |
| } |
| |
| open class Pipe: NSObject { |
| public let fileHandleForReading: FileHandle |
| public let fileHandleForWriting: FileHandle |
| |
| public override init() { |
| #if os(Windows) |
| var saAttr: SECURITY_ATTRIBUTES = SECURITY_ATTRIBUTES(nLength: DWORD(MemoryLayout<SECURITY_ATTRIBUTES>.size), lpSecurityDescriptor: nil, bInheritHandle: true) |
| |
| var hReadPipe: HANDLE? |
| var hWritePipe: HANDLE? |
| |
| if !CreatePipe(&hReadPipe, &hWritePipe, &saAttr, 0) { |
| fatalError("CreatePipe failed") |
| } |
| self.fileHandleForReading = FileHandle(handle: hReadPipe!, |
| closeOnDealloc: true) |
| self.fileHandleForWriting = FileHandle(handle: hWritePipe!, |
| closeOnDealloc: true) |
| #else |
| /// the `pipe` system call creates two `fd` in a malloc'ed area |
| var fds = UnsafeMutablePointer<Int32>.allocate(capacity: 2) |
| defer { |
| fds.deallocate() |
| } |
| /// If the operating system prevents us from creating file handles, stop |
| let ret = pipe(fds) |
| switch (ret, errno) { |
| case (0, _): |
| self.fileHandleForReading = FileHandle(fileDescriptor: fds.pointee, closeOnDealloc: true) |
| self.fileHandleForWriting = FileHandle(fileDescriptor: fds.successor().pointee, closeOnDealloc: true) |
| |
| case (-1, EMFILE), (-1, ENFILE): |
| // Unfortunately this initializer does not throw and isn't failable so this is only |
| // way of handling this situation. |
| self.fileHandleForReading = FileHandle(fileDescriptor: -1, closeOnDealloc: false) |
| self.fileHandleForWriting = FileHandle(fileDescriptor: -1, closeOnDealloc: false) |
| |
| default: |
| fatalError("Error calling pipe(): \(errno)") |
| } |
| #endif |
| super.init() |
| } |
| } |
| |