cat: avoid pipe() if stdout is pipe

This commit is contained in:
oech3
2026-03-31 11:21:43 +09:00
committed by Sylvestre Ledru
parent 162abf9de4
commit 9eae6db10a
3 changed files with 34 additions and 27 deletions
+1 -1
View File
@@ -367,6 +367,7 @@ uutils
# * function names
getcwd
setpipe
# * other
getlimits
@@ -374,7 +375,6 @@ weblate
algs
wasm
wasip
SETPIPE
# * stty terminal flags
brkint
+30 -23
View File
@@ -23,33 +23,40 @@ pub(super) fn write_fast_using_splice<R: FdReadable, S: AsRawFd + AsFd>(
handle: &InputHandle<R>,
write_fd: &S,
) -> CatResult<bool> {
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);
}
}
}
}
+3 -3
View File
@@ -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)))
}