Skip to content

TTY not defined when Stdio::piped() #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
heaths opened this issue Feb 20, 2025 · 3 comments · Fixed by #33
Closed

TTY not defined when Stdio::piped() #29

heaths opened this issue Feb 20, 2025 · 3 comments · Fixed by #33

Comments

@heaths
Copy link
Owner

heaths commented Feb 20, 2025

printenv was incorrectly writing VT sequences unconditionally, but when switching to anstream::println!() and anstream::eprintln!() color was no longer emitted. This is because the piped IO handles don't inherit (obviously) the FD so IsTerminal::is_terminal() returns false.

There are some crates out there like https://github.com/dtolnay/faketty that create a PTY, which might also help solve #22 better and, perhaps, is how Go's os/exec package is making all this work in op.

heaths added a commit that referenced this issue Feb 20, 2025
This does show there's a problem with `Stdio::piped()` in Rust, though. See #29 for details.
heaths added a commit that referenced this issue Feb 22, 2025
This does show there's a problem with `Stdio::piped()` in Rust, though. See #29 for details.
heaths added a commit that referenced this issue Feb 22, 2025
Works around #29 with an explicit color choice.
heaths added a commit that referenced this issue Feb 22, 2025
Works around #29 with an explicit color choice.
@heaths
Copy link
Owner Author

heaths commented Feb 25, 2025

We can create a PTY in nix with posix_openpt and friends (see crate pty-process, which is nix-only) and CreatePseudoConsole on Windows.

The problem in either case is not being able to separate stdout and stderr. I can create separate PTYs for each stream, but then we're back to #22 and not able to interleave stdout and stderr exactly in the order they were written.

@heaths
Copy link
Owner Author

heaths commented Apr 1, 2025

Something like this does work:

        let (pty, pts) =
            pty_process::blocking::open().map_err(|err| akv_cli::Error::new(ErrorKind::Io, err))?;


        let mut args = self.args.iter();
        let program = args.next().ok_or_else(|| {
            akv_cli::Error::with_message(ErrorKind::InvalidData, "command required")
        })?;
        let mut cmd = pty_process::blocking::Command::new(program).args(args);


        // If stdout or stderr is already redirected, redirect the same streams for the PTY.
        if !std::io::stdout().is_terminal() {
            cmd = cmd.stdout(std::io::stdout());
        }
        if !std::io::stderr().is_terminal() {
            cmd = cmd.stderr(std::io::stderr());
        }


        let mut process = cmd
            .spawn(pts)
            .map_err(|err| akv_cli::Error::new(ErrorKind::Io, err))?;
        let reader = BufReader::new(pty);
        let mut lines = reader.lines();
        while let Some(Ok(line)) = lines.next() {
            let masked = mask_secrets(&line, &secrets);
            println!("{masked}");
        }

But there doesn't seem to be any good cross-platform PTY support. Out of somewhat active, well-downloaded crates, sty-process is nice but only supports linux specifically and most POSIX systems. portable-pay may require starting commands using its CommandBuilder, which is rather simplistic and already launches processes (at least on Windows that I saw) under a shell unnecessarily.

What I really want is something that just creates a PTY. For POSIX - optimization on linux to use /dev/ptmx notwithstanding - is fairly easy using libc::openpt, libc::grantpt, and friends.

It's a bit more involved on Windows, but still pretty straight-forward. Indeed, Windows does require preparing the child process to use a PTY, but it looks like we can do that if we target nightly and use std::os::windows::process::CommandExt::spawn_with_attributes.

Thus, I could create a crate that just allocates a PTY and perhaps provides a helper on windows to set the attribute list accordingly. It does require nightly to enable an experimental feature, but it seems pretty active: rust-lang/rust#114854

Thus, I don't need to write yet another process abstraction. And because I'm using a PTY, I really don't need async from tokio::process::Command and, therefore, not tokio-util either.

@heaths heaths linked a pull request Apr 10, 2025 that will close this issue
@heaths heaths closed this as completed Apr 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant