Spawning interactive process

Following on my question at https://forums.swift.org/t/how-to-allow-process-to-receive-user-input-when-run-as-part-of-an-executable-e-g-to-enabled-sudo-commands/34357 I am trying to spawn an interactive process using a swift executable, specifically `sudo`.


I've tried a few things but nothing is working so I think I'm missing something fundemental.


I tried using `openpty` but I believe the issue is that I need to pass input to `amaster`, which I'm unsure how to do:


public func runOpenpty(_ command: [String]) throws {
    var amaster: Int32 = 0
    var aslave: Int32 = 0
    if openpty(&amaster, &aslave, nil, nil, nil) == -1 {
        print("Failed to open pty")
    }

    let masterHandle = FileHandle(fileDescriptor: amaster, closeOnDealloc: true)
    let slaveHandle = FileHandle(fileDescriptor: aslave, closeOnDealloc: true)

    let process = Process()
    process.launchPath = "/usr/bin/env"
    process.arguments = command
    process.standardInput = FileHandle.standardInput
    process.standardOutput = FileHandle.standardOutput

    try process.run()
    process.waitUntilExit()
}


I also came across https://github.com/eonil/PseudoTeletypewriter.Swift, which uses `forkpty`, but I've been unable to get it working from a swift executable.


How can I allow the user to provide input to a process that I spawn from my swift executable?

Answers

which uses

forkpty
, but I've been unable to get it working from a swift executable.

The

forkpty
call is not safe to use from Swift. It basically involves a fork without an exec, and it’s not safe to run Swift code in that environment (it’s the same for Objective-C).

I believe the issue is that I need to pass input to

amaster
, which I'm unsure how to do:

Not quite (-: Before we start, a note on terminology. While the pseudo terminal documentation is full of references to master and *****, I’m not going to use those in any text I write. Instead I’m going to use the names of the terminal devices themselves, pty and tty respectively.

Three things:

  • You want to pass tty to the child process, not pty. You want to retain pty and use it to control tty. See the

    pty
    man page for more background on this.
  • You need to pass tty to the child thrice, for

    standardInput
    ,
    standardOutput
    and
    standardError
    . I don’t think you need to do anything special here;
    FileHandle
    will do the right thing if you assign it to all three properties.
  • Once the child is running, make sure you close your copy of tty. Otherwise things can get very confusing.

Finally, this stuff is deep UNIX arcana and, as such, is best documented in various UNIX texts. My go-to book for this sort of thing in Advanced Programming in the Unix Environment. Get it, read it!

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"