Skip to content

Commit d31a388

Browse files
Auto merge of #150668 - the8472:stdio-swap, r=<try>
Unix implementation for stdio set/take/replace try-job: x86_64-gnu-aux
2 parents 3fda0e4 + f54d42e commit d31a388

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed

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

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,138 @@
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+
let _ = (this, other);
226+
Err(io::Error::UNSUPPORTED_PLATFORM)
227+
}
228+
}
229+
}

‎src/tools/miri/src/shims/files.rs‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,15 @@ impl FileDescription for io::Stdin {
249249
finish.call(ecx, result)
250250
}
251251

252+
fn destroy<'tcx>(
253+
self,
254+
_self_id: FdId,
255+
_communicate_allowed: bool,
256+
_ecx: &mut MiriInterpCx<'tcx>,
257+
) -> InterpResult<'tcx, io::Result<()>> {
258+
interp_ok(Ok(()))
259+
}
260+
252261
fn is_tty(&self, communicate_allowed: bool) -> bool {
253262
communicate_allowed && self.is_terminal()
254263
}
@@ -279,6 +288,15 @@ impl FileDescription for io::Stdout {
279288
finish.call(ecx, result)
280289
}
281290

291+
fn destroy<'tcx>(
292+
self,
293+
_self_id: FdId,
294+
_communicate_allowed: bool,
295+
_ecx: &mut MiriInterpCx<'tcx>,
296+
) -> InterpResult<'tcx, io::Result<()>> {
297+
interp_ok(Ok(()))
298+
}
299+
282300
fn is_tty(&self, communicate_allowed: bool) -> bool {
283301
communicate_allowed && self.is_terminal()
284302
}
@@ -289,6 +307,15 @@ impl FileDescription for io::Stderr {
289307
"stderr"
290308
}
291309

310+
fn destroy<'tcx>(
311+
self,
312+
_self_id: FdId,
313+
_communicate_allowed: bool,
314+
_ecx: &mut MiriInterpCx<'tcx>,
315+
) -> InterpResult<'tcx, io::Result<()>> {
316+
interp_ok(Ok(()))
317+
}
318+
292319
fn write<'tcx>(
293320
self: FileDescriptionRef<Self>,
294321
_communicate_allowed: bool,
@@ -436,6 +463,15 @@ impl FileDescription for NullOutput {
436463
// We just don't write anything, but report to the user that we did.
437464
finish.call(ecx, Ok(len))
438465
}
466+
467+
fn destroy<'tcx>(
468+
self,
469+
_self_id: FdId,
470+
_communicate_allowed: bool,
471+
_ecx: &mut MiriInterpCx<'tcx>,
472+
) -> InterpResult<'tcx, io::Result<()>> {
473+
interp_ok(Ok(()))
474+
}
439475
}
440476

441477
/// Internal type of a file-descriptor - this is what [`FdTable`] expects

0 commit comments

Comments
 (0)