nix-rust / nix

Rust friendly bindings to *nix APIs

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

On illumos tcsetattr() is unusably buggy as it truncates termios fields

nospam3089 opened this issue · comments

Attempting to run:

use nix::sys::termios::{tcgetattr, tcsetattr};

fn main() {
    let tty_in = std::io::stdin();
    let original_mode = tcgetattr(&tty_in).unwrap();
    println!("tcgetattr(): {original_mode:?}");
    println!("c_iflag: {}", libc::BRKINT | libc::IGNPAR | libc::ICRNL | libc::IXON |
        libc::IMAXBEL);
    println!("c_oflag: {}", libc::OPOST | libc::ONLCR);
    println!("c_cflag: {}", libc::CS6 | libc::CS7 | libc::CREAD);
    println!("c_lflag: {}", libc::ECHOKE | libc::ECHOE | libc::ECHOK | libc::ECHO | libc::ECHOCTL |
        libc::ISIG | libc::ICANON | libc::IEXTEN);

    std::thread::sleep(std::time::Duration::from_secs(10));
    tcsetattr(&tty_in, nix::sys::termios::SetArg::TCSADRAIN, &original_mode).unwrap();
    println!("Back from tcsetattr()?");
    std::thread::sleep(std::time::Duration::from_secs(10));
}

will output something like:

tcgetattr(): Termios { inner: RefCell { value: termios { c_iflag: 9478, c_oflag: 6149, c_cflag: 983231, c_lflag: 35387, c_cc: [3, 28, 127, 21, 4, 0, 0, 0, 17, 19, 26, 25, 18, 15, 23, 22, 20, 8, 0] } }, input_flags: InputFlags(BRKINT | IGNPAR | ICRNL | IXON | IMAXBEL), output_flags: OutputFlags(OPOST | ONLCR), control_flags: ControlFlags(CS6 | CS7 | CREAD), local_flags: LocalFlags(ECHOKE | ECHOE | ECHOK | ECHO | ECHOCTL | ISIG | ICANON | IEXTEN), control_chars: [3, 28, 127, 21, 4, 0, 0, 0, 17, 19, 26, 25, 18, 15, 23, 22, 20, 8, 0] }
c_iflag: 9478
c_oflag: 5
c_cflag: 176
c_lflag: 35387

Please notice two things:

  • The values of c_oflag and c_cflag have been truncated.
  • The terminal was hung up/terminated prior to the string Back from tcsetattr()? had any chance to get printed.

Quoting tcsetattr (3C):

The effect of tcsetattr() is undefined if the value of the termios structure pointed to by termios_p was not derived from the result of a call to tcgetattr(3C) on fildes; an application should modify only fields and flags defined by this document between the call to tcgetattr(3C) and tcsetattr(), leaving all other fields and flags unmodified.

There are also other relevant sections in the manual, but the bottom line is that tcsetattr() practically always close the terminal when called on illumos as currently implemented.

I can think of three ways to improve the current situation:

  • Make nix and libc aware of all flags on illumos.
  • Pass through the original values unmodified from their raw representation for all unknown bits.
  • Simply disable tcsetattr() on illumos.

Given the state of #935 and rust-lang/libc#1405, the first alternative does not seems likely to happen any time soon. The second alternative seems pragmatic, but would be a bit messy both to implement and maintain. The third alternative seems less desired than actually fixing the functionality.

I could do a PR to remove tcsetattr() (or rather the entire termios module?) on illumos, unless someone has a better feasible idea.

Adding missing flags to libc isn't very hard. It's much easier than adding illumos CI. So that's certainly the path of least resistance. It isn't future-proof, however, as it could be broken by future Illumos releases that add new flags.

Passing through the exact libc::termios value, potentially with modifications, would require more work. Instead of making Termios::input_flags pub for example, we might have to create Termios::{set_input_flags,clear_input_flags} methods that manipulate the raw structure while preserving unknown bits. It could be done, but I for one don't have the time to do it.

Disabling termios on illumos would be slightly sad, but only slightly. I would accept that if you really don't have time or ability to add the additional flags to libc.

Thanks for your involved response and for actively maintaining this crate!

It seems we agree the best fix would be to adapt the methods to be gentle with unknown bits. Given the man page quote above, I believe that is the only way to make the API safe on illumos. Since making Termios::input_flags private would require a major version increase, it might be worth considering whether also other breaking changes are in queue?

Unfortunately my time for this is also a bit sparse, but I'll make an attempt at a PR during next week. Then we'll see if it becomes good enough for inclusion, or if we'll have to settle with the unsettling alternative of disabling illumos support.

Don't worry about breaking changes. Nix isn't 1.0 , so we can and do make those from time to time.

I've pushed the two branches fix/disable_tcsetattr_illumos and fix/safe_termios, but they are not at all tested. The test suites seem to pass without adding any new failures, but I have no code to do sanely validate the functionality with.

Since I'm not a direct consumer of the nix crate, and the crate which made me encounter the problem has conflicting outdated dependencies, I'm afraid it seems to be a bit too much and too fiddly work for me to bring this to completion.

With the original C struct already stored in Termios, I believe the fix could actually be as simple as merely preserving the original flags as attempted on the fix/safe_termios branch, but I might need to leave this issue percolating until someone actually able to verify it happens to pass along.

Should I post the illumos-disabling branch to a PR, or what seems like the best way forward?

I am not an Illumos user, and I'm not an expert on the termios API either. If you don't have time to finish that branch, then we can't merge it. But if the current code is useless on Illumos, then sure we can merge a PR to disable it.