diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index b3004310a5..634936a253 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -20,7 +20,13 @@ clap = { workspace = true } fluent = { workspace = true } memchr = { workspace = true } thiserror = { workspace = true } -uucore = { workspace = true, features = ["fast-inc", "fs", "pipes", "signals"] } +uucore = { workspace = true, features = [ + "fast-inc", + "fs", + "pipes", + "signals", + "stdio", +] } [dev-dependencies] divan = { workspace = true } diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 768cb16984..4ae1393d8b 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -13,12 +13,14 @@ use memchr::memchr2; use std::ffi::OsString; use std::fs::{File, metadata}; use std::io::{self, BufWriter, ErrorKind, IsTerminal, Read, Write}; -#[cfg(any(unix, target_os = "wasi"))] +#[cfg(not(windows))] use std::os::fd::AsFd; #[cfg(unix)] use std::os::unix::fs::FileTypeExt; use uucore::display::Quotable; use uucore::error::{UResult, strip_errno}; +#[cfg(not(windows))] +use uucore::stdio; use uucore::translate; use uucore::{fast_inc::fast_inc_one, format_usage}; @@ -170,14 +172,14 @@ struct OutputState { one_blank_kept: bool, } -#[cfg(any(unix, target_os = "wasi"))] +#[cfg(not(windows))] trait FdReadable: Read + AsFd {} -#[cfg(not(any(unix, target_os = "wasi")))] +#[cfg(windows)] trait FdReadable: Read {} -#[cfg(any(unix, target_os = "wasi"))] +#[cfg(not(windows))] impl FdReadable for T where T: Read + AsFd {} -#[cfg(not(any(unix, target_os = "wasi")))] +#[cfg(windows)] impl FdReadable for T where T: Read {} /// Represents an open file handle, stream, or other device @@ -491,38 +493,30 @@ fn get_input_type(path: &OsString) -> CatResult { /// Writes handle to stdout with no configuration. This allows a /// simple memory copy. fn print_fast(handle: &mut InputHandle) -> CatResult<()> { - let stdout = io::stdout(); - #[cfg(any(target_os = "linux", target_os = "android"))] - let mut stdout = stdout; // Try to use the splice() system call for faster writing. If it works, we're done. #[cfg(any(target_os = "linux", target_os = "android"))] - if uucore::pipes::splice_unbounded_auto(&handle.reader, &mut stdout)?.is_ok() { + if uucore::pipes::splice_unbounded_auto(&handle.reader, &mut *stdio::stdout_raw())?.is_ok() { return Ok(()); } // If we're not on Linux or Android, or the splice() call failed, // fall back on slower writing. - print_unbuffered(handle, stdout) + print_unbuffered(handle) } #[cfg_attr(any(target_os = "linux", target_os = "android"), inline(never))] // splice fast-path does not require this allocation -fn print_unbuffered( - handle: &mut InputHandle, - stdout: io::Stdout, -) -> CatResult<()> { - #[cfg(any(unix, target_os = "wasi"))] - let mut stdout = uucore::io::RawWriter(stdout); // use raw syscall to remove buffering - #[cfg(not(any(unix, target_os = "wasi")))] - let mut stdout = stdout.lock(); +fn print_unbuffered(handle: &mut InputHandle) -> CatResult<()> { + #[cfg(not(windows))] + let stdout = &mut *stdio::stdout_raw(); + #[cfg(windows)] + let stdout = &mut io::stdout().lock(); let mut buf = [0; 1024 * 64]; loop { match handle.reader.read(&mut buf) { Ok(0) => return Ok(()), Ok(n) => { stdout.write_all(&buf[..n]).map_err(CatError::Write)?; - // cannot use rustix::io on Windows - // really bad workaround for unbuffered write - #[cfg(not(any(unix, target_os = "wasi")))] + #[cfg(windows)] stdout.flush().map_err(CatError::Write)?; } Err(e) if e.kind() != ErrorKind::Interrupted => return Err(e.into()), diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index b1867dae76..1f73d43df8 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -203,6 +203,7 @@ safe-traversal = ["libc", "nix/fs", "nix/dir", "nix/user", "rustix/fs"] selinux = ["dep:selinux", "libc"] smack = ["xattr"] signals = ["nix/fs", "nix/signal", "nix/poll"] +stdio = ["libc"] sum = [ "digest", "hex", diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 4bce7676bd..0e3be0c8e2 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -129,6 +129,8 @@ pub mod selinux; pub mod signals; #[cfg(all(feature = "smack", target_os = "linux"))] pub mod smack; +#[cfg(all(feature = "stdio", any(unix, target_os = "hermit", target_os = "wasi")))] +pub mod stdio; #[cfg(all(feature = "systemd_logind", target_os = "linux"))] pub mod systemd_logind; #[cfg(all( diff --git a/src/uucore/src/lib/features/stdio.rs b/src/uucore/src/lib/features/stdio.rs new file mode 100644 index 0000000000..1080533336 --- /dev/null +++ b/src/uucore/src/lib/features/stdio.rs @@ -0,0 +1,80 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +//! Abstractions for raw access to stdin and stdout, without buffering. + +use core::ops::{Deref, DerefMut}; +use std::fs::File; +use std::mem::ManuallyDrop; +use std::os::fd::{FromRawFd as _, OwnedFd, RawFd}; + +pub struct StdinRaw(ManuallyDrop); + +pub struct StdoutRaw(ManuallyDrop); + +pub struct StderrRaw(ManuallyDrop); + +pub fn stdin_raw() -> StdinRaw { + // SAFETY: We ensure that the file descriptor is never closed by + // wrapping the `File` in `ManuallyDrop`. + let fd = unsafe { OwnedFd::from_raw_fd(libc::STDIN_FILENO as RawFd) }; + StdinRaw(ManuallyDrop::new(File::from(fd))) +} + +pub fn stdout_raw() -> StdoutRaw { + // SAFETY: We ensure that the file descriptor is never closed by + // wrapping the `File` in `ManuallyDrop`. + let fd = unsafe { OwnedFd::from_raw_fd(libc::STDOUT_FILENO as RawFd) }; + StdoutRaw(ManuallyDrop::new(File::from(fd))) +} + +pub fn stderr_raw() -> StderrRaw { + // SAFETY: We ensure that the file descriptor is never closed by + // wrapping the `File` in `ManuallyDrop`. + let fd = unsafe { OwnedFd::from_raw_fd(libc::STDERR_FILENO as RawFd) }; + StderrRaw(ManuallyDrop::new(File::from(fd))) +} + +impl Deref for StdinRaw { + type Target = File; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for StdinRaw { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Deref for StdoutRaw { + type Target = File; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for StdoutRaw { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Deref for StderrRaw { + type Target = File; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for StderrRaw { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 85ccbaa332..85b163c2e8 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -77,6 +77,8 @@ pub use crate::features::quoting_style; pub use crate::features::ranges; #[cfg(feature = "ringbuffer")] pub use crate::features::ringbuffer; +#[cfg(all(feature = "stdio", any(unix, target_os = "hermit", target_os = "wasi")))] +pub use crate::features::stdio; #[cfg(feature = "sum")] pub use crate::features::sum; #[cfg(all(feature = "systemd_logind", target_os = "linux"))]