Skip to content

Commit aa5e351

Browse files
committed
Unix implementation for stdio set/take/replace
1 parent e25ee6b commit aa5e351

1 file changed

Lines changed: 122 additions & 0 deletions

File tree

  • library/std/src/os/unix/io

library/std/src/os/unix/io/mod.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,131 @@
9292
9393
#![stable(feature = "rust1", since = "1.0.0")]
9494

95+
use crate::io::{self, Stderr, StderrLock, Stdin, StdinLock, Stdout, StdoutLock, Write};
9596
#[stable(feature = "rust1", since = "1.0.0")]
9697
pub use crate::os::fd::*;
9798

9899
// Tests for this module
99100
#[cfg(test)]
100101
mod tests;
102+
103+
#[unstable(feature = "stdio_swap", issue = "150667", reason = "recently added")]
104+
pub trait StdioExt {
105+
/// Redirects the stdio file descriptor to point to the file description underpinning `fd`.
106+
///
107+
/// Rust std::io write buffers (if any) are flushed, but other runtimes
108+
/// (e.g. C stdio) or libraries that acquire a clone of the file descriptor
109+
/// will not be aware of this change.
110+
///
111+
/// # Platform-specific behavior
112+
///
113+
/// This is [currently] implemented using the `dup2` system call, where available.
114+
///
115+
/// [currently]: crate::io#platform-specific-behavior
116+
///
117+
/// ```
118+
/// #![feature(stdio_swap)]
119+
/// use std::io::{self, Read, Write};
120+
/// use std::os::unix::io::StdioExt;
121+
///
122+
/// fn main() -> io::Result<()> {
123+
/// let (reader, mut writer) = io::pipe()?;
124+
/// let mut stdin = io::stdin();
125+
/// stdin.set_fd(reader)?;
126+
/// writer.write_all(b"Hello, world!")?;
127+
/// let mut buffer = vec![0; 13];
128+
/// assert_eq!(stdin.read(&mut buffer)?, 13);
129+
/// assert_eq!(&buffer, b"Hello, world!");
130+
/// Ok(())
131+
/// }
132+
/// ```
133+
fn set_fd<T: Into<OwnedFd>>(&mut self, fd: T) -> io::Result<()>;
134+
135+
/// Redirects the stdio file descriptor and returns a new `OwnedFd`
136+
/// backed by the previous file description.
137+
///
138+
/// See [`set_fd()`] for details.
139+
fn replace_fd<T: Into<OwnedFd>>(&mut self, replace_with: T) -> io::Result<OwnedFd>;
140+
141+
/// Redirects the stdio file descriptor to the null device (`/dev/null`)
142+
/// and returns a new `OwnedFd` backed by the previous file description.
143+
///
144+
/// Programs that communicate structured data via stdio can use this early in `main()` to
145+
/// extract the fds, treat them as other IO types (`File`, `UnixStream`, etc),
146+
/// apply custom buffering or avoid interference from stdio use later in the program.
147+
///
148+
/// See [`set_fd()`] for additional details.
149+
fn take_fd(&mut self) -> io::Result<OwnedFd>;
150+
}
151+
152+
macro io_ext_impl($stdio_ty:ty, $stdio_lock_ty:ty, $writer:literal) {
153+
#[unstable(feature = "stdio_swap", issue = "150667", reason = "recently added")]
154+
impl StdioExt for $stdio_ty {
155+
fn set_fd<T: Into<OwnedFd>>(&mut self, fd: T) -> io::Result<()> {
156+
self.lock().set_fd(fd)
157+
}
158+
159+
fn take_fd(&mut self) -> io::Result<OwnedFd> {
160+
self.lock().take_fd()
161+
}
162+
163+
fn replace_fd<T: Into<OwnedFd>>(&mut self, replace_with: T) -> io::Result<OwnedFd> {
164+
self.lock().replace_fd(replace_with)
165+
}
166+
}
167+
168+
#[unstable(feature = "stdio_swap", issue = "150667", reason = "recently added")]
169+
impl StdioExt for $stdio_lock_ty {
170+
fn set_fd<T: Into<OwnedFd>>(&mut self, fd: T) -> io::Result<()> {
171+
#[cfg($writer)]
172+
self.flush()?;
173+
replace_stdio_fd(self.as_fd(), fd.into())
174+
}
175+
176+
fn take_fd(&mut self) -> io::Result<OwnedFd> {
177+
let null = null_fd($writer)?;
178+
let cloned = self.as_fd().try_clone_to_owned()?;
179+
self.set_fd(null)?;
180+
Ok(cloned)
181+
}
182+
183+
fn replace_fd<T: Into<OwnedFd>>(&mut self, replace_with: T) -> io::Result<OwnedFd> {
184+
let cloned = self.as_fd().try_clone_to_owned()?;
185+
self.set_fd(replace_with)?;
186+
Ok(cloned)
187+
}
188+
}
189+
}
190+
191+
io_ext_impl!(Stdout, StdoutLock<'_>, true);
192+
io_ext_impl!(Stdin, StdinLock<'_>, false);
193+
io_ext_impl!(Stderr, StderrLock<'_>, true);
194+
195+
fn null_fd(write: bool) -> io::Result<OwnedFd> {
196+
let null_dev = crate::fs::OpenOptions::new().read(!write).write(write).open("/dev/null")?;
197+
Ok(null_dev.into())
198+
}
199+
200+
/// Replaces the underlying file descriptor with the one from `other`.
201+
/// Does not set CLOEXEC.
202+
fn replace_stdio_fd(this: BorrowedFd<'_>, other: OwnedFd) -> io::Result<()> {
203+
cfg_select! {
204+
not(any(
205+
target_arch = "wasm32",
206+
target_os = "hermit",
207+
target_os = "trusty",
208+
target_os = "motor"
209+
)) => {
210+
unsafe {
211+
if libc::dup2(other.as_raw_fd(), this.as_raw_fd()) == -1 {
212+
Err(io::Error::last_os_error())
213+
} else {
214+
Ok(())
215+
}
216+
}
217+
}
218+
_ => {
219+
Err(io::Error::UNSUPPORTED_PLATFORM)
220+
}
221+
}
222+
}

0 commit comments

Comments
 (0)