From 3b19c5bbab1e398252a91e618d512432fd9c56dc Mon Sep 17 00:00:00 2001 From: oech3 <79379754+oech3@users.noreply.github.com> Date: Fri, 3 Apr 2026 02:43:23 +0900 Subject: [PATCH] uucore: add a function returning /dev/null to use splice() for wc,dd,tail --- src/uu/wc/src/count_fast.rs | 15 +-------------- src/uucore/src/lib/features/pipes.rs | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/uu/wc/src/count_fast.rs b/src/uu/wc/src/count_fast.rs index 247ef7d297f..ea334a8e72e 100644 --- a/src/uu/wc/src/count_fast.rs +++ b/src/uu/wc/src/count_fast.rs @@ -9,8 +9,6 @@ use uucore::hardware::SimdPolicy; use super::WordCountable; -#[cfg(any(target_os = "linux", target_os = "android"))] -use std::fs::OpenOptions; use std::io::{self, ErrorKind, Read}; #[cfg(unix)] @@ -41,18 +39,7 @@ const BUF_SIZE: usize = 256 * 1024; #[inline] #[cfg(any(target_os = "linux", target_os = "android"))] fn count_bytes_using_splice(fd: &impl AsFd) -> Result { - let null_file = OpenOptions::new() - .write(true) - .open("/dev/null") - .map_err(|_| 0_usize)?; - let null_rdev = rustix::fs::fstat(null_file.as_fd()) - .map_err(|_| 0_usize)? - .st_rdev as libc::dev_t; - if (libc::major(null_rdev), libc::minor(null_rdev)) != (1, 3) { - // This is not a proper /dev/null, writing to it is probably bad - // Bit of an edge case, but it has been known to happen - return Err(0); - } + let null_file = uucore::pipes::dev_null().ok_or(0_usize)?; // todo: avoid generating broker if input is pipe (fcntl_setpipe_size succeed) and directly splice() to /dev/null to save RAM usage let (pipe_rd, pipe_wr) = pipe().map_err(|_| 0_usize)?; diff --git a/src/uucore/src/lib/features/pipes.rs b/src/uucore/src/lib/features/pipes.rs index 89cff07ab27..c4fc19b2123 100644 --- a/src/uucore/src/lib/features/pipes.rs +++ b/src/uucore/src/lib/features/pipes.rs @@ -18,6 +18,7 @@ pub const MAX_ROOTLESS_PIPE_SIZE: usize = 1024 * 1024; /// Returns two `File` objects: everything written to the second can be read /// from the first. /// This is used only for resolving the limitation for splice: one of a input or output should be pipe +#[inline] #[cfg(any(target_os = "linux", target_os = "android"))] pub fn pipe() -> std::io::Result<(File, File)> { let (read, write) = rustix::pipe::pipe()?; @@ -36,6 +37,7 @@ pub fn pipe() -> std::io::Result<(File, File)> { /// To get around this requirement, consider splicing from your source into /// a [`pipe`] and then from the pipe into your target (with `splice_exact`): /// this is still very efficient. +#[inline] #[cfg(any(target_os = "linux", target_os = "android"))] pub fn splice(source: &impl AsFd, target: &impl AsFd, len: usize) -> std::io::Result { Ok(rustix::pipe::splice( @@ -53,6 +55,7 @@ pub fn splice(source: &impl AsFd, target: &impl AsFd, len: usize) -> std::io::Re /// Exactly `len` bytes are moved from `source` into `target`. /// /// Panics if `source` runs out of data before `len` bytes have been moved. +#[inline] #[cfg(any(target_os = "linux", target_os = "android"))] pub fn splice_exact(source: &impl AsFd, target: &impl AsFd, len: usize) -> std::io::Result<()> { let mut left = len; @@ -63,3 +66,22 @@ pub fn splice_exact(source: &impl AsFd, target: &impl AsFd, len: usize) -> std:: } Ok(()) } + +/// Return verified /dev/null +/// +/// `splice` to /dev/null is faster than `read` when we skip or count the input which is not able to seek +#[inline] +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn dev_null() -> Option { + let null = std::fs::OpenOptions::new() + .write(true) + .open("/dev/null") + .ok()?; + let stat = rustix::fs::fstat(&null).ok()?; + let dev = stat.st_rdev; + if (rustix::fs::major(dev), rustix::fs::minor(dev)) == (1, 3) { + Some(null) + } else { + None + } +}