Swift Process with Psuedo Terminal

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!

Pseudo terminals are a royal pain in… any language (-: …

Oh, I see you also started a thread for this on DevForums. That’s probably a better place for this conversation, so I’m going to respond over there.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like
Terms of Service

Privacy Policy

Cookie Policy