Skip to content

Instantly share code, notes, and snippets.

@finestructure
Created August 2, 2016 09:26
Show Gist options
  • Save finestructure/eff754257652cec27aba63e7c556dc70 to your computer and use it in GitHub Desktop.
Save finestructure/eff754257652cec27aba63e7c556dc70 to your computer and use it in GitHub Desktop.
posix_spawn based system call (swift 3.0-p1-ish, WIP)
/*
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 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: " ")
}
//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