|
92 | 92 |
|
93 | 93 | #![stable(feature = "rust1", since = "1.0.0")] |
94 | 94 |
|
| 95 | +use crate::io::{self, Stderr, StderrLock, Stdin, StdinLock, Stdout, StdoutLock, Write}; |
95 | 96 | #[stable(feature = "rust1", since = "1.0.0")] |
96 | 97 | pub use crate::os::fd::*; |
| 98 | +#[allow(unused_imports)] // not used on all targets |
| 99 | +use crate::sys::cvt; |
97 | 100 |
|
98 | 101 | // Tests for this module |
99 | 102 | #[cfg(test)] |
100 | 103 | 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 | +} |
0 commit comments