Created
August 2, 2016 09:26
-
-
Save finestructure/eff754257652cec27aba63e7c556dc70 to your computer and use it in GitHub Desktop.
posix_spawn based system call (swift 3.0-p1-ish, WIP)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This source file is part of the Swift.org open source project | |
Copyright 2015 - 2016 Apple Inc. and the Swift project authors | |
Licensed under Apache License v2.0 with Runtime Library Exception | |
See http://swift.org/LICENSE.txt for license information | |
See http://swift.org/CONTRIBUTORS.txt for Swift project authors | |
*/ | |
import Darwin.C | |
public enum SystemError: ErrorProtocol { | |
case chdir(Int32) | |
case close(Int32) | |
case dirfd(Int32, String) | |
case fgetc(Int32) | |
case fread(Int32) | |
case getcwd(Int32) | |
case mkdtemp(Int32) | |
case opendir(Int32, String) | |
case pipe(Int32) | |
case popen(Int32, String) | |
case posix_spawn(Int32, [String]) | |
case read(Int32) | |
case readdir(Int32, String) | |
case realpath(Int32, String) | |
case rename(Int32, old: String, new: String) | |
case stat(Int32, String) | |
case symlinkat(Int32, String) | |
case unlink(Int32, String) | |
case waitpid(Int32) | |
} | |
extension SystemError: CustomStringConvertible { | |
public var description: String { | |
func strerror(_ errno: Int32) -> String { | |
let cmsg = strerror(errno) | |
let msg = String(validatingUTF8: cmsg) ?? "Unknown Error" | |
return "\(msg) (\(errno))" | |
} | |
switch self { | |
case .chdir(let errno): | |
return "chdir error: \(strerror(errno))" | |
case .close(let errno): | |
return "close error: \(strerror(errno))" | |
case .dirfd(let errno, _): | |
return "dirfd error: \(strerror(errno))" | |
case .fgetc(let errno): | |
return "fgetc error: \(strerror(errno))" | |
case .fread(let errno): | |
return "fread error: \(strerror(errno))" | |
case .getcwd(let errno): | |
return "getcwd error: \(strerror(errno))" | |
case .mkdtemp(let errno): | |
return "mkdtemp error: \(strerror(errno))" | |
case .opendir(let errno, _): | |
return "opendir error: \(strerror(errno))" | |
case .pipe(let errno): | |
return "pipe error: \(strerror(errno))" | |
case .posix_spawn(let errno, let args): | |
return "posix_spawn error: \(strerror(errno)), `\(args)`" | |
case .popen(let errno, _): | |
return "popen error: \(strerror(errno))" | |
case .read(let errno): | |
return "read error: \(strerror(errno))" | |
case .readdir(let errno, _): | |
return "readdir error: \(strerror(errno))" | |
case .realpath(let errno, let path): | |
return "realpath error: \(strerror(errno)): \(path)" | |
case .rename(let errno, let old, let new): | |
return "rename error: \(strerror(errno)): \(old) -> \(new)" | |
case .stat(let errno, _): | |
return "stat error: \(strerror(errno))" | |
case .symlinkat(let errno, _): | |
return "symlinkat error: \(strerror(errno))" | |
case .unlink(let errno, let path): | |
return "unlink error: \(strerror(errno)): \(path)" | |
case .waitpid(let errno): | |
return "waitpid error: \(strerror(errno))" | |
} | |
} | |
} | |
public enum Error: ErrorProtocol { | |
case exitStatus(Int32, [String]) | |
case exitSignal | |
} | |
public enum ShellError: ErrorProtocol { | |
case system(arguments: [String], SystemError) | |
case popen(arguments: [String], SystemError) | |
} | |
extension Error: CustomStringConvertible { | |
public var description: String { | |
switch self { | |
case .exitStatus(let code, let args): | |
return "exit(\(code)): \(prettyArguments(args))" | |
case .exitSignal: | |
return "Child process exited with signal" | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This source file is part of the Swift.org open source project | |
Copyright 2015 - 2016 Apple Inc. and the Swift project authors | |
Licensed under Apache License v2.0 with Runtime Library Exception | |
See http://swift.org/LICENSE.txt for license information | |
See http://swift.org/CONTRIBUTORS.txt for Swift project authors | |
*/ | |
/** | |
For an array of String arguments that would be passed to system() or | |
popen(), returns a pretty-printed string that is user-readable and | |
could be typed into a Terminal to re-attempt execution. | |
*/ | |
public func prettyArguments(_ args: [String]) -> String { | |
return args.map { $0.characters.split(separator: " ").map(String.init).joined(separator: "\\ ") }.joined(separator: " ") | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//import Darwin.C | |
import func Darwin.C.fflush | |
import var Darwin.C.stdout | |
import func Darwin.C.posix_spawnp | |
import func Darwin.C.waitpid | |
import struct Darwin.C.pid_t | |
import func Darwin.C.strdup | |
import func Darwin.C.free | |
import func Darwin.C.getenv | |
import var Darwin.C.errno | |
import struct Darwin.C.posix_spawn_file_actions_t | |
import var Darwin.C.EINTR | |
@available(*, unavailable) | |
public func system() {} | |
#if os(OSX) | |
typealias swiftpm_posix_spawn_file_actions_t = posix_spawn_file_actions_t? | |
#else | |
typealias swiftpm_posix_spawn_file_actions_t = posix_spawn_file_actions_t | |
#endif | |
/// Convenience wrapper for posix_spawn. | |
func posix_spawnp(_ path: String, args: [String], environment: [String: String] = [:], fileActions: swiftpm_posix_spawn_file_actions_t? = nil) throws -> pid_t { | |
let argv: [UnsafeMutablePointer<CChar>?] = args.map{ $0.withCString(strdup) } | |
defer { for case let arg? in argv { free(arg) } } | |
var environment = environment | |
#if Xcode | |
let keys = ["SWIFT_EXEC", "HOME", "PATH", "TOOLCHAINS", "DEVELOPER_DIR", "LLVM_PROFILE_FILE"] | |
#else | |
let keys = ["SWIFT_EXEC", "HOME", "PATH", "SDKROOT", "TOOLCHAINS", "DEVELOPER_DIR", "LLVM_PROFILE_FILE"] | |
#endif | |
for key in keys { | |
if environment[key] == nil { | |
let out = getenv(key) | |
environment[key] = (out == nil ? nil : String(validatingUTF8: out!)) | |
} | |
} | |
let env: [UnsafeMutablePointer<CChar>?] = environment.map{ "\($0.0)=\($0.1)".withCString(strdup) } | |
defer { for case let arg? in env { free(arg) } } | |
var pid = pid_t() | |
let rv: Int32 | |
// if var fileActions = fileActions { | |
var fa = fileActions | |
rv = posix_spawnp(&pid, argv[0], &fa, nil, argv + [nil], env + [nil]) | |
// } else { | |
// rv = posix_spawnp(&pid, argv[0], nil, nil, argv + [nil], env + [nil]) | |
// } | |
guard rv == 0 else { | |
throw SystemError.posix_spawn(rv, args) | |
} | |
return pid | |
} | |
private func _WSTATUS(_ status: CInt) -> CInt { | |
return status & 0x7f | |
} | |
private func WIFEXITED(_ status: CInt) -> Bool { | |
return _WSTATUS(status) == 0 | |
} | |
private func WEXITSTATUS(_ status: CInt) -> CInt { | |
return (status >> 8) & 0xff | |
} | |
/// convenience wrapper for waitpid | |
func waitpid(_ pid: pid_t) throws -> Int32 { | |
while true { | |
var exitStatus: Int32 = 0 | |
let rv = waitpid(pid, &exitStatus, 0) | |
if rv != -1 { | |
if WIFEXITED(exitStatus) { | |
return WEXITSTATUS(exitStatus) | |
} else { | |
throw Error.exitSignal | |
} | |
} else if errno == EINTR { | |
continue // see: man waitpid | |
} else { | |
throw SystemError.waitpid(errno) | |
} | |
} | |
} | |
public func system(_ arguments: [String], environment: [String:String] = [:]) throws { | |
// make sure subprocess output doesn't get interleaved with our own | |
fflush(stdout) | |
do { | |
let pid = try posix_spawnp(arguments[0], args: arguments, environment: environment) | |
let exitStatus = try waitpid(pid) | |
guard exitStatus == 0 else { throw Error.exitStatus(exitStatus, arguments) } | |
} catch let underlyingError as SystemError { | |
throw ShellError.system(arguments: arguments, underlyingError) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment