From 9eae6db10a532f0f5b80ebac605ea88ecbb1d0ef Mon Sep 17 00:00:00 2001 From: oech3 <79379754+oech3@users.noreply.github.com> Date: Tue, 31 Mar 2026 11:21:43 +0900 Subject: [PATCH] cat: avoid pipe() if stdout is pipe --- .../workspace.wordlist.txt | 2 +- src/uu/cat/src/splice.rs | 53 +++++++++++-------- src/uucore/src/lib/features/pipes.rs | 6 +-- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index a32ca0770..04a140215 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -367,6 +367,7 @@ uutils # * function names getcwd +setpipe # * other getlimits @@ -374,7 +375,6 @@ weblate algs wasm wasip -SETPIPE # * stty terminal flags brkint diff --git a/src/uu/cat/src/splice.rs b/src/uu/cat/src/splice.rs index 268f0296c..14e88dd6e 100644 --- a/src/uu/cat/src/splice.rs +++ b/src/uu/cat/src/splice.rs @@ -23,33 +23,40 @@ pub(super) fn write_fast_using_splice( handle: &InputHandle, write_fd: &S, ) -> CatResult { - use nix::fcntl::{FcntlArg, fcntl}; - let (pipe_rd, pipe_wr) = pipe()?; - // improve performance - let _ = fcntl( - write_fd, - FcntlArg::F_SETPIPE_SZ(MAX_ROOTLESS_PIPE_SIZE as i32), - ); - - loop { - match splice(&handle.reader, &pipe_wr, MAX_ROOTLESS_PIPE_SIZE) { - Ok(n) => { - if n == 0 { - return Ok(false); + const FIRST_PIPE_SIZE: usize = 64 * 1024; + if splice(&handle.reader, &write_fd, FIRST_PIPE_SIZE).is_ok() { + // fcntl improves performance for large file which is large overhead for small files + let _ = rustix::pipe::fcntl_setpipe_size(write_fd, MAX_ROOTLESS_PIPE_SIZE); + loop { + match splice(&handle.reader, &write_fd, MAX_ROOTLESS_PIPE_SIZE) { + Ok(1..) => {} + Ok(0) => return Ok(false), + Err(_) => return Ok(true), + } + } + } else { + // output is not pipe. Needs broker to use splice() which is high cost for small files + let (pipe_rd, pipe_wr) = pipe()?; + loop { + match splice(&handle.reader, &pipe_wr, MAX_ROOTLESS_PIPE_SIZE) { + Ok(n) => { + if n == 0 { + return Ok(false); + } + if splice_exact(&pipe_rd, write_fd, n).is_err() { + // If the first splice manages to copy to the intermediate + // pipe, but the second splice to stdout fails for some reason + // we can recover by copying the data that we have from the + // intermediate pipe to stdout using normal read/write. Then + // we tell the caller to fall back. + copy_exact(&pipe_rd, write_fd, n)?; + return Ok(true); + } } - if splice_exact(&pipe_rd, write_fd, n).is_err() { - // If the first splice manages to copy to the intermediate - // pipe, but the second splice to stdout fails for some reason - // we can recover by copying the data that we have from the - // intermediate pipe to stdout using normal read/write. Then - // we tell the caller to fall back. - copy_exact(&pipe_rd, write_fd, n)?; + Err(_) => { return Ok(true); } } - Err(_) => { - return Ok(true); - } } } } diff --git a/src/uucore/src/lib/features/pipes.rs b/src/uucore/src/lib/features/pipes.rs index 6f613558e..2615912c0 100644 --- a/src/uucore/src/lib/features/pipes.rs +++ b/src/uucore/src/lib/features/pipes.rs @@ -5,12 +5,12 @@ //! Thin zero-copy-related wrappers around functions from the `rustix` crate. +#[cfg(any(target_os = "linux", target_os = "android"))] +use rustix::pipe::{SpliceFlags, fcntl_setpipe_size}; #[cfg(any(target_os = "linux", target_os = "android"))] use std::fs::File; #[cfg(any(target_os = "linux", target_os = "android"))] use std::os::fd::AsFd; -#[cfg(any(target_os = "linux", target_os = "android"))] -use rustix::pipe::SpliceFlags; pub const MAX_ROOTLESS_PIPE_SIZE: usize = 1024 * 1024; /// A wrapper around [`rustix::pipe::pipe`] that ensures the pipe is cleaned up. @@ -22,7 +22,7 @@ pub const MAX_ROOTLESS_PIPE_SIZE: usize = 1024 * 1024; pub fn pipe() -> std::io::Result<(File, File)> { let (read, write) = rustix::pipe::pipe()?; // improve performance for splice - let _ = fcntl(&read, FcntlArg::F_SETPIPE_SZ(MAX_ROOTLESS_PIPE_SIZE as i32)); + let _ = fcntl_setpipe_size(&read, MAX_ROOTLESS_PIPE_SIZE); Ok((File::from(read), File::from(write))) }