//===--- Subprocess.swift -------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 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 the list of Swift project authors // //===----------------------------------------------------------------------===// import SwiftPrivate #if os(OSX) || os(iOS) || os(watchOS) || os(tvOS) import Darwin #elseif os(Linux) || os(FreeBSD) import Glibc #endif // FIXME: Come up with a better way to deal with APIs that are pointers on some // platforms but not others. #if os(Linux) typealias swift_posix_spawn_file_actions_t = posix_spawn_file_actions_t #else typealias swift_posix_spawn_file_actions_t = posix_spawn_file_actions_t? #endif // posix_spawn isn't available in the public watchOS SDK, we sneak by the // unavailable attribute declaration here of the APIs that we need. @_silgen_name("posix_spawn_file_actions_init") func swift_posix_spawn_file_actions_init( _ file_actions: UnsafeMutablePointer ) -> CInt @_silgen_name("posix_spawn_file_actions_destroy") func swift_posix_spawn_file_actions_destroy( _ file_actions: UnsafeMutablePointer ) -> CInt @_silgen_name("posix_spawn_file_actions_addclose") func swift_posix_spawn_file_actions_addclose( _ file_actions: UnsafeMutablePointer, _ filedes: CInt) -> CInt @_silgen_name("posix_spawn_file_actions_adddup2") func swift_posix_spawn_file_actions_adddup2( _ file_actions: UnsafeMutablePointer, _ filedes: CInt, _ newfiledes: CInt) -> CInt @_silgen_name("posix_spawn") func swift_posix_spawn( _ pid: UnsafeMutablePointer?, _ file: UnsafePointer, _ file_actions: UnsafePointer?, _ attrp: UnsafePointer?, _ argv: UnsafePointer?>, _ envp: UnsafePointer?>?) -> CInt /// Calls POSIX `pipe()`. func posixPipe() -> (readFD: CInt, writeFD: CInt) { var fds: [CInt] = [ -1, -1 ] if pipe(&fds) != 0 { preconditionFailure("pipe() failed") } return (fds[0], fds[1]) } /// Start the same executable as a child process, redirecting its stdout and /// stderr. public func spawnChild(_ args: [String]) -> (pid: pid_t, stdinFD: CInt, stdoutFD: CInt, stderrFD: CInt) { var fileActions = _make_posix_spawn_file_actions_t() if swift_posix_spawn_file_actions_init(&fileActions) != 0 { preconditionFailure("swift_posix_spawn_file_actions_init() failed") } let childStdin = posixPipe() // Close the write end of the pipe on the child side. if swift_posix_spawn_file_actions_addclose( &fileActions, childStdin.writeFD) != 0 { preconditionFailure("swift_posix_spawn_file_actions_addclose() failed") } // Remap child's stdin. if swift_posix_spawn_file_actions_adddup2( &fileActions, childStdin.readFD, STDIN_FILENO) != 0 { preconditionFailure("swift_posix_spawn_file_actions_adddup2() failed") } let childStdout = posixPipe() // Close the read end of the pipe on the child side. if swift_posix_spawn_file_actions_addclose( &fileActions, childStdout.readFD) != 0 { preconditionFailure("swift_posix_spawn_file_actions_addclose() failed") } // Remap child's stdout. if swift_posix_spawn_file_actions_adddup2( &fileActions, childStdout.writeFD, STDOUT_FILENO) != 0 { preconditionFailure("swift_posix_spawn_file_actions_adddup2() failed") } let childStderr = posixPipe() // Close the read end of the pipe on the child side. if swift_posix_spawn_file_actions_addclose( &fileActions, childStderr.readFD) != 0 { preconditionFailure("swift_posix_spawn_file_actions_addclose() failed") } // Remap child's stderr. if swift_posix_spawn_file_actions_adddup2( &fileActions, childStderr.writeFD, STDERR_FILENO) != 0 { preconditionFailure("swift_posix_spawn_file_actions_adddup2() failed") } var pid: pid_t = -1 var childArgs = args childArgs.insert(Process.arguments[0], at: 0) let interpreter = getenv("SWIFT_INTERPRETER") if interpreter != nil { if let invocation = String(validatingUTF8: interpreter) { childArgs.insert(invocation, at: 0) } } let spawnResult = withArrayOfCStrings(childArgs) { swift_posix_spawn( &pid, childArgs[0], &fileActions, nil, $0, _getEnviron()) } if spawnResult != 0 { print(String(cString: strerror(spawnResult))) preconditionFailure("swift_posix_spawn() failed") } if swift_posix_spawn_file_actions_destroy(&fileActions) != 0 { preconditionFailure("swift_posix_spawn_file_actions_destroy() failed") } // Close the read end of the pipe on the parent side. if close(childStdin.readFD) != 0 { preconditionFailure("close() failed") } // Close the write end of the pipe on the parent side. if close(childStdout.writeFD) != 0 { preconditionFailure("close() failed") } // Close the write end of the pipe on the parent side. if close(childStderr.writeFD) != 0 { preconditionFailure("close() failed") } return (pid, childStdin.writeFD, childStdout.readFD, childStderr.readFD) } #if os(Linux) internal func _make_posix_spawn_file_actions_t() -> swift_posix_spawn_file_actions_t { return posix_spawn_file_actions_t() } #else internal func _make_posix_spawn_file_actions_t() -> swift_posix_spawn_file_actions_t { return nil } #endif internal func _readAll(_ fd: CInt) -> String { var buffer = [UInt8](repeating: 0, count: 1024) var usedBytes = 0 while true { let readResult: ssize_t = buffer.withUnsafeMutableBufferPointer { (buffer) in let ptr = UnsafeMutablePointer(buffer.baseAddress! + usedBytes) return read(fd, ptr, size_t(buffer.count - usedBytes)) } if readResult > 0 { usedBytes += readResult continue } if readResult == 0 { break } preconditionFailure("read() failed") } return String._fromCodeUnitSequenceWithRepair( UTF8.self, input: buffer[0.. 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 waitpid(pid, &status, 0) < 0 { preconditionFailure("waitpid() failed") } if (WIFEXITED(status)) { return .exit(Int(WEXITSTATUS(status))) } if (WIFSIGNALED(status)) { return .signal(Int(WTERMSIG(status))) } preconditionFailure("did not understand what happened to child process") } public func runChild(_ args: [String]) -> (stdout: String, stderr: String, status: ProcessTerminationStatus) { let (pid, _, stdoutFD, stderrFD) = spawnChild(args) // FIXME: reading stdout and stderr sequentially can block. Should use // select(). This is not so simple to implement because of: // Darwin module is missing fd_set-related macros let stdout = _readAll(stdoutFD) let stderr = _readAll(stderrFD) if close(stdoutFD) != 0 { preconditionFailure("close() failed") } if close(stderrFD) != 0 { preconditionFailure("close() failed") } let status = posixWaitpid(pid) return (stdout, stderr, status) } #if os(OSX) || os(iOS) || os(watchOS) || os(tvOS) @_silgen_name("_NSGetEnviron") func _NSGetEnviron() -> UnsafeMutablePointer?>> #endif internal func _getEnviron() -> UnsafeMutablePointer?> { #if os(OSX) || os(iOS) || os(watchOS) || os(tvOS) return _NSGetEnviron().pointee #elseif os(FreeBSD) return environ; #else return __environ #endif }