From 6d5f909bd5e0f8d99056a6b73224a18e3cb350fb Mon Sep 17 00:00:00 2001 From: oech3 <79379754+oech3@users.noreply.github.com> Date: Tue, 12 May 2026 19:07:49 +0900 Subject: [PATCH] yes: no hang with fcntl failure --- src/uu/yes/Cargo.toml | 2 +- src/uu/yes/src/yes.rs | 16 +++++++++------- src/uucore/src/lib/features/pipes.rs | 21 ++++++++++++++++++--- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/uu/yes/Cargo.toml b/src/uu/yes/Cargo.toml index 314c2bd067f..53621f7101f 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -22,7 +22,7 @@ doctest = false clap = { workspace = true } itertools = { workspace = true } fluent = { workspace = true } -rustix = { workspace = true, features = ["pipe"] } +rustix = { workspace = true, features = ["stdio", "pipe"] } uucore = { workspace = true, features = ["pipes"] } [[bin]] diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index 22508a1b28a..24c1da253e0 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -112,22 +112,24 @@ pub fn exec(mut bytes: Vec) -> io::Result<()> { #[cfg(any(target_os = "linux", target_os = "android"))] pub fn exec(mut bytes: Vec) -> io::Result<()> { - use uucore::pipes::{pipe, splice, tee}; + use uucore::io::RawWriter; + use uucore::pipes::{pipe_exact, splice, tee}; const PAGE_SIZE: usize = 4096; let aligned = PAGE_SIZE.is_multiple_of(bytes.len()); repeat_content_to_capacity(&mut bytes); let bytes = bytes.as_slice(); - let mut stdout = io::stdout(); // no need to lock with zero-copy + let stdout = rustix::stdio::stdout(); // improve throughput - let _ = rustix::pipe::fcntl_setpipe_size(&stdout, MAX_ROOTLESS_PIPE_SIZE); + let _ = rustix::pipe::fcntl_setpipe_size(stdout, MAX_ROOTLESS_PIPE_SIZE); // don't show any error from fast-path and fallback to write for proper message - if let Ok((p_read, mut p_write)) = pipe() + // todo: zero-copy with default pipe size is logically possible when fcntl failed + if let Ok((p_read, mut p_write)) = pipe_exact(MAX_ROOTLESS_PIPE_SIZE) && p_write.write_all(bytes).is_ok() { if aligned && tee(&p_read, &stdout, MAX_ROOTLESS_PIPE_SIZE).is_ok() { while let Ok(1..) = tee(&p_read, &stdout, MAX_ROOTLESS_PIPE_SIZE) {} - } else if let Ok((broker_read, broker_write)) = pipe() { + } else if let Ok((broker_read, broker_write)) = pipe_exact(MAX_ROOTLESS_PIPE_SIZE) { // tee() cannot control offset and write to non-pipe 'hybrid: while let Ok(mut remain) = tee(&p_read, &broker_write, MAX_ROOTLESS_PIPE_SIZE) { @@ -137,7 +139,7 @@ pub fn exec(mut bytes: Vec) -> io::Result<()> { remain -= s; } else { // avoid output breakage with reduced remain even if it would not happen - stdout.write_all(&bytes[bytes.len() - remain..])?; + RawWriter(stdout).write_all(&bytes[bytes.len() - remain..])?; break 'hybrid; } } @@ -145,7 +147,7 @@ pub fn exec(mut bytes: Vec) -> io::Result<()> { } } // fallback - let mut stdout = stdout.lock(); + let mut stdout = RawWriter(stdout); loop { stdout.write_all(bytes)?; } diff --git a/src/uucore/src/lib/features/pipes.rs b/src/uucore/src/lib/features/pipes.rs index f36c79b3b45..7c74f3670ca 100644 --- a/src/uucore/src/lib/features/pipes.rs +++ b/src/uucore/src/lib/features/pipes.rs @@ -8,9 +8,8 @@ #[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::{ + fs::File, io::{Read, Write}, os::fd::AsFd, sync::OnceLock, @@ -35,13 +34,14 @@ pub fn pipe() -> std::io::Result<(File, File)> { Ok((File::from(read), File::from(write))) } -/// return pipe larger than given size and kernel's default size +/// try to return pipe larger than given size /// /// useful to save RAM usage #[inline] #[cfg(any(target_os = "linux", target_os = "android"))] fn pipe_with_size(s: usize) -> std::io::Result<(File, File)> { let (read, write) = rustix::pipe::pipe()?; + // avoid unnecessary syscall if s > KERNEL_DEFAULT_PIPE_SIZE { let _ = fcntl_setpipe_size(&read, s); } @@ -49,6 +49,21 @@ fn pipe_with_size(s: usize) -> std::io::Result<(File, File)> { Ok((File::from(read), File::from(write))) } +// used only from yes currently. We might use this for dd... +/// return pipe larger than given size +/// +/// use this if writing size to pipe should not hang +#[inline] +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn pipe_exact(s: usize) -> std::io::Result<(File, File)> { + let (read, write) = rustix::pipe::pipe()?; + if s > KERNEL_DEFAULT_PIPE_SIZE { + fcntl_setpipe_size(&read, s)?; + } + + Ok((File::from(read), File::from(write))) +} + /// Less noisy wrapper around [`rustix::pipe::splice`]. /// /// Up to `len` bytes are moved from `source` to `target`. Returns the number