Skip to content

Partial implementation of close for stdin/out/err (#40032) #46063

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 144 additions & 1 deletion src/libstd/io/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ impl Stdin {
/// println!("{} bytes read", n);
/// println!("{}", input);
/// }
/// Err(error) => println!("error: {}", error),
/// Err(error) => eprintln!("error: {}", error),
/// }
/// ```
///
Expand All @@ -274,6 +274,51 @@ impl Stdin {
pub fn read_line(&self, buf: &mut String) -> io::Result<usize> {
self.lock().read_line(buf)
}

/// Lock this handle and then close it.
///
/// "Closing" standard input actually replaces it with a file open
/// on the null device. Thus, after `stdin().close()`, both stdin
/// and `libc::STDIN_FILENO` can still be used, but will read as empty.
/// However, the original file has indeed been closed; for instance,
/// if standard input is a pipe, anyone still writing to it will receive
/// a "broken pipe" notification.
///
/// # Examples
///
/// ```no_run
/// # #![feature(close_std_streams)]
/// use std::io;
///
/// let mut input = String::new();
///
/// match io::stdin().read_line(&mut input) {
/// Ok(n) => {
/// println!("{} bytes read", n);
/// println!("{}", input);
/// }
/// Err(error) => eprintln!("read error: {}", error),
/// }
///
/// if let Err(error) = io::stdin().close() {
/// eprintln!("close error: {}", error);
/// }
///
/// match io::stdin().read_line(&mut input) {
/// Ok(n) => {
/// println!("{} bytes read", n);
/// println!("{}", input);
/// }
/// Err(error) => eprintln!("read error: {}", error),
/// }
/// ```
///
/// If this program is run with two or more lines of input, it will
/// print only the first line.
#[unstable(feature = "close_std_streams", issue = "40032")]
pub fn close(&mut self) -> io::Result<()> {
self.lock().close()
}
}

#[stable(feature = "std_debug", since = "1.16.0")]
Expand Down Expand Up @@ -303,6 +348,20 @@ impl Read for Stdin {
}
}

impl<'a> StdinLock<'a> {
/// Close this file handle.
///
/// For detailed semantics of this method, see the documentation on
/// [`Stdin::close`].
///
/// [`Stdin::close`]: struct.Stdin.html#method.close
#[unstable(feature = "close_std_streams", issue = "40032")]
pub fn close(&mut self) -> io::Result<()> {
// Do this regardless of whether inner is real.
stdio::Stdin::new()?.close()
}
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<'a> Read for StdinLock<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
Expand Down Expand Up @@ -435,6 +494,33 @@ impl Stdout {
pub fn lock(&self) -> StdoutLock {
StdoutLock { inner: self.inner.lock().unwrap_or_else(|e| e.into_inner()) }
}

/// Lock this handle and then close it.
///
/// "Closing" standard output actually replaces it with a file open
/// on the null device. Thus, after `stdout().close()`, both stdout
/// and `libc::STDOUT_FILENO` can still be used, but will discard
/// everything written to them. However, the original file has
/// indeed been closed; for instance, if standard output is a pipe,
/// whoever is reading from it will receive an end-of-file notification.
///
/// # Examples
///
/// ```no_run
/// # #![feature(close_std_streams)]
/// use std::io;
///
/// println!("this line is printed");
/// if let Err(error) = io::stdout().close() {
/// eprintln!("close error: {}", error);
/// } else {
/// println!("this line is discarded");
/// }
/// ```
#[unstable(feature = "close_std_streams", issue = "40032")]
pub fn close(&mut self) -> io::Result<()> {
self.lock().close()
}
}

#[stable(feature = "std_debug", since = "1.16.0")]
Expand All @@ -459,6 +545,21 @@ impl Write for Stdout {
self.lock().write_fmt(args)
}
}

impl<'a> StdoutLock<'a> {
/// Close this file handle.
///
/// For detailed semantics of this method, see the documentation on
/// [`Stdout::close`].
///
/// [`Stdout::close`]: struct.Stdout.html#method.close
#[unstable(feature = "close_std_streams", issue = "40032")]
pub fn close(&mut self) -> io::Result<()> {
// Do this regardless of whether inner is real.
stdio::Stdout::new()?.close()
}
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<'a> Write for StdoutLock<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
Expand Down Expand Up @@ -570,6 +671,33 @@ impl Stderr {
pub fn lock(&self) -> StderrLock {
StderrLock { inner: self.inner.lock().unwrap_or_else(|e| e.into_inner()) }
}

/// Lock this handle and then close it.
///
/// "Closing" standard error actually replaces it with a file open
/// on the null device. Thus, after `stderr().close()`, both stderr
/// and `libc::STDERR_FILENO` can still be used, but will discard
/// everything written to them. However, the original file has
/// indeed been closed; for instance, if standard error is a pipe,
/// whoever is reading from it will receive an end-of-file notification.
///
/// # Examples
///
/// ```no_run
/// # #![feature(close_std_streams)]
/// use std::io;
///
/// eprintln!("this line is printed");
/// if let Err(error) = io::stdout().close() {
/// eprintln!("close error: {}", error);
/// } else {
/// eprintln!("this line is discarded");
/// }
/// ```
#[unstable(feature = "close_std_streams", issue = "40032")]
pub fn close(&mut self) -> io::Result<()> {
self.lock().close()
}
}

#[stable(feature = "std_debug", since = "1.16.0")]
Expand All @@ -594,6 +722,21 @@ impl Write for Stderr {
self.lock().write_fmt(args)
}
}

impl<'a> StderrLock<'a> {
/// Close this file handle.
///
/// For detailed semantics of this method, see the documentation on
/// [`Stderr::close`].
///
/// [`Stderr::close`]: struct.Stderr.html#method.close
#[unstable(feature = "close_std_streams", issue = "40032")]
pub fn close(&mut self) -> io::Result<()> {
// Do this regardless of whether inner is real.
stdio::Stderr::new()?.close()
}
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<'a> Write for StderrLock<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
Expand Down
6 changes: 6 additions & 0 deletions src/libstd/sys/redox/fd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ impl FileDesc {
}
cvt(syscall::fcntl(self.fd, syscall::F_SETFL, flags)).and(Ok(()))
}

pub fn replace(&mut self, other: FileDesc) -> io::Result<()> {
let fd = cvt(syscall::dup2(other.fd, self.fd, &[]))?;
assert!(fd == self.fd);
Ok(())
}
}

impl<'a> Read for &'a FileDesc {
Expand Down
40 changes: 40 additions & 0 deletions src/libstd/sys/redox/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,23 @@
// except according to those terms.

use io;
use mem::ManuallyDrop;
use sys::{cvt, syscall};
use sys::fd::FileDesc;

pub struct Stdin(());
pub struct Stdout(());
pub struct Stderr(());

// FIXME: This duplicates code from process.rs.
fn open_null_device (readable: bool) -> io::Result<FileDesc> {
let mut opts = OpenOptions::new();
opts.read(readable);
opts.write(!readable);
let fd = File::open(Path::new("null:"), &opts)?;
Ok(fd.into_fd())
}

impl Stdin {
pub fn new() -> io::Result<Stdin> { Ok(Stdin(())) }

Expand All @@ -25,6 +35,20 @@ impl Stdin {
fd.into_raw();
ret
}

pub fn close(&mut self) -> io::Result<()> {
// To close stdin, atomically replace the file underlying fd 0
// with the null device. This protects against code (perhaps
// in third-party libraries) that assumes fd 0 is always open
// and always refers to the same thing as stdin.
//
// This function does not "drain" any not-yet-read input.

// fd 0 itself should never actually be closed.
let mut fd = ManuallyDrop::new(FileDesc::new(0));
fd.replace(open_null_device(true)?)?;
Ok(())
}
}

impl Stdout {
Expand All @@ -40,6 +64,14 @@ impl Stdout {
pub fn flush(&self) -> io::Result<()> {
cvt(syscall::fsync(1)).and(Ok(()))
}

pub fn close(&mut self) -> io::Result<()> {
// See commentary for Stdin::close.

let mut fd = ManuallyDrop::new(FileDesc::new(2));
fd.replace(open_null_device(false)?)?;
Ok(())
}
}

impl Stderr {
Expand All @@ -55,6 +87,14 @@ impl Stderr {
pub fn flush(&self) -> io::Result<()> {
cvt(syscall::fsync(2)).and(Ok(()))
}

pub fn close(&mut self) -> io::Result<()> {
// See commentary for Stdin::close.

let mut fd = ManuallyDrop::new(FileDesc::new(2));
fd.replace(open_null_device(false)?)?;
Ok(())
}
}

// FIXME: right now this raw stderr handle is used in a few places because
Expand Down
24 changes: 24 additions & 0 deletions src/libstd/sys/unix/fd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,30 @@ impl FileDesc {
}
cvt(unsafe { libc::fcntl(fd, libc::F_DUPFD, 0) }).and_then(make_filedesc)
}

/// Atomically replace the open file referred to by `self` with
/// the open file referred to by `other`. This does not change
/// the file descriptor _number_ within self; rather, it changes
/// what open file that number refers to.
///
/// On success, the file formerly referred to by `self` may have
/// been closed (if there was no other descriptor referring to
/// it); if this is undesirable, call duplicate() first.
/// On failure, `self` is unchanged.
///
/// Regardless of success or failure, `other` is consumed, which
/// means the file descriptor formerly held by `other` will be
/// closed.
///
/// The file underlying `self` is replaced atomically, but the
/// _overall_ operation is not atomic; concurrent threads that snoop on
/// the set of valid file descriptors (which they shouldn't) can observe
/// intermediate states.
pub fn replace(&mut self, other: FileDesc) -> io::Result<()> {
let fd = cvt(unsafe { libc::dup2(other.fd, self.fd) })?;
assert!(fd == self.fd);
Ok(())
}
}

impl<'a> Read for &'a FileDesc {
Expand Down
46 changes: 46 additions & 0 deletions src/libstd/sys/unix/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,27 @@

use io;
use libc;
use mem::ManuallyDrop;
use sys::fd::FileDesc;
use sys::fs::{File, OpenOptions};
use ffi::CStr;

pub struct Stdin(());
pub struct Stdout(());
pub struct Stderr(());

// FIXME: This duplicates code from process_common.rs.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, would it be possible to extract the two to common code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, where should it live? File::open_bit_bucket maybe?

fn open_null_device (readable: bool) -> io::Result<FileDesc> {
let mut opts = OpenOptions::new();
opts.read(readable);
opts.write(!readable);
let path = unsafe {
CStr::from_ptr("/dev/null\0".as_ptr() as *const _)
};
let fd = File::open_c(&path, &opts)?;
Ok(fd.into_fd())
}

impl Stdin {
pub fn new() -> io::Result<Stdin> { Ok(Stdin(())) }

Expand All @@ -25,6 +40,21 @@ impl Stdin {
fd.into_raw();
ret
}

pub fn close(&mut self) -> io::Result<()> {
// To close stdin, atomically replace the file underlying
// STDIN_FILENO with the null device. This protects against
// code (perhaps in third-party libraries) that assumes
// STDIN_FILENO is always open and always refers to the same
// thing as stdin.
//
// This function does not "drain" any not-yet-read input.

// STDIN_FILENO itself should never actually be closed.
let mut fd = ManuallyDrop::new(FileDesc::new(libc::STDIN_FILENO));
fd.replace(open_null_device(true)?)?;
Ok(())
}
}

impl Stdout {
Expand All @@ -40,6 +70,14 @@ impl Stdout {
pub fn flush(&self) -> io::Result<()> {
Ok(())
}

pub fn close(&mut self) -> io::Result<()> {
// See commentary for Stdin::close.

let mut fd = ManuallyDrop::new(FileDesc::new(libc::STDOUT_FILENO));
fd.replace(open_null_device(false)?)?;
Ok(())
}
}

impl Stderr {
Expand All @@ -55,6 +93,14 @@ impl Stderr {
pub fn flush(&self) -> io::Result<()> {
Ok(())
}

pub fn close(&mut self) -> io::Result<()> {
// See commentary for Stdin::close.

let mut fd = ManuallyDrop::new(FileDesc::new(libc::STDERR_FILENO));
fd.replace(open_null_device(false)?)?;
Ok(())
}
}

// FIXME: right now this raw stderr handle is used in a few places because
Expand Down
Loading