diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 66bd69dfc85..3890c6e0126 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -487,8 +487,9 @@ fn print_fast(handle: &mut InputHandle) -> CatResult<()> { { // If we're on Linux or Android, try to use the splice() system call // for faster writing. If it works, we're done. - if !splice::write_fast_using_splice(handle, &mut stdout)? { - return Ok(()); + match splice::write_fast_using_splice(handle, &mut stdout)? { + uucore::pipes::SpliceState::Ended => return Ok(()), + uucore::pipes::SpliceState::Fallback => {} } } // If we're not on Linux or Android, or the splice() call failed, diff --git a/src/uu/cat/src/splice.rs b/src/uu/cat/src/splice.rs index 5b4f25dbf67..b3e94a08dec 100644 --- a/src/uu/cat/src/splice.rs +++ b/src/uu/cat/src/splice.rs @@ -13,17 +13,23 @@ use uucore::pipes::{MAX_ROOTLESS_PIPE_SIZE, might_fuse, splice}; /// without copying between kernel and user spaces. This results in a large /// speedup. /// -/// The `bool` in the result value indicates if we need to fall back to normal -/// copying or not. False means we don't have to. +/// The `SpliceState` in the result value indicates if we need to fall back to +/// normal copying or not. `SpliceState::Ended` means we don't have to. #[inline] pub(super) fn write_fast_using_splice( handle: &InputHandle, write_fd: &mut S, -) -> CatResult { - let res = match splice(&handle.reader, &write_fd, MAX_ROOTLESS_PIPE_SIZE) { +) -> CatResult { + let splice_state = match splice(&handle.reader, &write_fd, MAX_ROOTLESS_PIPE_SIZE) { Ok(_) => uucore::pipes::splice_unbounded(&handle.reader, write_fd)?, // both of in/output are not pipe _ => uucore::pipes::splice_unbounded_broker(&handle.reader, write_fd)?, }; - Ok(res || might_fuse(&handle.reader)) + let final_state = match splice_state { + uucore::pipes::SpliceState::Ended if might_fuse(&handle.reader) => { + uucore::pipes::SpliceState::Fallback + } + state => state, + }; + Ok(final_state) } diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 72f6e57c0e4..4e2010ed340 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -591,8 +591,13 @@ fn print_target_section< } else { // zero-copy fast-path #[cfg(any(target_os = "linux", target_os = "android"))] - if uucore::pipes::splice_unbounded_broker(file, &mut stdout)? { - io::copy(file, &mut stdout)?; + { + match uucore::pipes::splice_unbounded_broker(file, &mut stdout)? { + uucore::pipes::SpliceState::Ended => return Ok(()), + uucore::pipes::SpliceState::Fallback => { + io::copy(file, &mut stdout)?; + } + } } #[cfg(not(any(target_os = "linux", target_os = "android")))] io::copy(file, &mut stdout)?; diff --git a/src/uucore/src/lib/features/buf_copy/linux.rs b/src/uucore/src/lib/features/buf_copy/linux.rs index 0af494754b5..99b7589e333 100644 --- a/src/uucore/src/lib/features/buf_copy/linux.rs +++ b/src/uucore/src/lib/features/buf_copy/linux.rs @@ -42,18 +42,18 @@ where // If we're on Linux or Android, try to use the splice() system call // for faster writing. If it works, we're done. // todo: bypass broker pipe this if input or output is pipe. We use this mostly for stream. - if !crate::pipes::splice_unbounded_broker(src, dest)? { - return Ok(()); + match crate::pipes::splice_unbounded_broker(src, dest)? { + crate::pipes::SpliceState::Ended => Ok(()), + crate::pipes::SpliceState::Fallback => { + std::io::copy(src, dest)?; + + // If the splice() call failed and there has been some data written to + // stdout via while loop above AND there will be second splice() call + // that will succeed, data pushed through splice will be output before + // the data buffered in stdout.lock. Therefore additional explicit flush + // is required here. + dest.flush()?; + Ok(()) + } } - - // If the splice() call failed, fall back on slower writing. - std::io::copy(src, dest)?; - - // If the splice() call failed and there has been some data written to - // stdout via while loop above AND there will be second splice() call - // that will succeed, data pushed through splice will be output before - // the data buffered in stdout.lock. Therefore additional explicit flush - // is required here. - dest.flush()?; - Ok(()) } diff --git a/src/uucore/src/lib/features/pipes.rs b/src/uucore/src/lib/features/pipes.rs index d3ee83300e7..2b978d0fd95 100644 --- a/src/uucore/src/lib/features/pipes.rs +++ b/src/uucore/src/lib/features/pipes.rs @@ -40,6 +40,12 @@ pub fn pipe(s: usize) -> std::io::Result<(File, File) Ok((File::from(read), File::from(write))) } +#[cfg(any(target_os = "linux", target_os = "android"))] +pub enum SpliceState { + Ended, + Fallback, +} + /// Less noisy wrapper around [`rustix::pipe::splice`]. /// /// Up to `len` bytes are moved from `source` to `target`. Returns the number @@ -81,11 +87,11 @@ pub fn might_fuse(source: &impl AsFd) -> bool { } /// splice all of source to dest -/// return true if we need read/write fallback -/// fails if one of in/output should be pipe +/// return `SpliceState` indicating if we need read/write fallback +/// fallback if one of in/output should be pipe #[inline] #[cfg(any(target_os = "linux", target_os = "android"))] -pub fn splice_unbounded(source: &R, dest: &mut S) -> std::io::Result +pub fn splice_unbounded(source: &R, dest: &mut S) -> std::io::Result where R: Read + AsFd, S: AsFd, @@ -99,19 +105,19 @@ where loop { match splice(&source, &dest, MAX_ROOTLESS_PIPE_SIZE) { Ok(1..) => {} - Ok(0) => return Ok(false), - Err(_) => return Ok(true), + Ok(0) => return Ok(SpliceState::Ended), + Err(_) => return Ok(SpliceState::Fallback), } } } /// force-splice source to dest even both of them are not pipe -/// return true if we need read/write fallback +/// return `SpliceState` indicating if we need read/write fallback /// /// This should not be used if one of them are pipe to save resources #[inline] #[cfg(any(target_os = "linux", target_os = "android"))] -pub fn splice_unbounded_broker(source: &R, dest: &mut S) -> std::io::Result +pub fn splice_unbounded_broker(source: &R, dest: &mut S) -> std::io::Result where R: Read + AsFd, S: AsFd, @@ -121,7 +127,7 @@ where .get_or_init(|| pipe::(MAX_ROOTLESS_PIPE_SIZE).ok()) .as_ref() else { - return Ok(true); + return Ok(SpliceState::Fallback); }; // improve throughput // no need to increase pipe size of input fd since @@ -131,7 +137,7 @@ where loop { match splice(&source, &pipe_wr, MAX_ROOTLESS_PIPE_SIZE) { - Ok(0) => return Ok(false), + Ok(0) => return Ok(SpliceState::Ended), Ok(n) => { if splice_exact(&pipe_rd, dest, n).is_err() { // If the first splice manages to copy to the intermediate @@ -143,10 +149,10 @@ where let mut drain = Vec::with_capacity(n); pipe_rd.take(n as u64).read_to_end(&mut drain)?; crate::io::RawWriter(&dest).write_all(&drain)?; - return Ok(true); + return Ok(SpliceState::Fallback); } } - Err(_) => return Ok(true), + Err(_) => return Ok(SpliceState::Fallback), } } }