File size: 1,974 Bytes
0dc8570 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import Foundation
struct ProcessResult {let status:Int32,output:String,error:String}
enum Subprocess {
static func run(_ executable:String,_ args:[String],input:Data?=nil,environment:[String:String]=[:],timeout:Double=240) async throws -> ProcessResult {
try await withCheckedThrowingContinuation { continuation in
DispatchQueue.global(qos:.userInitiated).async {
let fm=FileManager.default;let dir=fm.temporaryDirectory.appendingPathComponent(UUID().uuidString)
do {
try fm.createDirectory(at:dir,withIntermediateDirectories:true,attributes:[.posixPermissions:0o700]);defer{try? fm.removeItem(at:dir)}
let out=dir.appendingPathComponent("out"),err=dir.appendingPathComponent("err")
fm.createFile(atPath:out.path,contents:nil,attributes:[.posixPermissions:0o600]);fm.createFile(atPath:err.path,contents:nil,attributes:[.posixPermissions:0o600])
let oh=try FileHandle(forWritingTo:out),eh=try FileHandle(forWritingTo:err);defer{try? oh.close();try? eh.close()}
let p=Process();p.executableURL=URL(fileURLWithPath:executable);p.arguments=args;p.standardOutput=oh;p.standardError=eh
var env=ProcessInfo.processInfo.environment;environment.forEach{env[$0]=$1};p.environment=env
let pipe=Pipe();p.standardInput=pipe
try p.run()
let watchdog=DispatchWorkItem { if p.isRunning {p.terminate();DispatchQueue.global().asyncAfter(deadline:.now()+3){if p.isRunning {kill(p.processIdentifier,SIGKILL)}}} }
DispatchQueue.global().asyncAfter(deadline:.now()+timeout,execute:watchdog)
if let input {try pipe.fileHandleForWriting.write(contentsOf:input)}
try pipe.fileHandleForWriting.close();p.waitUntilExit();watchdog.cancel()
let result=ProcessResult(status:p.terminationStatus,output:String(decoding:try Data(contentsOf:out),as:UTF8.self),error:String(decoding:try Data(contentsOf:err),as:UTF8.self))
continuation.resume(returning:result)
} catch {continuation.resume(throwing:error)}
}
}
}
}
|