From 98c6a12045c1554fae2f2df75c44bb1c71e9f3b9 Mon Sep 17 00:00:00 2001 From: oech3 <79379754+oech3@users.noreply.github.com> Date: Mon, 23 Mar 2026 04:22:21 +0900 Subject: [PATCH] yes: use tee syscall as pipe only fast-path --- .../workspace.wordlist.txt | 2 ++ Cargo.lock | 1 + Cargo.toml | 1 + src/uu/yes/Cargo.toml | 1 + src/uu/yes/src/yes.rs | 28 +++++++++++++++++-- tests/by-util/test_yes.rs | 2 +- 6 files changed, 31 insertions(+), 4 deletions(-) diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index a5d245016c7..3618dd0e15b 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -8,6 +8,7 @@ advapi32-sys aho-corasick backtrace blake2b_simd +rustix # * uutils project uutils @@ -362,6 +363,7 @@ uutils # * function names getcwd +setpipe # * other getlimits diff --git a/Cargo.lock b/Cargo.lock index de54b50704f..776a684724e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4422,6 +4422,7 @@ dependencies = [ "clap", "fluent", "itertools 0.14.0", + "rustix", "uucore", ] diff --git a/Cargo.toml b/Cargo.toml index db9c0c40523..f40ce378d3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -435,6 +435,7 @@ rstest = "0.26.0" rstest_reuse = "0.7.0" rustc-hash = "2.1.1" rust-ini = "0.21.0" +rustix = "1.1.4" same-file = "1.0.6" self_cell = "1.0.4" selinux = "=0.6.0" diff --git a/src/uu/yes/Cargo.toml b/src/uu/yes/Cargo.toml index b8703bb55d1..b49e76befcb 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -22,6 +22,7 @@ path = "src/yes.rs" clap = { workspace = true } itertools = { workspace = true } fluent = { workspace = true } +rustix = { workspace = true, features = ["pipe"] } [target.'cfg(unix)'.dependencies] uucore = { workspace = true, features = ["pipes", "signals"] } diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index 732a1bf5877..7280cb2139d 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -13,8 +13,12 @@ use uucore::error::{UResult, USimpleError, strip_errno}; use uucore::format_usage; use uucore::translate; -// it's possible that using a smaller or larger buffer might provide better performance on some -// systems, but honestly this is good enough +#[cfg(any(target_os = "linux", target_os = "android"))] +const MAX_ROOTLESS_PIPE_SIZE: usize = 1024 * 1024; +// todo: investigate best rate +#[cfg(any(target_os = "linux", target_os = "android"))] +const BUF_SIZE: usize = MAX_ROOTLESS_PIPE_SIZE; +#[cfg(not(any(target_os = "linux", target_os = "android")))] const BUF_SIZE: usize = 16 * 1024; #[uucore::main] @@ -110,8 +114,25 @@ fn prepare_buffer(buf: &mut Vec) { pub fn exec(bytes: &[u8]) -> io::Result<()> { let stdout = io::stdout(); - let mut stdout = stdout.lock(); + #[cfg(any(target_os = "linux", target_os = "android"))] + { + use rustix::pipe::{SpliceFlags, fcntl_setpipe_size, pipe, tee}; + // fast-path for pipe. todo: port the fast-path for > file + if let Ok((p_read, p_write)) = pipe() { + let _ = fcntl_setpipe_size(&p_read, MAX_ROOTLESS_PIPE_SIZE); + let _ = fcntl_setpipe_size(&stdout, MAX_ROOTLESS_PIPE_SIZE); + if std::fs::File::from(p_write).write_all(bytes).is_ok() { + while let Ok(1..) = tee( + &p_read, + &stdout, + MAX_ROOTLESS_PIPE_SIZE, + SpliceFlags::empty(), + ) {} + } + } + } + let mut stdout = stdout.lock(); loop { stdout.write_all(bytes)?; } @@ -122,6 +143,7 @@ mod tests { use super::*; #[test] + #[cfg(not(any(target_os = "linux", target_os = "android")))] // Linux uses different buffer size fn test_prepare_buffer() { let tests = [ (150, 16350), diff --git a/tests/by-util/test_yes.rs b/tests/by-util/test_yes.rs index db460c998fc..c638915be94 100644 --- a/tests/by-util/test_yes.rs +++ b/tests/by-util/test_yes.rs @@ -70,7 +70,7 @@ fn test_long_input() { #[cfg(windows)] const TIMES: usize = 500; let arg = "abcdef".repeat(TIMES) + "\n"; - let expected_out = arg.repeat(30); + let expected_out = arg.repeat(5); run(&[&arg[..arg.len() - 1]], expected_out.as_bytes()); }