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.
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3129,6 +3129,7 @@ version = "0.33.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"atty",
|
||||||
"cap-fs-ext",
|
"cap-fs-ext",
|
||||||
"cap-rand",
|
"cap-rand",
|
||||||
"cap-std",
|
"cap-std",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ rustix = "0.31.0"
|
|||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = "0.3"
|
winapi = "0.3"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
atty = "0.2.14"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
|
|||||||
@@ -121,6 +121,16 @@ impl WasiFile for File {
|
|||||||
async fn num_ready_bytes(&self) -> Result<u64, Error> {
|
async fn num_ready_bytes(&self) -> Result<u64, Error> {
|
||||||
Ok(self.0.num_ready_bytes()?)
|
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> {
|
async fn readable(&self) -> Result<(), Error> {
|
||||||
Err(Error::badf())
|
Err(Error::badf())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,12 @@ impl WasiFile for Stdin {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn get_filetype(&self) -> Result<FileType, Error> {
|
async fn get_filetype(&self) -> Result<FileType, Error> {
|
||||||
|
if self.isatty() {
|
||||||
|
Ok(FileType::CharacterDevice)
|
||||||
|
} else {
|
||||||
Ok(FileType::Unknown)
|
Ok(FileType::Unknown)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
|
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
|
||||||
Ok(FdFlags::empty())
|
Ok(FdFlags::empty())
|
||||||
}
|
}
|
||||||
@@ -104,6 +108,16 @@ impl WasiFile for Stdin {
|
|||||||
async fn num_ready_bytes(&self) -> Result<u64, Error> {
|
async fn num_ready_bytes(&self) -> Result<u64, Error> {
|
||||||
Ok(self.0.num_ready_bytes()?)
|
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> {
|
async fn readable(&self) -> Result<(), Error> {
|
||||||
Err(Error::badf())
|
Err(Error::badf())
|
||||||
}
|
}
|
||||||
@@ -125,7 +139,7 @@ impl AsFd for Stdin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! wasi_file_write_impl {
|
macro_rules! wasi_file_write_impl {
|
||||||
($ty:ty) => {
|
($ty:ty, $ident:ident) => {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl WasiFile for $ty {
|
impl WasiFile for $ty {
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
@@ -138,8 +152,12 @@ macro_rules! wasi_file_write_impl {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn get_filetype(&self) -> Result<FileType, Error> {
|
async fn get_filetype(&self) -> Result<FileType, Error> {
|
||||||
|
if self.isatty() {
|
||||||
|
Ok(FileType::CharacterDevice)
|
||||||
|
} else {
|
||||||
Ok(FileType::Unknown)
|
Ok(FileType::Unknown)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
|
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
|
||||||
Ok(FdFlags::APPEND)
|
Ok(FdFlags::APPEND)
|
||||||
}
|
}
|
||||||
@@ -210,6 +228,16 @@ macro_rules! wasi_file_write_impl {
|
|||||||
async fn num_ready_bytes(&self) -> Result<u64, Error> {
|
async fn num_ready_bytes(&self) -> Result<u64, Error> {
|
||||||
Ok(0)
|
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> {
|
async fn readable(&self) -> Result<(), Error> {
|
||||||
Err(Error::badf())
|
Err(Error::badf())
|
||||||
}
|
}
|
||||||
@@ -237,11 +265,11 @@ pub struct Stdout(std::io::Stdout);
|
|||||||
pub fn stdout() -> Stdout {
|
pub fn stdout() -> Stdout {
|
||||||
Stdout(std::io::stdout())
|
Stdout(std::io::stdout())
|
||||||
}
|
}
|
||||||
wasi_file_write_impl!(Stdout);
|
wasi_file_write_impl!(Stdout, Stdout);
|
||||||
|
|
||||||
pub struct Stderr(std::io::Stderr);
|
pub struct Stderr(std::io::Stderr);
|
||||||
|
|
||||||
pub fn stderr() -> Stderr {
|
pub fn stderr() -> Stderr {
|
||||||
Stderr(std::io::stderr())
|
Stderr(std::io::stderr())
|
||||||
}
|
}
|
||||||
wasi_file_write_impl!(Stderr);
|
wasi_file_write_impl!(Stderr, Stderr);
|
||||||
|
|||||||
@@ -71,15 +71,31 @@ impl WasiCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_stdin(&mut self, f: Box<dyn WasiFile>) {
|
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>) {
|
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>) {
|
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(
|
pub fn push_preopened_dir(
|
||||||
|
|||||||
@@ -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 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 peek(&self, buf: &mut [u8]) -> Result<u64, Error>; // read op
|
||||||
async fn num_ready_bytes(&self) -> 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 readable(&self) -> Result<(), Error>;
|
||||||
async fn writable(&self) -> Result<(), Error>;
|
async fn writable(&self) -> Result<(), Error>;
|
||||||
|
|||||||
@@ -180,6 +180,9 @@ impl<R: Read + Any + Send + Sync> WasiFile for ReadPipe<R> {
|
|||||||
async fn num_ready_bytes(&self) -> Result<u64, Error> {
|
async fn num_ready_bytes(&self) -> Result<u64, Error> {
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
|
fn isatty(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
async fn readable(&self) -> Result<(), Error> {
|
async fn readable(&self) -> Result<(), Error> {
|
||||||
Err(Error::badf())
|
Err(Error::badf())
|
||||||
}
|
}
|
||||||
@@ -336,6 +339,9 @@ impl<W: Write + Any + Send + Sync> WasiFile for WritePipe<W> {
|
|||||||
async fn num_ready_bytes(&self) -> Result<u64, Error> {
|
async fn num_ready_bytes(&self) -> Result<u64, Error> {
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
|
fn isatty(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
async fn readable(&self) -> Result<(), Error> {
|
async fn readable(&self) -> Result<(), Error> {
|
||||||
Err(Error::badf())
|
Err(Error::badf())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,9 @@ macro_rules! wasi_file_impl {
|
|||||||
async fn num_ready_bytes(&self) -> Result<u64, Error> {
|
async fn num_ready_bytes(&self) -> Result<u64, Error> {
|
||||||
block_on_dummy_executor(|| self.0.num_ready_bytes())
|
block_on_dummy_executor(|| self.0.num_ready_bytes())
|
||||||
}
|
}
|
||||||
|
fn isatty(&self) -> bool {
|
||||||
|
self.0.isatty()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
async fn readable(&self) -> Result<(), Error> {
|
async fn readable(&self) -> Result<(), Error> {
|
||||||
|
|||||||
Reference in New Issue
Block a user