This makes fstat work for stdout, stdin and stderr as expected. This seemed like the only reasonable functions to implement from the filestat_* set, for stdout, stdin and stderr. Fixes #2515
242 lines
7.8 KiB
Rust
242 lines
7.8 KiB
Rust
// The reason we have a separate Stdio wrappers is to correctly facilitate redirects on Windows.
|
|
// To elaborate further, in POSIX, we can get a stdio handle by opening a specific fd {0,1,2}.
|
|
// On Windows however, we need to issue a syscall that's separate from standard Windows "open"
|
|
// to get a console handle, and this is GetStdHandle. This is exactly what Rust does and what
|
|
// is wrapped inside their Stdio object in the libstd. We wrap it here as well because of this
|
|
// nuance on Windows:
|
|
//
|
|
// The standard handles of a process may be redirected by a call to SetStdHandle, in which
|
|
// case GetStdHandle returns the redirected handle.
|
|
//
|
|
// The MSDN also says this however:
|
|
//
|
|
// If the standard handles have been redirected, you can specify the CONIN$ value in a call
|
|
// to the CreateFile function to get a handle to a console's input buffer. Similarly, you
|
|
// can specify the CONOUT$ value to get a handle to a console's active screen buffer.
|
|
//
|
|
// TODO it might worth re-investigating the suitability of this type on Windows.
|
|
|
|
use super::{fd, AsFile};
|
|
use crate::handle::{Fdflags, Filestat, Filetype, Handle, HandleRights, Rights, RightsExt, Size};
|
|
use crate::sandboxed_tty_writer::SandboxedTTYWriter;
|
|
use crate::{Error, Result};
|
|
use std::any::Any;
|
|
use std::cell::Cell;
|
|
use std::convert::TryInto;
|
|
use std::io::{self, Read, Write};
|
|
|
|
pub(crate) trait StdinExt: Sized {
|
|
/// Create `Stdin` from `io::stdin`.
|
|
fn stdin() -> io::Result<Box<dyn Handle>>;
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct Stdin {
|
|
pub(crate) file_type: Filetype,
|
|
pub(crate) rights: Cell<HandleRights>,
|
|
}
|
|
|
|
impl Handle for Stdin {
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
|
|
Ok(Box::new(self.clone()))
|
|
}
|
|
fn get_file_type(&self) -> Filetype {
|
|
self.file_type
|
|
}
|
|
fn get_rights(&self) -> HandleRights {
|
|
self.rights.get()
|
|
}
|
|
fn set_rights(&self, new_rights: HandleRights) {
|
|
self.rights.set(new_rights)
|
|
}
|
|
// FdOps
|
|
fn fdstat_get(&self) -> Result<Fdflags> {
|
|
fd::fdstat_get(&*self.as_file()?)
|
|
}
|
|
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
|
|
if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
|
|
// OK, this means we should somehow update the underlying os handle,
|
|
// and we can't do that with `std::io::std{in, out, err}`, so we'll
|
|
// panic for now.
|
|
panic!("Tried updating Fdflags on Stdio handle by re-opening as file!");
|
|
}
|
|
Ok(())
|
|
}
|
|
fn filestat_get(&self) -> Result<Filestat> {
|
|
fd::filestat_get(&*self.as_file()?)
|
|
}
|
|
fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
|
|
let nread = io::stdin().read_vectored(iovs)?;
|
|
Ok(nread)
|
|
}
|
|
}
|
|
|
|
pub(crate) trait StdoutExt: Sized {
|
|
/// Create `Stdout` from `io::stdout`.
|
|
fn stdout() -> io::Result<Box<dyn Handle>>;
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct Stdout {
|
|
pub(crate) file_type: Filetype,
|
|
pub(crate) rights: Cell<HandleRights>,
|
|
}
|
|
|
|
impl Handle for Stdout {
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
|
|
Ok(Box::new(self.clone()))
|
|
}
|
|
fn get_file_type(&self) -> Filetype {
|
|
self.file_type
|
|
}
|
|
fn get_rights(&self) -> HandleRights {
|
|
self.rights.get()
|
|
}
|
|
fn set_rights(&self, new_rights: HandleRights) {
|
|
self.rights.set(new_rights)
|
|
}
|
|
// FdOps
|
|
fn fdstat_get(&self) -> Result<Fdflags> {
|
|
fd::fdstat_get(&*self.as_file()?)
|
|
}
|
|
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
|
|
if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
|
|
// OK, this means we should somehow update the underlying os handle,
|
|
// and we can't do that with `std::io::std{in, out, err}`, so we'll
|
|
// panic for now.
|
|
panic!("Tried updating Fdflags on Stdio handle by re-opening as file!");
|
|
}
|
|
Ok(())
|
|
}
|
|
fn filestat_get(&self) -> Result<Filestat> {
|
|
fd::filestat_get(&*self.as_file()?)
|
|
}
|
|
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
|
|
// lock for the duration of the scope
|
|
let stdout = io::stdout();
|
|
let mut stdout = stdout.lock();
|
|
let nwritten = if self.is_tty() {
|
|
SandboxedTTYWriter::new(&mut stdout).write_vectored(&iovs)?
|
|
} else {
|
|
stdout.write_vectored(iovs)?
|
|
};
|
|
stdout.flush()?;
|
|
Ok(nwritten)
|
|
}
|
|
}
|
|
|
|
pub(crate) trait StderrExt: Sized {
|
|
/// Create `Stderr` from `io::stderr`.
|
|
fn stderr() -> io::Result<Box<dyn Handle>>;
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct Stderr {
|
|
pub(crate) file_type: Filetype,
|
|
pub(crate) rights: Cell<HandleRights>,
|
|
}
|
|
|
|
impl Handle for Stderr {
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
|
|
Ok(Box::new(self.clone()))
|
|
}
|
|
fn get_file_type(&self) -> Filetype {
|
|
self.file_type
|
|
}
|
|
fn get_rights(&self) -> HandleRights {
|
|
self.rights.get()
|
|
}
|
|
fn set_rights(&self, new_rights: HandleRights) {
|
|
self.rights.set(new_rights)
|
|
}
|
|
// FdOps
|
|
fn fdstat_get(&self) -> Result<Fdflags> {
|
|
fd::fdstat_get(&*self.as_file()?)
|
|
}
|
|
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
|
|
if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
|
|
// OK, this means we should somehow update the underlying os handle,
|
|
// and we can't do that with `std::io::std{in, out, err}`, so we'll
|
|
// panic for now.
|
|
panic!("Tried updating Fdflags on Stdio handle by re-opening as file!");
|
|
}
|
|
Ok(())
|
|
}
|
|
fn filestat_get(&self) -> Result<Filestat> {
|
|
fd::filestat_get(&*self.as_file()?)
|
|
}
|
|
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
|
|
// Always sanitize stderr, even if it's not directly connected to a tty,
|
|
// because stderr is meant for diagnostics rather than binary output,
|
|
// and may be redirected to a file which could end up being displayed
|
|
// on a tty later.
|
|
let nwritten = SandboxedTTYWriter::new(&mut io::stderr()).write_vectored(&iovs)?;
|
|
Ok(nwritten)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct NullDevice {
|
|
pub(crate) rights: Cell<HandleRights>,
|
|
pub(crate) fd_flags: Cell<Fdflags>,
|
|
}
|
|
|
|
impl NullDevice {
|
|
pub(crate) fn new() -> Self {
|
|
let rights = HandleRights::new(
|
|
Rights::character_device_base(),
|
|
Rights::character_device_inheriting(),
|
|
);
|
|
let rights = Cell::new(rights);
|
|
let fd_flags = Fdflags::empty();
|
|
let fd_flags = Cell::new(fd_flags);
|
|
Self { rights, fd_flags }
|
|
}
|
|
}
|
|
|
|
impl Handle for NullDevice {
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
|
|
Ok(Box::new(self.clone()))
|
|
}
|
|
fn get_file_type(&self) -> Filetype {
|
|
Filetype::CharacterDevice
|
|
}
|
|
fn get_rights(&self) -> HandleRights {
|
|
self.rights.get()
|
|
}
|
|
fn set_rights(&self, rights: HandleRights) {
|
|
self.rights.set(rights)
|
|
}
|
|
// FdOps
|
|
fn fdstat_get(&self) -> Result<Fdflags> {
|
|
Ok(self.fd_flags.get())
|
|
}
|
|
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
|
|
self.fd_flags.set(fdflags);
|
|
Ok(())
|
|
}
|
|
fn read_vectored(&self, _iovs: &mut [io::IoSliceMut]) -> Result<usize> {
|
|
Ok(0)
|
|
}
|
|
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
|
|
let mut total_len = 0u32;
|
|
for iov in iovs {
|
|
let len: Size = iov.len().try_into()?;
|
|
total_len = total_len.checked_add(len).ok_or(Error::Overflow)?;
|
|
}
|
|
Ok(total_len as usize)
|
|
}
|
|
}
|