crossterm-rs / crossterm

Cross platform terminal library rust
MIT License
3.27k stars 279 forks source link

enable_raw_mode() error in version 0.28.0 under WSL and Android #912

Open tajkhan opened 2 months ago

tajkhan commented 2 months ago

Solution

rustix has released 0.38.36, which fixes this bug. Crossterm 0.28.1 is compatible with this version of rustix. Run cargo update to update your dependencies in your lock file.


Problem

Describe the bug Was following the hecto tutorial which uses crossterm and found that the enable_raw_mode() function in bash under WSL in the latest version "0.28.0" returns an error. Apparently was working fine in version "0.27.0".

To Reproduce Steps to reproduce the behavior:

  1. Go to bash in Ubuntu under WSL and create a new binary crate with the following code in src/main.rs:
use std::io::{self, Read};
use crossterm::terminal::enable_raw_mode;
use crossterm::terminal::disable_raw_mode;

fn main() {
    enable_raw_mode().unwrap();
    for b in io::stdin().bytes() {
        let c = b.unwrap() as char;
        println!("{}", c);
        if c == 'q' {
        disable_raw_mode().unwrap();
            break;
        }
    }
}
  1. Do cargo add crossterm. By default it will add the latest version "0.28.0".
  2. Run cargo run
  3. The output contains the error: 'called Result::unwrap() on an Err value: Os { code: 25, kind: Uncategorized, message: "Inappropriate ioctl for device" }'
  4. Moving the dependency version to "0.27.0" as used by the tutorial works fine though.

Expected behavior Calling enable_raw_mode() should return Ok(()) and switch the terminal to raw mode.

OS I am running bash in Ubuntu in WSL under Windows 10 home edition; The terminal is xterm-256color.

jbncode commented 2 months ago

I'm experiencing the same issue. I found that enabling the libc feature of crossterm makes it work again. It looks like 0.27 always uses the "libc" method, but in 0.28 a new way that does not require libc was added as the default. It is this new non-libc version of the function that doesn't work under WSL.

joshka commented 2 months ago

Please post a full error message including backtrace for this if you can.

Good to hear switching back to libc works. I pushed to keep the libc code in place when the rustix implementation was added, and was starting to second guess myself about being a little over cautious.

sunfishcode commented 2 months ago

I don't have a WSL environment to test this in, however a notable difference in the strace output between the rustix and libc versions here is that rustix is using TCGETS2 (introduced in Linux 2.6.20) for the ioctl, while libc is using TCGETS. I found a report that WSL doesn't support TCGETS2 on serial ports, so it's plausible that the explanation here is that WSL doesn't support TCGETS2 on its terminal either. I'll look into having rustix use TCGETS.

The full error message would help confirm this. And if anyone who is able to reproduce this is able to run the program under strace and post the output, that would be helpful as well. Thanks!

jbncode commented 2 months ago

Thanks for looking into this. For the following program:

fn main() {
    crossterm::terminal::enable_raw_mode().unwrap();
}

I get this output from strace, which does indeed point to TCGETS2 being the issue:

execve("./target/debug/crossterm-test", ["./target/debug/crossterm-test"...], 0x7fffde974aa0 /* 49 vars */) = 0
brk(NULL)                               = 0x7fffe62fa000
arch_prctl(0x3001 /* ARCH_??? */, 0x7fffeeed12f0) = -1 EINVAL (Invalid argument)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f55805d0000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=129419, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 129419, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f5580570000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=125488, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 127720, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f5580550000
mmap(0x7f5580553000, 94208, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7f5580553000
mmap(0x7f558056a000, 16384, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a000) = 0x7f558056a000
mmap(0x7f558056e000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1d000) = 0x7f558056e000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\237\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0I\17\357\204\3$\f\221\2039x\324\224\323\236S"..., 68, 896) = 68
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=2220400, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2264656, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f5580320000
mprotect(0x7f5580348000, 2023424, PROT_NONE) = 0
mmap(0x7f5580348000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7f5580348000
mmap(0x7f55804dd000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x7f55804dd000
mmap(0x7f5580536000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x215000) = 0x7f5580536000
mmap(0x7f558053c000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f558053c000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5580310000
arch_prctl(ARCH_SET_FS, 0x7f55803107c0) = 0
set_tid_address(0x7f5580310a90)         = 3291
set_robust_list(0x7f5580310aa0, 24)     = 0
rseq(0x7f5580311160, 0x20, 0, 0x53053053) = -1 ENOSYS (Function not implemented)
mprotect(0x7f5580536000, 16384, PROT_READ) = 0
mprotect(0x7f558056e000, 4096, PROT_READ) = 0
mprotect(0x7f5580643000, 16384, PROT_READ) = 0
mprotect(0x7f55805c8000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=8192*1024}) = 0
munmap(0x7f5580570000, 129419)          = 0
poll([{fd=0, events=0}, {fd=1, events=0}, {fd=2, events=0}], 3, 0) = 0 (Timeout)
rt_sigaction(SIGPIPE, {sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0
x7f5580362520}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7fc25d7d2520}, 8) = 0
getrandom("\x4f\xc8\xee\xb0\x5d\x3a\x97\x3f", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0x7fffe62fa000
brk(0x7fffe631b000)                     = 0x7fffe631b000
openat(AT_FDCWD, "/proc/self/maps", O_RDONLY|O_CLOEXEC) = 3
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=8192*1024}) = 0
newfstatat(3, "", {st_mode=S_IFREG|0444, st_size=0, ...}, AT_EMPTY_PATH) = 0
read(3, "7f5580310000-7f5580313000 rw-p 0"..., 4096) = 3572
close(3)                                = 0
sched_getaffinity(3291, 32, [0, 1, 2, 3, 4, 5, 6, 7]) = 32
rt_sigaction(SIGSEGV, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fc25d7d2520}, 8) = 0
rt_sigaction(SIGSEGV, {sa_handler=0x7f5580610440, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_SIGINFO, sa_restorer=0x7f5580362520}, NULL, 8) = 0
rt_sigaction(SIGBUS, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fc25d7d2520}, 8) = 0
rt_sigaction(SIGBUS, {sa_handler=0x7f5580610440, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_SIGINFO, sa_restorer=0x7f5580362520}, NULL, 8) = 0
sigaltstack(NULL, {ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}) = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7f5580580000
mprotect(0x7f5580580000, 4096, PROT_NONE) = 0
sigaltstack({ss_sp=0x7f5580581000, ss_flags=0, ss_size=8192}, NULL) = 0
ioctl(0, TIOCGWINSZ, {ws_row=51, ws_col=105, ws_xpixel=1680, ws_ypixel=1632}) = 0
ioctl(0, TCGETS2, 0x7fffeeed0ddc)       = -1 ENOTTY (Inappropriate ioctl for device)
write(2, "thread '", 8thread ')                 = 8
write(2, "main", 4main)                     = 4
write(2, "' panicked at ", 14' panicked at )          = 14
write(2, "src/main.rs", 11src/main.rs)             = 11
write(2, ":", 1:)                        = 1
write(2, "2", 12)                        = 1
write(2, ":", 1:)                        = 1
write(2, "44", 244)                       = 2
write(2, ":\n", 2:
)                      = 2
write(2, "called `Result::unwrap()` on an "..., 124called `Result::unwrap()` on an `Err` value: Os { code: 25, kind: Uncategorized, message: "Inappropriate ioctl for device" }) = 124
write(2, "\n", 1
)                       = 1
write(2, "note: run with `RUST_BACKTRACE=1"..., 78note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
) = 78
futex(0x7f558056f210, FUTEX_WAKE_PRIVATE, 2147483647) = 0
sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=8192}, NULL) = 0
munmap(0x7f5580580000, 12288)           = 0
exit_group(101)                         = ?
+++ exited with 101 +++

The backtrace doesn't go any deeper than the call to enable_raw_mode, but in case it's still helpful it is:

thread 'main' panicked at src/main.rs:2:44:
called `Result::unwrap()` on an `Err` value: Os { code: 25, kind: Uncategorized, message: "Inappropriate ioctl for device" }
stack backtrace:
   0: rust_begin_unwind
             at /rustc/3f5fd8dd41153bc5fdca9427e9e05be2c767ba23/library/std/src/panicking.rs:652:5
   1: core::panicking::panic_fmt
             at /rustc/3f5fd8dd41153bc5fdca9427e9e05be2c767ba23/library/core/src/panicking.rs:72:14
   2: core::result::unwrap_failed
             at /rustc/3f5fd8dd41153bc5fdca9427e9e05be2c767ba23/library/core/src/result.rs:1679:5
   3: core::result::Result<T,E>::unwrap
             at /rustc/3f5fd8dd41153bc5fdca9427e9e05be2c767ba23/library/core/src/result.rs:1102:23
   4: crossterm_test::main
             at ./src/main.rs:2:5
   5: core::ops::function::FnOnce::call_once
             at /rustc/3f5fd8dd41153bc5fdca9427e9e05be2c767ba23/library/core/src/ops/function.rs:250:5

The calling sequence that causes the error is: enable_raw_mode -> get_terminal_attr -> rustix::termios::tcgetattr.

joshka commented 2 months ago

Thanks for the diagnosis. Can you also try to get strace output for the libc feature too? It would be a useful comparison to see where they differ.

jbncode commented 2 months ago

Here's the strace output with the libc feature:

execve("./target/debug/crossterm-test", ["./target/debug/crossterm-test"], 0x7fffecb9dcd0 /* 49 vars */) = 0
brk(NULL)                               = 0x7fffe2a39000
arch_prctl(0x3001 /* ARCH_??? */, 0x7fffe9e0e8a0) = -1 EINVAL (Invalid argument)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5a02ef0000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=129419, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 129419, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f5a02ed0000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=125488, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 127720, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f5a02eb0000
mmap(0x7f5a02eb3000, 94208, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7f5a02eb3000
mmap(0x7f5a02eca000, 16384, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a000) = 0x7f5a02eca000
mmap(0x7f5a02ece000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1d000) = 0x7f5a02ece000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\237\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0I\17\357\204\3$\f\221\2039x\324\224\323\236S"..., 68, 896) = 68
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=2220400, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2264656, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f5a02c80000
mprotect(0x7f5a02ca8000, 2023424, PROT_NONE) = 0
mmap(0x7f5a02ca8000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7f5a02ca8000
mmap(0x7f5a02e3d000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x7f5a02e3d000
mmap(0x7f5a02e96000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x215000) = 0x7f5a02e96000
mmap(0x7f5a02e9c000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f5a02e9c000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5a02c70000
arch_prctl(ARCH_SET_FS, 0x7f5a02c707c0) = 0
set_tid_address(0x7f5a02c70a90)         = 5557
set_robust_list(0x7f5a02c70aa0, 24)     = 0
rseq(0x7f5a02c71160, 0x20, 0, 0x53053053) = -1 ENOSYS (Function not implemented)
mprotect(0x7f5a02e96000, 16384, PROT_READ) = 0
mprotect(0x7f5a02ece000, 4096, PROT_READ) = 0
mprotect(0x7f5a02fa3000, 16384, PROT_READ) = 0
mprotect(0x7f5a02f38000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=8192*1024}) = 0
munmap(0x7f5a02ed0000, 129419)          = 0
poll([{fd=0, events=0}, {fd=1, events=0}, {fd=2, events=0}], 3, 0) = 0 (Timeout)
rt_sigaction(SIGPIPE, {sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f5a02cc2520}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f79694a2520}, 8) = 0
getrandom("\x87\xd5\xa7\x88\xd4\x50\x9e\xa6", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0x7fffe2a39000
brk(0x7fffe2a5a000)                     = 0x7fffe2a5a000
openat(AT_FDCWD, "/proc/self/maps", O_RDONLY|O_CLOEXEC) = 3
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=8192*1024}) = 0
newfstatat(3, "", {st_mode=S_IFREG|0444, st_size=0, ...}, AT_EMPTY_PATH) = 0
read(3, "7f5a02c70000-7f5a02c73000 rw-p 0"..., 4096) = 3516
close(3)                                = 0
sched_getaffinity(5557, 32, [0, 1, 2, 3, 4, 5, 6, 7]) = 32
rt_sigaction(SIGSEGV, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f79694a2520}, 8) = 0
rt_sigaction(SIGSEGV, {sa_handler=0x7f5a02f72240, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_SIGINFO, sa_restorer=0x7f5a02cc2520}, NULL, 8) = 0
rt_sigaction(SIGBUS, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f79694a2520}, 8) = 0
rt_sigaction(SIGBUS, {sa_handler=0x7f5a02f72240, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_SIGINFO, sa_restorer=0x7f5a02cc2520}, NULL, 8) = 0
sigaltstack(NULL, {ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}) = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7f5a02ee0000
mprotect(0x7f5a02ee0000, 4096, PROT_NONE) = 0
sigaltstack({ss_sp=0x7f5a02ee1000, ss_flags=0, ss_size=8192}, NULL) = 0
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(0, SNDCTL_TMR_START or TCSETS, {B38400 -opost -isig -icanon -echo ...}) = 0
ioctl(0, TCGETS, {B38400 -opost -isig -icanon -echo ...}) = 0
sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=8192}, NULL) = 0
munmap(0x7f5a02ee0000, 12288)           = 0
exit_group(0)                           = ?
+++ exited with 0 +++
sunfishcode commented 2 months ago

I've now posted https://github.com/bytecodealliance/rustix/pull/1146 with a possible fix. I don't have a WSL environment to test it in myself, but if anyone who does would like to try it, it should be sufficient to add these lines to Cargo.toml:

[patch.crates-io]
rustix = { git = "https://github.com/bytecodealliance/rustix", rev = "4d1f3eaaae138138817dcaaabf5eaea6d9b93a08" }
jbncode commented 2 months ago

Yes, that rustix patch seems to fix it for me. Thanks!

sunfishcode commented 2 months ago

Thanks for testing! I've now released rustix 0.38.36 with the fix.

joshka commented 2 months ago

Added a note to the first comment for anyone that finds this issue marking the solution as run cargo update (until #926 is merged and released).