Skip to content

Commit

Permalink
Fix isatty in WASI. (#3696)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sunfishcode authored Jan 24, 2022
1 parent b1ad02e commit 5fc01ba
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 8 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/wasi-common/cap-std-sync/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ rustix = "0.31.0"
[target.'cfg(windows)'.dependencies]
winapi = "0.3"
lazy_static = "1.4"
atty = "0.2.14"

[dev-dependencies]
tempfile = "3.1.0"
10 changes: 10 additions & 0 deletions crates/wasi-common/cap-std-sync/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ impl WasiFile for File {
async fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(self.0.num_ready_bytes()?)
}
fn isatty(&self) -> bool {
#[cfg(unix)]
{
rustix::io::isatty(&self.0)
}
#[cfg(windows)]
{
false
}
}
async fn readable(&self) -> Result<(), Error> {
Err(Error::badf())
}
Expand Down
38 changes: 33 additions & 5 deletions crates/wasi-common/cap-std-sync/src/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ impl WasiFile for Stdin {
Ok(())
}
async fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Unknown)
if self.isatty() {
Ok(FileType::CharacterDevice)
} else {
Ok(FileType::Unknown)
}
}
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::empty())
Expand Down Expand Up @@ -104,6 +108,16 @@ impl WasiFile for Stdin {
async fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(self.0.num_ready_bytes()?)
}
fn isatty(&self) -> bool {
#[cfg(unix)]
{
rustix::io::isatty(&self.0)
}
#[cfg(not(unix))]
{
atty::is(atty::Stream::Stdin)
}
}
async fn readable(&self) -> Result<(), Error> {
Err(Error::badf())
}
Expand All @@ -125,7 +139,7 @@ impl AsFd for Stdin {
}

macro_rules! wasi_file_write_impl {
($ty:ty) => {
($ty:ty, $ident:ident) => {
#[async_trait::async_trait]
impl WasiFile for $ty {
fn as_any(&self) -> &dyn Any {
Expand All @@ -138,7 +152,11 @@ macro_rules! wasi_file_write_impl {
Ok(())
}
async fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Unknown)
if self.isatty() {
Ok(FileType::CharacterDevice)
} else {
Ok(FileType::Unknown)
}
}
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::APPEND)
Expand Down Expand Up @@ -210,6 +228,16 @@ macro_rules! wasi_file_write_impl {
async fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
fn isatty(&self) -> bool {
#[cfg(unix)]
{
rustix::io::isatty(&self.0)
}
#[cfg(not(unix))]
{
atty::is(atty::Stream::$ident)
}
}
async fn readable(&self) -> Result<(), Error> {
Err(Error::badf())
}
Expand Down Expand Up @@ -237,11 +265,11 @@ pub struct Stdout(std::io::Stdout);
pub fn stdout() -> Stdout {
Stdout(std::io::stdout())
}
wasi_file_write_impl!(Stdout);
wasi_file_write_impl!(Stdout, Stdout);

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

pub fn stderr() -> Stderr {
Stderr(std::io::stderr())
}
wasi_file_write_impl!(Stderr);
wasi_file_write_impl!(Stderr, Stderr);
22 changes: 19 additions & 3 deletions crates/wasi-common/src/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,31 @@ impl WasiCtx {
}

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

pub fn set_stdout(&mut self, f: Box<dyn WasiFile>) {
self.insert_file(1, f, FileCaps::all());
let rights = Self::stdio_rights(&*f);
self.insert_file(1, f, rights);
}

pub fn set_stderr(&mut self, f: Box<dyn WasiFile>) {
self.insert_file(2, f, FileCaps::all());
let rights = Self::stdio_rights(&*f);
self.insert_file(2, f, rights);
}

fn stdio_rights(f: &dyn WasiFile) -> FileCaps {
let mut rights = FileCaps::all();

// If `f` is a tty, restrict the `tell` and `seek` capabilities, so
// that wasi-libc's `isatty` correctly detects the file descriptor
// as a tty.
if f.isatty() {
rights &= !(FileCaps::TELL | FileCaps::SEEK);
}

rights
}

pub fn push_preopened_dir(
Expand Down
1 change: 1 addition & 0 deletions crates/wasi-common/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub trait WasiFile: Send + Sync {
async fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error>; // file op that generates a new stream from a file will supercede this
async fn peek(&self, buf: &mut [u8]) -> Result<u64, Error>; // read op
async fn num_ready_bytes(&self) -> Result<u64, Error>; // read op
fn isatty(&self) -> bool;

async fn readable(&self) -> Result<(), Error>;
async fn writable(&self) -> Result<(), Error>;
Expand Down
6 changes: 6 additions & 0 deletions crates/wasi-common/src/pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ impl<R: Read + Any + Send + Sync> WasiFile for ReadPipe<R> {
async fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
fn isatty(&self) -> bool {
false
}
async fn readable(&self) -> Result<(), Error> {
Err(Error::badf())
}
Expand Down Expand Up @@ -336,6 +339,9 @@ impl<W: Write + Any + Send + Sync> WasiFile for WritePipe<W> {
async fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
fn isatty(&self) -> bool {
false
}
async fn readable(&self) -> Result<(), Error> {
Err(Error::badf())
}
Expand Down
3 changes: 3 additions & 0 deletions crates/wasi-common/tokio/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ macro_rules! wasi_file_impl {
async fn num_ready_bytes(&self) -> Result<u64, Error> {
block_on_dummy_executor(|| self.0.num_ready_bytes())
}
fn isatty(&self) -> bool {
self.0.isatty()
}

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

0 comments on commit 5fc01ba

Please sign in to comment.