Skip to content

Commit 0f5cf5e

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

1 file changed

Lines changed: 128 additions & 0 deletions

File tree

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

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

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,137 @@
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::*;
98+
#[allow(unused_imports)] // not used on all targets
99+
use crate::sys::cvt;
97100

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

0 commit comments

Comments
 (0)