Skip to content

Commit 5fc01ba

Browse files
authored
Fix isatty in WASI. (#3696)
WASI doesn't have an `isatty` ioctl or syscall, so wasi-libc's `isatty` implementation uses the file descriptor type and rights to determine if the file descriptor is likely to be a tty. The real fix here will be to add an `isatty` call to WASI. But for now, have Wasmtime set the filetype and rights for file descriptors so that wasi-libc's `isatty` works as expected.
1 parent b1ad02e commit 5fc01ba

File tree

8 files changed

+74
-8
lines changed

8 files changed

+74
-8
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/wasi-common/cap-std-sync/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ rustix = "0.31.0"
3030
[target.'cfg(windows)'.dependencies]
3131
winapi = "0.3"
3232
lazy_static = "1.4"
33+
atty = "0.2.14"
3334

3435
[dev-dependencies]
3536
tempfile = "3.1.0"

crates/wasi-common/cap-std-sync/src/file.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,16 @@ impl WasiFile for File {
121121
async fn num_ready_bytes(&self) -> Result<u64, Error> {
122122
Ok(self.0.num_ready_bytes()?)
123123
}
124+
fn isatty(&self) -> bool {
125+
#[cfg(unix)]
126+
{
127+
rustix::io::isatty(&self.0)
128+
}
129+
#[cfg(windows)]
130+
{
131+
false
132+
}
133+
}
124134
async fn readable(&self) -> Result<(), Error> {
125135
Err(Error::badf())
126136
}

crates/wasi-common/cap-std-sync/src/stdio.rs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ impl WasiFile for Stdin {
3535
Ok(())
3636
}
3737
async fn get_filetype(&self) -> Result<FileType, Error> {
38-
Ok(FileType::Unknown)
38+
if self.isatty() {
39+
Ok(FileType::CharacterDevice)
40+
} else {
41+
Ok(FileType::Unknown)
42+
}
3943
}
4044
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
4145
Ok(FdFlags::empty())
@@ -104,6 +108,16 @@ impl WasiFile for Stdin {
104108
async fn num_ready_bytes(&self) -> Result<u64, Error> {
105109
Ok(self.0.num_ready_bytes()?)
106110
}
111+
fn isatty(&self) -> bool {
112+
#[cfg(unix)]
113+
{
114+
rustix::io::isatty(&self.0)
115+
}
116+
#[cfg(not(unix))]
117+
{
118+
atty::is(atty::Stream::Stdin)
119+
}
120+
}
107121
async fn readable(&self) -> Result<(), Error> {
108122
Err(Error::badf())
109123
}
@@ -125,7 +139,7 @@ impl AsFd for Stdin {
125139
}
126140

127141
macro_rules! wasi_file_write_impl {
128-
($ty:ty) => {
142+
($ty:ty, $ident:ident) => {
129143
#[async_trait::async_trait]
130144
impl WasiFile for $ty {
131145
fn as_any(&self) -> &dyn Any {
@@ -138,7 +152,11 @@ macro_rules! wasi_file_write_impl {
138152
Ok(())
139153
}
140154
async fn get_filetype(&self) -> Result<FileType, Error> {
141-
Ok(FileType::Unknown)
155+
if self.isatty() {
156+
Ok(FileType::CharacterDevice)
157+
} else {
158+
Ok(FileType::Unknown)
159+
}
142160
}
143161
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
144162
Ok(FdFlags::APPEND)
@@ -210,6 +228,16 @@ macro_rules! wasi_file_write_impl {
210228
async fn num_ready_bytes(&self) -> Result<u64, Error> {
211229
Ok(0)
212230
}
231+
fn isatty(&self) -> bool {
232+
#[cfg(unix)]
233+
{
234+
rustix::io::isatty(&self.0)
235+
}
236+
#[cfg(not(unix))]
237+
{
238+
atty::is(atty::Stream::$ident)
239+
}
240+
}
213241
async fn readable(&self) -> Result<(), Error> {
214242
Err(Error::badf())
215243
}
@@ -237,11 +265,11 @@ pub struct Stdout(std::io::Stdout);
237265
pub fn stdout() -> Stdout {
238266
Stdout(std::io::stdout())
239267
}
240-
wasi_file_write_impl!(Stdout);
268+
wasi_file_write_impl!(Stdout, Stdout);
241269

242270
pub struct Stderr(std::io::Stderr);
243271

244272
pub fn stderr() -> Stderr {
245273
Stderr(std::io::stderr())
246274
}
247-
wasi_file_write_impl!(Stderr);
275+
wasi_file_write_impl!(Stderr, Stderr);

crates/wasi-common/src/ctx.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,31 @@ impl WasiCtx {
7171
}
7272

7373
pub fn set_stdin(&mut self, f: Box<dyn WasiFile>) {
74-
self.insert_file(0, f, FileCaps::all());
74+
let rights = Self::stdio_rights(&*f);
75+
self.insert_file(0, f, rights);
7576
}
7677

7778
pub fn set_stdout(&mut self, f: Box<dyn WasiFile>) {
78-
self.insert_file(1, f, FileCaps::all());
79+
let rights = Self::stdio_rights(&*f);
80+
self.insert_file(1, f, rights);
7981
}
8082

8183
pub fn set_stderr(&mut self, f: Box<dyn WasiFile>) {
82-
self.insert_file(2, f, FileCaps::all());
84+
let rights = Self::stdio_rights(&*f);
85+
self.insert_file(2, f, rights);
86+
}
87+
88+
fn stdio_rights(f: &dyn WasiFile) -> FileCaps {
89+
let mut rights = FileCaps::all();
90+
91+
// If `f` is a tty, restrict the `tell` and `seek` capabilities, so
92+
// that wasi-libc's `isatty` correctly detects the file descriptor
93+
// as a tty.
94+
if f.isatty() {
95+
rights &= !(FileCaps::TELL | FileCaps::SEEK);
96+
}
97+
98+
rights
8399
}
84100

85101
pub fn push_preopened_dir(

crates/wasi-common/src/file.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub trait WasiFile: Send + Sync {
3434
async fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error>; // file op that generates a new stream from a file will supercede this
3535
async fn peek(&self, buf: &mut [u8]) -> Result<u64, Error>; // read op
3636
async fn num_ready_bytes(&self) -> Result<u64, Error>; // read op
37+
fn isatty(&self) -> bool;
3738

3839
async fn readable(&self) -> Result<(), Error>;
3940
async fn writable(&self) -> Result<(), Error>;

crates/wasi-common/src/pipe.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ impl<R: Read + Any + Send + Sync> WasiFile for ReadPipe<R> {
180180
async fn num_ready_bytes(&self) -> Result<u64, Error> {
181181
Ok(0)
182182
}
183+
fn isatty(&self) -> bool {
184+
false
185+
}
183186
async fn readable(&self) -> Result<(), Error> {
184187
Err(Error::badf())
185188
}
@@ -336,6 +339,9 @@ impl<W: Write + Any + Send + Sync> WasiFile for WritePipe<W> {
336339
async fn num_ready_bytes(&self) -> Result<u64, Error> {
337340
Ok(0)
338341
}
342+
fn isatty(&self) -> bool {
343+
false
344+
}
339345
async fn readable(&self) -> Result<(), Error> {
340346
Err(Error::badf())
341347
}

crates/wasi-common/tokio/src/file.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ macro_rules! wasi_file_impl {
112112
async fn num_ready_bytes(&self) -> Result<u64, Error> {
113113
block_on_dummy_executor(|| self.0.num_ready_bytes())
114114
}
115+
fn isatty(&self) -> bool {
116+
self.0.isatty()
117+
}
115118

116119
#[cfg(not(windows))]
117120
async fn readable(&self) -> Result<(), Error> {

0 commit comments

Comments
 (0)