cfgetospeed(): called `Result::unwrap()` on an `Err` value: EINVAL
Given
use nix::sys::termios::{
cfgetospeed, tcgetattr
};
fn main() {
let stdin = std::io::stdin();
let termios = tcgetattr(stdin).expect("Could not get terminal attributes");
let speed = cfgetospeed(&termios);
println!("{:?}", speed);
}
We receive:
> cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/demo`
thread 'main' panicked at /home/jak/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/nix-0.30.1/src/sys/termios.rs:767:70:
called `Result::unwrap()` on an `Err` value: EINVAL
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
with glibc 2.42
(Minimized reproducer of https://github.com/uutils/coreutils/issues/8474)
Not sure why though given that when I strace it I see no EINVAL at all and get:
ioctl(0, TCGETS2, {c_iflag=BRKINT|ICRNL|IMAXBEL, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|B38400<<IBSHIFT|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
It seems that glibc 2.42 has migrated from the baud encoding with the B variables to the BSD approach:
jak@jak-t14-g3 /t/demo (main)> cat a.c
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
int main(void)
{
struct termios termios;
errno = 0;
if (tcgetattr(STDIN_FILENO, &termios))
perror("tcgetattr");
if (errno != 0)
perror("tcgetattr");
speed_t speed = cfgetospeed(&termios);
if (errno != 0)
perror("cfgetospeed");
printf("%u %u\n", speed, B38400);
}
jak@jak-t14-g3 /t/demo (main)> ./a.out
38400 38400
@hpax is the same issue as Possible regression in 2.42 termios refactoring re non-standard baud rate ? Do you have a suggested fix?
It sounds like this is not an issue with Nix itself, right?
No this is a problem in Nix, in glibc 2.42 or newer it needs to use the same code paths as bsd, only older versions of glibc use the current Linux code paths.
I do not know if you can match the glibc version when building the crate but it's a must to support both old glibc and new
I do not know if you can match the glibc version when building the crate but it's a must to support both old glibc and new
We can't, because glibc is a shared library and according to Bugzilla, upgrading glibc alone is enough to trigger the bug. So it's not possible for Nix to anticipate at compile-time which version of glibc it will be used with.
glibc made a backwards-incompatible change in a patch release. That's almost guaranteed to cause trouble. I'll accept a patch if you can provide one. Is there any code that works with both the old and new glibc versions?
Could you use the C function gnu_get_libc_version() to determine the version at runtime? @thesamesam any suggestions?
How are you invoking glibc? The glibc symbols are versioned for a reason. If you are going to invoke the ABI directly without using the header-provided information, then you should look at the symbol versions. Structure formats and types can also change; in fact, in this case they have for MIPS and SPARC.
I hate to say it, but this seems to me to be a much more serious problem in how you manage your API translation layer. Now, there are several options available to you:
- Detect and, if present, use the baud_t (cfsetobaud() et al) functions in glibc 2.42+
- Probe the Bxxx symbols from the appropriate header file. If they are equal to their numeric value, then the translation is trivial (for Linux, B300 == 300 is enough of a test.)
- Observe the version number of the cfsetospeed() etc symbols.
- Use a C shim layer.
- Ignore libc and directly interface with the kernel. NOT recommended – out of those that have tried, most implementations have been seriously buggy.
I didn't realize that glibc used versioned symbols. But if if does, then that's definitely the best solution. You need to make a PR to https://github.com/rust-lang/libc to add #[link_name] attributes to all of the symbols whose versions have changed. libc has a policy of maintaining backwards-compatibility with some specific glibc version (I can't remember which).
Well, I'm not a Rust user, but it seems that you have an idea what you need to do. The new API is enabled with GLIBC_2.42; the versions for older glibc seems to be somewhat platform-based. Note that using the old API comes with a serious loss of functionality, so you would really want to use the new one if available.
Note that this might work for functions, but the baud rate constants can not be adjusted this way. They are not symbols in the ELF file, they're compile time constants.
Programs using the baud rate constants are all going to be broken by upgrading glibc.
/edit: Ah, this is not true.. they would keep calling the old functions that actually support the old baud rate constants.
Question as a end user: is https://github.com/uutils/coreutils/issues/8474#issuecomment-3591504418 a valid workaround?
Cannot use BSD's code path and https://docs.rs/glibc_version/latest/glibc_version/ ? (Not sure what does it return on cross build) [Edit] Oh... Binary linked with old glibc would not work with this...
Smart way is to link a simple symbol reference, look at the version of the symbol picked and then pick the proper constants probably