Skip to content

Foundation: port FileHandle to Windows #1848

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
Jan 30, 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
230 changes: 221 additions & 9 deletions Foundation/FileHandle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,21 @@
import CoreFoundation

open class FileHandle : NSObject, NSSecureCoding {
#if os(Windows)
private var _handle: HANDLE
#else
private var _fd: Int32
#endif
private var _closeOnDealloc: Bool

@available(windows, unavailable,
message: "cannot perform non-owning handle to fd conversion")
open var fileDescriptor: Int32 {
#if os(Windows)
return -1
#else
return _fd
#endif
}

open var readabilityHandler: ((FileHandle) -> Void)? = {
Expand Down Expand Up @@ -47,6 +57,80 @@ open class FileHandle : NSObject, NSSecureCoding {
}

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

if length == 0 && !untilEOF {
// Nothing requested, return empty response
return NSData.NSDataReadResult(bytes: nil, length: 0, deallocator: nil)
}

var fiFileInfo: BY_HANDLE_FILE_INFORMATION = BY_HANDLE_FILE_INFORMATION()
if GetFileInformationByHandle(_handle, &fiFileInfo) == FALSE {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(GetLastError()),
userInfo: nil)
}

if fiFileInfo.dwFileAttributes & DWORD(FILE_ATTRIBUTE_NORMAL) == FILE_ATTRIBUTE_NORMAL {
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) == FALSE {
fatalError("UnmapViewOfFile failed")
}
if CloseHandle(hMapping) == FALSE {
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) == FALSE {
free(buffer)
throw NSError(domain: NSPOSIXErrorDomain, code: Int(GetLastError()), userInfo: nil)
}
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
precondition(_fd >= 0, "Bad file descriptor")
if length == 0 && !untilEOF {
// Nothing requested, return empty response
Expand Down Expand Up @@ -114,9 +198,22 @@ open class FileHandle : NSObject, NSSecureCoding {
return NSData.NSDataReadResult(bytes: bytePtr, length: total) { buffer, length in
free(buffer)
}
#endif
}

open func write(_ data: Data) {
#if os(Windows)
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
data.enumerateBytes() { (bytes, range, stop) 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 }
data.enumerateBytes() { (bytes, range, stop) in
do {
Expand All @@ -125,44 +222,136 @@ open class FileHandle : NSObject, NSSecureCoding {
fatalError("Write failure")
}
}
#endif
}

// TODO: Error handling.

open var offsetInFile: UInt64 {
#if os(Windows)
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
var liPointer: LARGE_INTEGER = LARGE_INTEGER(QuadPart: 0)
if SetFilePointerEx(_handle, LARGE_INTEGER(QuadPart: 0),
&liPointer, DWORD(FILE_CURRENT)) == FALSE {
fatalError("SetFilePointerEx failed")
}
return UInt64(liPointer.QuadPart)
#else
precondition(_fd >= 0, "Bad file descriptor")
return UInt64(lseek(_fd, 0, SEEK_CUR))
#endif
}

@discardableResult
open func seekToEndOfFile() -> UInt64 {
#if os(Windows)
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
var liPointer: LARGE_INTEGER = LARGE_INTEGER(QuadPart: 0)
if SetFilePointerEx(_handle, LARGE_INTEGER(QuadPart: 0),
&liPointer, DWORD(FILE_END)) == FALSE {
fatalError("SetFilePointerEx failed")
}
return UInt64(liPointer.QuadPart)
#else
precondition(_fd >= 0, "Bad file descriptor")
return UInt64(lseek(_fd, 0, SEEK_END))
#endif
}

open func seek(toFileOffset offset: UInt64) {
#if os(Windows)
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
if SetFilePointerEx(_handle, LARGE_INTEGER(QuadPart: LONGLONG(offset)),
nil, DWORD(FILE_BEGIN)) == FALSE {
fatalError("SetFilePointerEx failed")
}
#else
precondition(_fd >= 0, "Bad file descriptor")
lseek(_fd, off_t(offset), SEEK_SET)
#endif
}

open func truncateFile(atOffset offset: UInt64) {
#if os(Windows)
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
if SetFilePointerEx(_handle, LARGE_INTEGER(QuadPart: LONGLONG(offset)),
nil, DWORD(FILE_BEGIN)) == FALSE {
fatalError("SetFilePointerEx failed")
}
if SetEndOfFile(_handle) == FALSE {
fatalError("SetEndOfFile failed")
}
#else
precondition(_fd >= 0, "Bad file descriptor")
if lseek(_fd, off_t(offset), SEEK_SET) < 0 { fatalError("lseek() failed.") }
if ftruncate(_fd, off_t(offset)) < 0 { fatalError("ftruncate() failed.") }
#endif
}

open func synchronizeFile() {
#if os(Windows)
precondition(_handle != INVALID_HANDLE_VALUE, "invalid file handle")
if FlushFileBuffers(_handle) == FALSE {
fatalError("FlushFileBuffers failed: \(GetLastError())")
}
#else
precondition(_fd >= 0, "Bad file descriptor")
fsync(_fd)
#endif
}

open func closeFile() {
#if os(Windows)
if _handle != INVALID_HANDLE_VALUE {
if CloseHandle(_handle) == FALSE {
fatalError("CloseHandle failed")
}
_handle = INVALID_HANDLE_VALUE
}
#else
if _fd >= 0 {
close(_fd)
_fd = -1
}
#endif
}

#if os(Windows)
public 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,
DWORD(DUPLICATE_SAME_ACCESS), FALSE, 0) == FALSE {
fatalError("DuplicateHandle() failed")
}
_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) {
self.init(fileDescriptor: _CFOpenFileWithMode(path, flags,
mode_t(createMode)),
closeOnDealloc: true)
if _handle == INVALID_HANDLE_VALUE { return nil }
}
#else
public init(fileDescriptor fd: Int32, closeOnDealloc closeopt: Bool) {
_fd = fd
_closeOnDealloc = closeopt
Expand All @@ -180,14 +369,25 @@ open class FileHandle : NSObject, NSSecureCoding {
return nil
}
}

#endif

deinit {
if _fd >= 0 && _closeOnDealloc {
guard _closeOnDealloc == true else { return }
#if os(Windows)
if _handle != INVALID_HANDLE_VALUE {
if CloseHandle(_handle) == FALSE {
fatalError("CloseHandle failed")
}
_handle = INVALID_HANDLE_VALUE
}
#else
if _fd >= 0 {
close(_fd)
_fd = -1
}
#endif
}

public required init?(coder: NSCoder) {
NSUnimplemented()
}
Expand Down Expand Up @@ -362,6 +562,17 @@ open class Pipe: NSObject {
public let fileHandleForWriting: FileHandle

public override init() {
#if os(Windows)
var hReadPipe: HANDLE?
var hWritePipe: HANDLE?
if CreatePipe(&hReadPipe, &hWritePipe, nil, 0) == FALSE {
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 {
Expand All @@ -383,6 +594,7 @@ open class Pipe: NSObject {
default:
fatalError("Error calling pipe(): \(errno)")
}
#endif
super.init()
}
}
4 changes: 4 additions & 0 deletions Foundation/NSSwiftRuntime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import CoreFoundation

@_exported import Dispatch

#if os(Windows)
import WinSDK
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did we end up on this? Will this cause people importing Foundation to also get WinSDK?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think that currently it does currently result in WinSDK getting pulled in, since we do not yet have the private import (I think that @jrose-apple was working on the that?). If there is a way to avoid that, I'm happy to try that out. I don't think that going with Shims for this is very good though because as we start using more interfaces across Windows, we will effectively end up re-creating the SDK.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably reasonable in the end; on Darwin it's not like you can't help but import standard Darwin headers if you import Foundation.

#endif

#if os(Android) // shim required for bzero
@_transparent func bzero(_ ptr: UnsafeMutableRawPointer, _ size: size_t) {
memset(ptr, 0, size)
Expand Down