Hi!
I'm looking for some insight and guidance on using the Foundation.Process
type with a PTY (Psuedo Terminal) so that the subprocess can accept input and behave as if it was running via a terminal.
The reason for needing a PTY is that for programs like ssh
or in my case (xcodes) which ask for user input including passwords, running these via Foundation.Process
does not display the prompts to the user as the output is usually buffered (this works fine in the Xcode debugger console but when running via a real terminal that is buffered the prompts are never displayed in the terminal)
Looking at other threads it seems like correct approach here is create a PTY and use the filehandles to attach to the Process
.
While I've got this to work to the point where prompts are now shown, I cant seem to figure out how to pass input back to the process as these are being controlled by the PTY.
Here is my Process
setup:
let process = Process()
// Setup the process with path, args, etc...
// Setup the PTY handles
var parentDescriptor: Int32 = 0
var childDescriptor: Int32 = 0
guard Darwin.openpty(&parentDescriptor, &childDescriptor, nil, nil, nil) != -1 else {
fatalError("Failed to spawn PTY")
}
parentHandle = FileHandle(fileDescriptor: parentDescriptor, closeOnDealloc: true)
childHandle = FileHandle(fileDescriptor: childDescriptor, closeOnDealloc: true)
process.standardInput = childHandle
process.standardOutput = childHandle
process.standardError = childHandle
With this setup I then read the parent handle and output any result it gets (such as the input prompts):
parentHandle?.readabilityHandler = { handle in
guard let line = String(data: handle.availableData, encoding: .utf8), !line.isEmpty else {
return
}
logger.notice("\(line)")
}
When process.run()
is executed the program runs and I can see it asks for Apple ID:
input in my terminal, however, when typing input into the terminal the process does not seem to react to this input.
I've tried forwarding the FileHandle.standardInput
:
FileHandle.standardInput.readabilityHandler = { handle in
parentHandle?.write(handle.availableData)
}
But this doesn't seem to work either.
What is the recommended way to setup a PTY with Foundation.Process
for executing arbitrary programs and having them behave as if they were being run in a terminal context?
Most of the resources I found online are about other languages and I'd like to stick with Foundation.Process
vs. doing anything custom in C/C++
if possible as it just makes it easier to reason about / maintain. The resources for Swift on this topic are very lacking and I've checked out some open source projects that claim to do this but most require manually sending input to the PTY handle vs. accepting them from the user in a terminal.
Any insight / help is very much appreciated!