Skip to content

PrivateLibcExtras: Port to Windows #20962

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 2 commits into from
Dec 19, 2018
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
1 change: 1 addition & 0 deletions stdlib/private/SwiftPrivateLibcExtras/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ add_swift_target_library(swiftSwiftPrivateLibcExtras ${SWIFT_STDLIB_LIBRARY_BUIL
SWIFT_MODULE_DEPENDS_FREEBSD Glibc
SWIFT_MODULE_DEPENDS_CYGWIN Glibc
SWIFT_MODULE_DEPENDS_HAIKU Glibc
SWIFT_MODULE_DEPENDS_WINDOWS MSVCRT WinSDK
INSTALL_IN_COMPONENT stdlib-experimental)
153 changes: 124 additions & 29 deletions stdlib/private/SwiftPrivateLibcExtras/Subprocess.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,133 @@ import SwiftPrivate
import Darwin
#elseif os(Linux) || os(FreeBSD) || os(PS4) || os(Android) || os(Cygwin) || os(Haiku)
import Glibc
#elseif os(Windows)
import MSVCRT
import WinSDK
#endif
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't it make most sense to structure this like:

#if canImport(Darwin)
import Darwin
#elseif os(Windows)
import MSVCRT
import WinSDK
#elseif canImport(Glibc)
import Glibc
#endif

Then things work automatically for other POSIX platforms which probably use Glibc (NetBSD, OpenBSD, etc.?) without needing to explicitly update the list every time someone adds a new OS to Swift.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is why I had proposed having a C module which hides this mess. In any case, I suppose that I can change that, but this pattern is prevalent through out the code base. I think it makes sense for someone to go through and catch them all later.

Copy link
Contributor

Choose a reason for hiding this comment

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

We've talked about such a module before and the primary problem is what to name it. :-) I think there'd be support on -evolution.



internal func _signalToString(_ signal: Int) -> String {
switch CInt(signal) {
case SIGILL: return "SIGILL"
case SIGABRT: return "SIGABRT"
case SIGFPE: return "SIGFPE"
case SIGSEGV: return "SIGSEGV"
#if !os(Windows)
// posix_spawn is not available on Windows.
case SIGTRAP: return "SIGTRAP"
case SIGBUS: return "SIGBUS"
case SIGSYS: return "SIGSYS"
#endif
default: return "SIG???? (\(signal))"
}
}

public enum ProcessTerminationStatus : CustomStringConvertible {
case exit(Int)
case signal(Int)

public var description: String {
switch self {
case .exit(let status):
return "Exit(\(status))"
case .signal(let signal):
return "Signal(\(_signalToString(signal)))"
}
}
}


#if os(Windows)
public func spawnChild(_ args: [String])
-> (process: HANDLE, stdin: HANDLE, stdout: HANDLE, stderr: HANDLE) {
var _stdin: (read: HANDLE?, write: HANDLE?)
var _stdout: (read: HANDLE?, write: HANDLE?)
var _stderr: (read: HANDLE?, write: HANDLE?)

var saAttributes: SECURITY_ATTRIBUTES = SECURITY_ATTRIBUTES()
saAttributes.nLength = DWORD(MemoryLayout<SECURITY_ATTRIBUTES>.size)
saAttributes.bInheritHandle = TRUE
saAttributes.lpSecurityDescriptor = nil

if CreatePipe(&_stdin.read, &_stdin.write, &saAttributes, 0) == FALSE {
fatalError("CreatePipe() failed")
}
if SetHandleInformation(_stdin.write, HANDLE_FLAG_INHERIT, 0) == FALSE {
fatalError("SetHandleInformation() failed")
}

if CreatePipe(&_stdout.read, &_stdout.write, &saAttributes, 0) == FALSE {
fatalError("CreatePipe() failed")
}
if SetHandleInformation(_stdout.read, HANDLE_FLAG_INHERIT, 0) == FALSE {
fatalError("SetHandleInformation() failed")
}

if CreatePipe(&_stderr.read, &_stderr.write, &saAttributes, 0) == FALSE {
fatalError("CreatePipe() failed")
}
if SetHandleInformation(_stderr.read, HANDLE_FLAG_INHERIT, 0) == FALSE {
fatalError("SetHandleInformation() failed")
}

var siStartupInfo: STARTUPINFOW = STARTUPINFOW()
siStartupInfo.cb = DWORD(MemoryLayout<STARTUPINFOW>.size)
siStartupInfo.hStdError = _stderr.write
siStartupInfo.hStdOutput = _stdout.write
siStartupInfo.hStdInput = _stdin.read
siStartupInfo.dwFlags |= STARTF_USESTDHANDLES

var piProcessInfo: PROCESS_INFORMATION = PROCESS_INFORMATION()

// TODO(compnerd): properly quote the command line being invoked here. See
// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
// for more details on how to properly quote the command line for Windows.
let command: String =
([CommandLine.arguments[0]] + args).joined(separator: " ")
command.withCString(encodedAs: UTF16.self) { cString in
if CreateProcessW(nil, UnsafeMutablePointer<WCHAR>(mutating: cString),
nil, nil, TRUE, 0, nil, nil,
&siStartupInfo, &piProcessInfo) == FALSE {
let dwError: DWORD = GetLastError()
fatalError("CreateProcessW() failed \(dwError)")
}
}

if CloseHandle(_stdin.read) == FALSE {
fatalError("CloseHandle() failed")
}
if CloseHandle(_stdout.write) == FALSE {
fatalError("CloseHandle() failed")
}
if CloseHandle(_stderr.write) == FALSE {
fatalError("CloseHandle() failed")
}

// CloseHandle(piProcessInfo.hProcess)
CloseHandle(piProcessInfo.hThread)

return (piProcessInfo.hProcess,
_stdin.write ?? INVALID_HANDLE_VALUE,
_stdout.read ?? INVALID_HANDLE_VALUE,
_stderr.read ?? INVALID_HANDLE_VALUE)
}

public func waitProcess(_ process: HANDLE) -> ProcessTerminationStatus {
let result = WaitForSingleObject(process, INFINITE)
if result != WAIT_OBJECT_0 {
fatalError("WaitForSingleObject() failed")
}

var status: DWORD = 0
if GetExitCodeProcess(process, &status) == FALSE {
fatalError("GetExitCodeProcess() failed")
}

if status & DWORD(0x80000000) == DWORD(0x80000000) {
return .signal(Int(status))
}
return .exit(Int(status))
}
#else
// posix_spawn is not available on Android.
// posix_spawn is not available on Haiku.
#if !os(Android) && !os(Haiku)
Expand Down Expand Up @@ -237,33 +359,6 @@ internal func _make_posix_spawn_file_actions_t()
#endif
#endif

internal func _signalToString(_ signal: Int) -> String {
switch CInt(signal) {
case SIGILL: return "SIGILL"
case SIGTRAP: return "SIGTRAP"
case SIGABRT: return "SIGABRT"
case SIGFPE: return "SIGFPE"
case SIGBUS: return "SIGBUS"
case SIGSEGV: return "SIGSEGV"
case SIGSYS: return "SIGSYS"
default: return "SIG???? (\(signal))"
}
}

public enum ProcessTerminationStatus : CustomStringConvertible {
case exit(Int)
case signal(Int)

public var description: String {
switch self {
case .exit(let status):
return "Exit(\(status))"
case .signal(let signal):
return "Signal(\(_signalToString(signal)))"
}
}
}

public func posixWaitpid(_ pid: pid_t) -> ProcessTerminationStatus {
var status: CInt = 0
#if os(Cygwin)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ import Darwin
#elseif os(Linux) || os(FreeBSD) || os(PS4) || os(Android) || os(Cygwin) || os(Haiku)
import Glibc
#elseif os(Windows)
import ucrt
import MSVCRT
#endif

#if !os(Windows)
public func _stdlib_mkstemps(_ template: inout String, _ suffixlen: CInt) -> CInt {
#if os(Android) || os(Haiku)
#if os(Android) || os(Haiku) || os(Windows)
preconditionFailure("mkstemps doesn't work on your platform")
#else
var utf8CStr = template.utf8CString
Expand All @@ -35,8 +34,8 @@ public func _stdlib_mkstemps(_ template: inout String, _ suffixlen: CInt) -> CIn
return fd
#endif
}
#endif

#if !os(Windows)
public var _stdlib_FD_SETSIZE: CInt {
return 1024
}
Expand Down Expand Up @@ -85,7 +84,6 @@ public struct _stdlib_fd_set {
}
}

#if !os(Windows)
public func _stdlib_select(
_ readfds: inout _stdlib_fd_set, _ writefds: inout _stdlib_fd_set,
_ errorfds: inout _stdlib_fd_set, _ timeout: UnsafeMutablePointer<timeval>?
Expand Down Expand Up @@ -135,6 +133,7 @@ public func _stdlib_pipe() -> (readEnd: CInt, writeEnd: CInt, error: CInt) {
}


#if !os(Windows)
//
// Functions missing in `Darwin` module.
//
Expand All @@ -161,3 +160,5 @@ public func WEXITSTATUS(_ status: CInt) -> CInt {
public func WTERMSIG(_ status: CInt) -> CInt {
return _WSTATUS(status)
}
#endif

24 changes: 24 additions & 0 deletions stdlib/public/Platform/winsdk.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,36 @@

module WinSDK [system] [extern_c] {
module core {
// api-ms-win-core-errhandling-l1-1-0.dll
module errhandling {
header "errhandlingapi.h"
export *
}

// api-ms-win-core-file-l1-1-0.dll
module file {
header "fileapi.h"
export *
}

// api-ms-win-core-handle-l1-1-0.dll
module handle {
header "handleapi.h"
export *
}

// api-ms-win-core-libloader-l1-1-0.dll
module libloader {
header "libloaderapi.h"
export *
}

// api-ms-win-core-namedpipe-l1-1-2-0.dll
module namedpipe {
header "namedpipeapi.h"
export *
}

// api-ms-win-core-processthreads-l1-1-2.dll
module processthreads {
header "processthreadsapi.h"
Expand Down