From ec94ed0d9bc35f9bcc157ee2541952561eef10e6 Mon Sep 17 00:00:00 2001 From: kilyanni Date: Wed, 5 Aug 2026 18:49:57 +0200 Subject: [PATCH 1/3] feat(wasix): report stdin/out/err as Unknown if it isnt a tty --- lib/virtual-fs/src/host_fs.rs | 13 ++++++++++++ lib/virtual-fs/src/lib.rs | 8 +++++++ lib/wasix/src/fs/mod.rs | 39 +++++++++++++++++++++++++++++++---- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/lib/virtual-fs/src/host_fs.rs b/lib/virtual-fs/src/host_fs.rs index c765c4d318dc..26042bf1af94 100644 --- a/lib/virtual-fs/src/host_fs.rs +++ b/lib/virtual-fs/src/host_fs.rs @@ -616,6 +616,10 @@ impl VirtualFile for Stdout { Some(1) } + fn is_terminal(&self) -> bool { + std::io::IsTerminal::is_terminal(&std::io::stdout()) + } + fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(0)) } @@ -790,6 +794,10 @@ impl VirtualFile for Stderr { Some(2) } + fn is_terminal(&self) -> bool { + std::io::IsTerminal::is_terminal(&std::io::stderr()) + } + fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(0)) } @@ -905,6 +913,11 @@ impl VirtualFile for Stdin { fn get_special_fd(&self) -> Option { Some(0) } + + fn is_terminal(&self) -> bool { + std::io::IsTerminal::is_terminal(&std::io::stdin()) + } + fn poll_read_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { { let read_buffer = self.read_buffer.lock().unwrap(); diff --git a/lib/virtual-fs/src/lib.rs b/lib/virtual-fs/src/lib.rs index 5ad36bbdfbd1..051cd62f12d6 100644 --- a/lib/virtual-fs/src/lib.rs +++ b/lib/virtual-fs/src/lib.rs @@ -389,6 +389,14 @@ pub trait VirtualFile: None } + /// Indicates whether this file is connected to a terminal. + /// + /// Virtual files are not terminals by default. Implementations that wrap + /// host standard streams may override this to report the host TTY state. + fn is_terminal(&self) -> bool { + false + } + /// Writes to this file using an mmap offset and reference /// (this method only works for mmap optimized file systems) fn write_from_mmap(&mut self, _offset: u64, _len: u64) -> std::io::Result<()> { diff --git a/lib/wasix/src/fs/mod.rs b/lib/wasix/src/fs/mod.rs index c1dcd7f6409a..ef664d2d058a 100644 --- a/lib/wasix/src/fs/mod.rs +++ b/lib/wasix/src/fs/mod.rs @@ -1838,11 +1838,25 @@ impl WasiFs { Ok(*guard.deref()) } + fn std_fd_filetype(is_tty: bool) -> Filetype { + if is_tty { + Filetype::CharacterDevice + } else { + Filetype::Unknown + } + } + + fn std_fd_is_terminal(&self, fd: WasiFd) -> bool { + WasiInodes::std_dev_get(&self.fd_map, fd) + .map(|file| file.is_terminal()) + .unwrap_or(false) + } + pub fn fdstat(&self, fd: WasiFd) -> Result { match fd { __WASI_STDIN_FILENO => { return Ok(Fdstat { - fs_filetype: Filetype::CharacterDevice, + fs_filetype: Self::std_fd_filetype(self.std_fd_is_terminal(fd)), fs_flags: Fdflags::empty(), fs_rights_base: STDIN_DEFAULT_RIGHTS, fs_rights_inheriting: Rights::empty(), @@ -1850,7 +1864,7 @@ impl WasiFs { } __WASI_STDOUT_FILENO => { return Ok(Fdstat { - fs_filetype: Filetype::CharacterDevice, + fs_filetype: Self::std_fd_filetype(self.std_fd_is_terminal(fd)), fs_flags: Fdflags::APPEND, fs_rights_base: STDOUT_DEFAULT_RIGHTS, fs_rights_inheriting: Rights::empty(), @@ -1858,7 +1872,7 @@ impl WasiFs { } __WASI_STDERR_FILENO => { return Ok(Fdstat { - fs_filetype: Filetype::CharacterDevice, + fs_filetype: Self::std_fd_filetype(self.std_fd_is_terminal(fd)), fs_flags: Fdflags::APPEND, fs_rights_base: STDERR_DEFAULT_RIGHTS, fs_rights_inheriting: Rights::empty(), @@ -2899,13 +2913,30 @@ mod tests { use super::*; use once_cell::sync::OnceCell; use tempfile::tempdir; - use virtual_fs::{RootFileSystemBuilder, TmpFileSystem}; + use virtual_fs::{NullFile, RootFileSystemBuilder, TmpFileSystem}; use wasmer::Engine; use wasmer_config::package::PackageId; use crate::WasiEnvBuilder; use crate::bin_factory::{BinaryPackage, BinaryPackageMount, BinaryPackageMounts}; + #[test] + fn fdstat_uses_swapped_stdio_terminal_state() { + let inodes = WasiInodes::new(); + let fs_backing = + WasiFsRoot::from_filesystem(Arc::new(RootFileSystemBuilder::default().build_tmp())); + let wasi_fs = WasiFs::new_init(fs_backing, &inodes, FS_ROOT_INO).unwrap(); + + for fd in [ + __WASI_STDIN_FILENO, + __WASI_STDOUT_FILENO, + __WASI_STDERR_FILENO, + ] { + wasi_fs.swap_file(fd, Box::::default()).unwrap(); + assert_eq!(wasi_fs.fdstat(fd).unwrap().fs_filetype, Filetype::Unknown); + } + } + fn webc_symlink_fs() -> virtual_fs::WebcVolumeFileSystem { let timestamps = webc::v3::Timestamps::default(); let dir = webc::v3::write::Directory::new( From 209ad27775b817f28c5789d963d99fa7f8acebe7 Mon Sep 17 00:00:00 2001 From: kilyanni Date: Wed, 5 Aug 2026 19:47:35 +0200 Subject: [PATCH 2/3] fix(tests): update tests for new isatty semantics --- lib/wasix/src/fs/mod.rs | 4 ++-- lib/wasix/tests/wasm_tests/wasi_fyi/ported_isatty.stdout | 6 +++--- .../cli/tests/snapshots/snapshot__snapshot_bash_bash.snap | 2 +- .../cli/tests/snapshots/snapshot__snapshot_bash_cd_ls.snap | 2 +- .../cli/tests/snapshots/snapshot__snapshot_bash_dash.snap | 2 +- .../cli/tests/snapshots/snapshot__snapshot_bash_echo.snap | 2 +- .../cli/tests/snapshots/snapshot__snapshot_bash_ls.snap | 2 +- .../cli/tests/snapshots/snapshot__snapshot_bash_pipe.snap | 2 +- .../cli/tests/snapshots/snapshot__snapshot_bash_python.snap | 2 +- .../cli/tests/snapshots/snapshot__snapshot_dash_bash.snap | 2 +- .../cli/tests/snapshots/snapshot__snapshot_dash_dash.snap | 2 +- .../cli/tests/snapshots/snapshot__snapshot_dash_echo.snap | 2 +- .../snapshots/snapshot__snapshot_dash_echo_to_cat.snap | 2 +- .../cli/tests/snapshots/snapshot__snapshot_dash_python.snap | 2 +- .../cli/tests/snapshots/snapshot__snapshot_epoll.snap | 2 +- .../cli/tests/snapshots/snapshot__snapshot_epoll_async.snap | 2 +- .../cli/tests/snapshots/snapshot__snapshot_execve.snap | 2 +- .../tests/snapshots/snapshot__snapshot_fork_and_exec.snap | 2 +- .../snapshots/snapshot__snapshot_fork_and_exec_async.snap | 2 +- 19 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/wasix/src/fs/mod.rs b/lib/wasix/src/fs/mod.rs index ef664d2d058a..cb700e06bfac 100644 --- a/lib/wasix/src/fs/mod.rs +++ b/lib/wasix/src/fs/mod.rs @@ -2920,8 +2920,8 @@ mod tests { use crate::WasiEnvBuilder; use crate::bin_factory::{BinaryPackage, BinaryPackageMount, BinaryPackageMounts}; - #[test] - fn fdstat_uses_swapped_stdio_terminal_state() { + #[tokio::test] + async fn fdstat_uses_swapped_stdio_terminal_state() { let inodes = WasiInodes::new(); let fs_backing = WasiFsRoot::from_filesystem(Arc::new(RootFileSystemBuilder::default().build_tmp())); diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/ported_isatty.stdout b/lib/wasix/tests/wasm_tests/wasi_fyi/ported_isatty.stdout index 497b5781889d..164b227bd42d 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/ported_isatty.stdout +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/ported_isatty.stdout @@ -1,3 +1,3 @@ -stdin: 1 -stdout: 1 -stderr: 1 +stdin: 0 +stdout: 0 +stderr: 0 diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_bash.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_bash.snap index 668ddf22a3da..7783f3a779a2 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_bash.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_bash.snap @@ -18,7 +18,7 @@ expression: snapshot "result": { "Success": { "stdout": "hi\nhi2\n", - "stderr": "test.wasm-5.1# bash-dist# bash-dist# exit\ntest.wasm-5.1# test.wasm-5.1# exit\n", + "stderr": "", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_cd_ls.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_cd_ls.snap index 898a218846f0..9c10b74ff429 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_cd_ls.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_cd_ls.snap @@ -21,7 +21,7 @@ expression: snapshot "result": { "Success": { "stdout": "arch\nbase32\nbase64\nbaseenc\nbasename\nbash\ncat\nchcon\nchgrp\nchmod\nchown\nchroot\ncksum\ncomm\ncp\ncsplit\ncut\ndate\ndd\ndf\ndircolors\ndirname\ndu\necho\nenv\nexpand\nexpr\nfactor\nfalse\nfmt\nfold\ngroups\nhashsum\nhead\nhostid\nhostname\nid\ninstall\njoin\nkill\nlink\nln\nlogname\nls\nmkdir\nmkfifo\nmknod\nmktemp\nmore\nmv\nnice\nnl\nnohup\nnproc\nnumfmt\nod\npaste\npathchk\npinky\npr\nprintenv\nprintf\nptx\npwd\nreadlink\nrealpath\nrelpath\nrm\nrmdir\nruncon\nseq\nsh\nshred\nshuf\nsleep\nsort\nsplit\nstat\nstdbuf\nsum\nsync\ntac\ntail\ntee\ntest\ntimeout\ntouch\ntr\ntrue\ntruncate\ntsort\ntty\nuname\nunexpand\nuniq\nunlink\nuptime\nusers\nwasmer\nwc\nwho\nwhoami\nyes\n", - "stderr": "test.wasm-5.1# test.wasm-5.1# test.wasm-5.1# exit\n", + "stderr": "", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_dash.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_dash.snap index 05ec9090228e..11bb61600776 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_dash.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_dash.snap @@ -18,7 +18,7 @@ expression: snapshot "result": { "Success": { "stdout": "hi\n", - "stderr": "test.wasm-5.1# test.wasm-5.1# test.wasm-5.1# exit\n", + "stderr": "", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_echo.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_echo.snap index 9ce6021f3754..22534a53c8d7 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_echo.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_echo.snap @@ -15,7 +15,7 @@ expression: snapshot "result": { "Success": { "stdout": "hello\n", - "stderr": "test.wasm-5.1# test.wasm-5.1# exit\n", + "stderr": "", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_ls.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_ls.snap index 9debcfb10f26..bcb09fc9fcdc 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_ls.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_ls.snap @@ -20,7 +20,7 @@ expression: snapshot "result": { "Success": { "stdout": "bin\ndev\netc\ntmp\nusr\n", - "stderr": "test.wasm-5.1# test.wasm-5.1# exit\n", + "stderr": "", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_pipe.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_pipe.snap index c021cd19bbc2..b87e9946c30b 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_pipe.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_pipe.snap @@ -21,7 +21,7 @@ expression: snapshot "result": { "Success": { "stdout": "hello\n", - "stderr": "test.wasm-5.1# test.wasm-5.1# exit\n", + "stderr": "", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_python.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_python.snap index 23a163a8ad99..c999deb05f34 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_python.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_python.snap @@ -21,7 +21,7 @@ expression: snapshot "result": { "Success": { "stdout": "10\n", - "stderr": "test.wasm-5.1# test.wasm-5.1# exit\n", + "stderr": "", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_bash.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_bash.snap index c9c2b7e06e43..ff9c2e4be5c7 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_bash.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_bash.snap @@ -18,7 +18,7 @@ expression: snapshot "result": { "Success": { "stdout": "hi\n", - "stderr": "# bash-dist# exit\n# # ", + "stderr": "", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_dash.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_dash.snap index b5d978c5b504..b5ea593e4d49 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_dash.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_dash.snap @@ -18,7 +18,7 @@ expression: snapshot "result": { "Success": { "stdout": "hi\n", - "stderr": "# # # ", + "stderr": "", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo.snap index 9fb550044e3e..a5c42c63b237 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo.snap @@ -15,7 +15,7 @@ expression: snapshot "result": { "Success": { "stdout": "2\n", - "stderr": "# # \n", + "stderr": "", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo_to_cat.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo_to_cat.snap index 5dc341acfae6..ffe7bfaaa1f2 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo_to_cat.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo_to_cat.snap @@ -21,7 +21,7 @@ expression: snapshot "result": { "Success": { "stdout": "hello\n", - "stderr": "# # \n", + "stderr": "", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_python.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_python.snap index ba2d3250aa8b..549fc8e990a9 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_python.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_python.snap @@ -21,7 +21,7 @@ expression: snapshot "result": { "Success": { "stdout": "10\n", - "stderr": "# # \n", + "stderr": "", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_epoll.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_epoll.snap index db55c60509dc..e045c34801b1 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_epoll.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_epoll.snap @@ -13,7 +13,7 @@ expression: snapshot }, "result": { "Success": { - "stdout": "EFD_NONBLOCK:4\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\n", + "stdout": "EFD_NONBLOCK:4\nsuccess write to efd, write 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\n", "stderr": "", "exit_code": 0 } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_epoll_async.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_epoll_async.snap index 6b7bc10a8277..1444366185a0 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_epoll_async.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_epoll_async.snap @@ -14,7 +14,7 @@ expression: snapshot }, "result": { "Success": { - "stdout": "EFD_NONBLOCK:4\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\n", + "stdout": "EFD_NONBLOCK:4\nsuccess write to efd, write 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\n", "stderr": "", "exit_code": 0 } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_execve.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_execve.snap index 3bf34367384f..9d1c96355d17 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_execve.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_execve.snap @@ -19,7 +19,7 @@ expression: snapshot }, "result": { "Success": { - "stdout": "Main program started\nexecve: echo hi-from-child\nhi-from-child\nhi-from-parent\n", + "stdout": "Main program started\nhi-from-child\nhi-from-parent\n", "stderr": "Child(2) exited with 0\nexecve: echo hi-from-parent\n", "exit_code": 0 } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec.snap index ac7d359a9eff..8fe2be847ea1 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec.snap @@ -19,7 +19,7 @@ expression: snapshot }, "result": { "Success": { - "stdout": "Main program started\nexecve: echo hi-from-child\nhi-from-child\nhi-from-parent\n", + "stdout": "Main program started\nhi-from-child\nhi-from-parent\n", "stderr": "Child(2) exited with 0\nexecve: echo hi-from-parent\n", "exit_code": 0 } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec_async.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec_async.snap index d93fb0273c7f..40507a2526b7 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec_async.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec_async.snap @@ -16,7 +16,7 @@ expression: snapshot }, "result": { "Success": { - "stdout": "Main program started\nexecve: echo hi-from-child\nhi-from-child\nhi-from-parent\n", + "stdout": "Main program started\nhi-from-child\nhi-from-parent\n", "stderr": "Child(2) exited with 0\nexecve: echo hi-from-parent\n", "exit_code": 0 } From 26faddb044144126d2fbb98cb37fda093f53a05c Mon Sep 17 00:00:00 2001 From: kilyanni Date: Thu, 6 Aug 2026 15:19:46 +0200 Subject: [PATCH 3/3] test(wasix): cover TTY detection for swapped PTY stdio --- lib/virtual-fs/src/host_fs.rs | 4 +++ lib/wasix/src/fs/mod.rs | 48 +++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/lib/virtual-fs/src/host_fs.rs b/lib/virtual-fs/src/host_fs.rs index 26042bf1af94..d9b22a12a276 100644 --- a/lib/virtual-fs/src/host_fs.rs +++ b/lib/virtual-fs/src/host_fs.rs @@ -468,6 +468,10 @@ impl VirtualFile for File { None } + fn is_terminal(&self) -> bool { + std::io::IsTerminal::is_terminal(&self.inner_std) + } + fn poll_read_ready(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { let cursor = match self.inner_std.stream_position() { Ok(a) => a, diff --git a/lib/wasix/src/fs/mod.rs b/lib/wasix/src/fs/mod.rs index cb700e06bfac..83cf39f94bdb 100644 --- a/lib/wasix/src/fs/mod.rs +++ b/lib/wasix/src/fs/mod.rs @@ -2937,6 +2937,54 @@ mod tests { } } + #[cfg(all(unix, feature = "host-fs"))] + #[tokio::test] + async fn fdstat_reports_a_swapped_pty_as_a_terminal() { + use std::{ + io::IsTerminal, + os::fd::{FromRawFd, RawFd}, + }; + + let mut master: RawFd = -1; + let mut slave: RawFd = -1; + assert_eq!( + unsafe { + libc::openpty( + &mut master, + &mut slave, + std::ptr::null_mut(), + std::ptr::null(), + std::ptr::null(), + ) + }, + 0 + ); + + let _master = unsafe { std::fs::File::from_raw_fd(master) }; + let slave = unsafe { std::fs::File::from_raw_fd(slave) }; + assert!(slave.is_terminal()); + let inodes = WasiInodes::new(); + let fs_backing = + WasiFsRoot::from_filesystem(Arc::new(RootFileSystemBuilder::default().build_tmp())); + let wasi_fs = WasiFs::new_init(fs_backing, &inodes, FS_ROOT_INO).unwrap(); + let pty = virtual_fs::host_fs::File::new( + tokio::runtime::Handle::current(), + slave, + PathBuf::from("/dev/pts/test"), + true, + true, + false, + ); + + wasi_fs + .swap_file(__WASI_STDIN_FILENO, Box::new(pty)) + .unwrap(); + assert_eq!( + wasi_fs.fdstat(__WASI_STDIN_FILENO).unwrap().fs_filetype, + Filetype::CharacterDevice + ); + } + fn webc_symlink_fs() -> virtual_fs::WebcVolumeFileSystem { let timestamps = webc::v3::Timestamps::default(); let dir = webc::v3::write::Directory::new(