diff --git a/Cargo.lock b/Cargo.lock index 8397192b3f0..d53778e5945 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2862,6 +2862,7 @@ dependencies = [ "file_diff", "filetime", "libc", + "nix", "uucore", ] diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index f2df1c343ab..58e1cc18ffc 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -24,6 +24,9 @@ uucore = { workspace = true, features = ["fs", "pipes"] } [target.'cfg(unix)'.dependencies] nix = { workspace = true } +[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] +uucore = { workspace = true, features = ["splice"] } + [[bin]] name = "cat" path = "src/main.rs" diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 544af3138fd..e8f26c757cf 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -12,12 +12,12 @@ use uucore::display::Quotable; use uucore::error::UResult; use uucore::fs::FileInformation; -#[cfg(unix)] -use std::os::fd::{AsFd, AsRawFd}; - /// Linux splice support #[cfg(any(target_os = "linux", target_os = "android"))] -mod splice; +use uucore::splice::write_fast_using_splice; + +#[cfg(unix)] +use std::os::fd::{AsFd, AsRawFd}; /// Unix domain socket support #[cfg(unix)] @@ -26,6 +26,7 @@ use std::net::Shutdown; use std::os::unix::fs::FileTypeExt; #[cfg(unix)] use std::os::unix::net::UnixStream; + use uucore::{format_usage, help_about, help_usage}; const USAGE: &str = help_usage!("cat.md"); @@ -454,7 +455,7 @@ fn write_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, &stdout_lock)? { + if !write_fast_using_splice(&handle.reader, &stdout_lock)?.1 { return Ok(()); } } diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 6801e6a0960..43a2c7746e6 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -41,6 +41,9 @@ indicatif = { workspace = true } xattr = { workspace = true } exacl = { workspace = true, optional = true } +[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] +uucore = { workspace = true, features = ["splice"] } + [[bin]] name = "cp" path = "src/main.rs" diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 32168b09009..b43e3a7705b 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -10,7 +10,7 @@ use std::collections::{HashMap, HashSet}; #[cfg(not(windows))] use std::ffi::CString; use std::ffi::OsString; -use std::fs::{self, File, Metadata, OpenOptions, Permissions}; +use std::fs::{self, Metadata, OpenOptions, Permissions}; use std::io; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; @@ -1933,10 +1933,12 @@ fn handle_copy_mode( let source_is_symlink = source_file_type.is_symlink(); + #[cfg(unix)] + let source_is_stream = source_file_type.is_fifo() + || source_file_type.is_block_device() + || source_file_type.is_char_device(); #[cfg(unix)] let source_is_fifo = source_file_type.is_fifo(); - #[cfg(not(unix))] - let source_is_fifo = false; match options.copy_mode { CopyMode::Link => { @@ -1971,7 +1973,10 @@ fn handle_copy_mode( options, context, source_is_symlink, + #[cfg(unix)] source_is_fifo, + #[cfg(unix)] + source_is_stream, symlinked_files, )?; } @@ -1991,7 +1996,10 @@ fn handle_copy_mode( options, context, source_is_symlink, + #[cfg(unix)] source_is_fifo, + #[cfg(unix)] + source_is_stream, symlinked_files, )?; } @@ -2021,7 +2029,10 @@ fn handle_copy_mode( options, context, source_is_symlink, + #[cfg(unix)] source_is_fifo, + #[cfg(unix)] + source_is_stream, symlinked_files, )?; } @@ -2034,7 +2045,10 @@ fn handle_copy_mode( options, context, source_is_symlink, + #[cfg(unix)] source_is_fifo, + #[cfg(unix)] + source_is_stream, symlinked_files, )?; } @@ -2284,6 +2298,19 @@ fn copy_file( } if options.dereference(source_in_command_line) { + // HACK: when `cp` reads from inter-process pipe, its original pipe would've been + // inaccessible once we reach this part of the code as it has been closed. That means we + // can't dereference it to get its attribute. + // find a more reliable way to distinguish between named pipes and anonymous pipes. + #[cfg(unix)] + if source_metadata.file_type().is_fifo() { + copy_attributes(source, dest, &options.attributes)?; + } else if let Ok(src) = canonicalize(source, MissingHandling::Normal, ResolveMode::Physical) + { + copy_attributes(&src, dest, &options.attributes)?; + } + + #[cfg(not(unix))] if let Ok(src) = canonicalize(source, MissingHandling::Normal, ResolveMode::Physical) { copy_attributes(&src, dest, &options.attributes)?; } @@ -2350,13 +2377,15 @@ fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { /// Copy the file from `source` to `dest` either using the normal `fs::copy` or a /// copy-on-write scheme if --reflink is specified and the filesystem supports it. +#[allow(clippy::too_many_arguments)] fn copy_helper( source: &Path, dest: &Path, options: &Options, context: &str, source_is_symlink: bool, - source_is_fifo: bool, + #[cfg(unix)] source_is_fifo: bool, + #[cfg(unix)] source_is_stream: bool, symlinked_files: &mut HashSet, ) -> CopyResult<()> { if options.parents { @@ -2368,15 +2397,13 @@ fn copy_helper( return Err(Error::NotADirectory(dest.to_path_buf())); } - if source.as_os_str() == "/dev/null" { - /* workaround a limitation of fs::copy - * https://github.com/rust-lang/rust/issues/79390 - */ - File::create(dest).context(dest.display().to_string())?; - } else if source_is_fifo && options.recursive && !options.copy_contents { + #[cfg(unix)] + if source_is_fifo && options.recursive && !options.copy_contents { #[cfg(unix)] copy_fifo(dest, options.overwrite, options.debug)?; - } else if source_is_symlink { + return Ok(()); + } + if source_is_symlink { copy_link(source, dest, symlinked_files)?; } else { let copy_debug = copy_on_write( @@ -2385,7 +2412,9 @@ fn copy_helper( options.reflink_mode, options.sparse_mode, context, - #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))] + #[cfg(unix)] + source_is_stream, + #[cfg(unix)] source_is_fifo, )?; diff --git a/src/uu/cp/src/platform/linux.rs b/src/uu/cp/src/platform/linux.rs index 949bd5e03c7..51f6f86e1be 100644 --- a/src/uu/cp/src/platform/linux.rs +++ b/src/uu/cp/src/platform/linux.rs @@ -6,13 +6,16 @@ use libc::{SEEK_DATA, SEEK_HOLE}; use std::fs::{File, OpenOptions}; -use std::io::Read; +use std::io::{Read, Write}; +use std::os::fd::AsFd; use std::os::unix::fs::FileExt; use std::os::unix::fs::MetadataExt; use std::os::unix::fs::{FileTypeExt, OpenOptionsExt}; use std::os::unix::io::AsRawFd; use std::path::Path; +use uucore::splice::write_fast_using_splice; + use quick_error::ResultExt; use uucore::mode::get_umask; @@ -220,8 +223,8 @@ fn check_dest_is_fifo(dest: &Path) -> bool { } } -/// Copy the contents of the given source FIFO to the given file. -fn copy_fifo_contents

(source: P, dest: P) -> std::io::Result +/// Copy the contents of the given source stream to a given file. +fn copy_stream_contents

(is_pipe: bool, source: P, dest: P) -> std::io::Result where P: AsRef, { @@ -250,23 +253,50 @@ where .write(true) .mode(mode) .open(&dest)?; - let num_bytes_copied = std::io::copy(&mut src_file, &mut dst_file)?; - dst_file.set_permissions(src_file.metadata()?.permissions())?; - Ok(num_bytes_copied) + + let num_bytes_copied; + #[cfg(any(target_os = "linux", target_os = "android"))] + { + // If we're on Linux or Android, try to use the splice() system call + // for faster writing. If it works, we're done. + let res = write_fast_using_splice(&src_file, &dst_file.as_fd())?; + if !res.1 { + num_bytes_copied = res.0; + } else { + num_bytes_copied = std::io::copy(&mut src_file, &mut dst_file)? + .try_into() + .unwrap(); + dst_file.flush()?; + } + } + + #[cfg(not(any(target_os = "linux", target_os = "android")))] + { + num_bytes_copied = std::io::copy(&mut src_file, &mut dst_file)? + .try_into() + .unwrap(); + } + + // If stream is a character or block device, we don't need to take care of its permissions for + // now. See: https://github.com/uutils/coreutils/pull/4211 + if is_pipe { + dst_file.set_permissions(src_file.metadata()?.permissions())?; + } + + Ok(num_bytes_copied.try_into().unwrap()) } /// Copies `source` to `dest` using copy-on-write if possible. /// -/// The `source_is_fifo` flag must be set to `true` if and only if -/// `source` is a FIFO (also known as a named pipe). In this case, -/// copy-on-write is not possible, so we copy the contents using -/// [`std::io::copy`]. +/// The `source_is_stream` flag must be set to `true` if and only if +/// `source` is a stream (i.e FIFOs, block/character devices). pub(crate) fn copy_on_write( source: &Path, dest: &Path, reflink_mode: ReflinkMode, sparse_mode: SparseMode, context: &str, + source_is_stream: bool, source_is_fifo: bool, ) -> CopyResult { let mut copy_debug = CopyDebug { @@ -279,10 +309,10 @@ pub(crate) fn copy_on_write( copy_debug.sparse_detection = SparseDebug::Zeros; // Default SparseDebug val for SparseMode::Always copy_debug.reflink = OffloadReflinkDebug::No; - if source_is_fifo { + if source_is_stream { copy_debug.offload = OffloadReflinkDebug::Avoided; - copy_fifo_contents(source, dest).map(|_| ()) + copy_stream_contents(source_is_fifo, source, dest).map(|_| ()) } else { let mut copy_method = CopyMethod::Default; let result = handle_reflink_never_sparse_always(source, dest); @@ -300,10 +330,10 @@ pub(crate) fn copy_on_write( (ReflinkMode::Never, SparseMode::Never) => { copy_debug.reflink = OffloadReflinkDebug::No; - if source_is_fifo { + if source_is_stream { copy_debug.offload = OffloadReflinkDebug::Avoided; - copy_fifo_contents(source, dest).map(|_| ()) + copy_stream_contents(source_is_fifo, source, dest).map(|_| ()) } else { let result = handle_reflink_never_sparse_never(source); if let Ok(debug) = result { @@ -315,9 +345,9 @@ pub(crate) fn copy_on_write( (ReflinkMode::Never, SparseMode::Auto) => { copy_debug.reflink = OffloadReflinkDebug::No; - if source_is_fifo { + if source_is_stream { copy_debug.offload = OffloadReflinkDebug::Avoided; - copy_fifo_contents(source, dest).map(|_| ()) + copy_stream_contents(source_is_fifo, source, dest).map(|_| ()) } else { let mut copy_method = CopyMethod::Default; let result = handle_reflink_never_sparse_auto(source, dest); @@ -335,10 +365,10 @@ pub(crate) fn copy_on_write( (ReflinkMode::Auto, SparseMode::Always) => { copy_debug.sparse_detection = SparseDebug::Zeros; // Default SparseDebug val for // SparseMode::Always - if source_is_fifo { + if source_is_stream { copy_debug.offload = OffloadReflinkDebug::Avoided; - copy_fifo_contents(source, dest).map(|_| ()) + copy_stream_contents(source_is_fifo, source, dest).map(|_| ()) } else { let mut copy_method = CopyMethod::Default; let result = handle_reflink_auto_sparse_always(source, dest); @@ -356,9 +386,9 @@ pub(crate) fn copy_on_write( (ReflinkMode::Auto, SparseMode::Never) => { copy_debug.reflink = OffloadReflinkDebug::No; - if source_is_fifo { + if source_is_stream { copy_debug.offload = OffloadReflinkDebug::Avoided; - copy_fifo_contents(source, dest).map(|_| ()) + copy_stream_contents(source_is_fifo, source, dest).map(|_| ()) } else { let result = handle_reflink_auto_sparse_never(source); if let Ok(debug) = result { @@ -369,9 +399,9 @@ pub(crate) fn copy_on_write( } } (ReflinkMode::Auto, SparseMode::Auto) => { - if source_is_fifo { + if source_is_stream { copy_debug.offload = OffloadReflinkDebug::Unsupported; - copy_fifo_contents(source, dest).map(|_| ()) + copy_stream_contents(source_is_fifo, source, dest).map(|_| ()) } else { let mut copy_method = CopyMethod::Default; let result = handle_reflink_auto_sparse_auto(source, dest); diff --git a/src/uu/cp/src/platform/macos.rs b/src/uu/cp/src/platform/macos.rs index 77bdbbbdb83..9d86a2f4a34 100644 --- a/src/uu/cp/src/platform/macos.rs +++ b/src/uu/cp/src/platform/macos.rs @@ -15,15 +15,16 @@ use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug /// Copies `source` to `dest` using copy-on-write if possible. /// -/// The `source_is_fifo` flag must be set to `true` if and only if -/// `source` is a FIFO (also known as a named pipe). +/// The `source_is_stream` flag must be set to `true` if and only if +/// `source` is a stream (i.e FIFOs, block/character devices) pub(crate) fn copy_on_write( source: &Path, dest: &Path, reflink_mode: ReflinkMode, sparse_mode: SparseMode, context: &str, - source_is_fifo: bool, + source_is_stream: bool, + #[allow(unused_variables)] source_is_fifo: bool, ) -> CopyResult { if sparse_mode != SparseMode::Auto { return Err("--sparse is only supported on linux".to_string().into()); @@ -85,7 +86,9 @@ pub(crate) fn copy_on_write( } _ => { copy_debug.reflink = OffloadReflinkDebug::Yes; - if source_is_fifo { + if source_is_stream { + // `fs::copy` does not support copying special files (including streams). + // Instead, `cp` would have to make the files then use io::copy. let mut src_file = File::open(source)?; let mut dst_file = File::create(dest)?; io::copy(&mut src_file, &mut dst_file).context(context)? diff --git a/src/uu/cp/src/platform/other.rs b/src/uu/cp/src/platform/other.rs index 7ca1a5ded7c..6216e1f5cba 100644 --- a/src/uu/cp/src/platform/other.rs +++ b/src/uu/cp/src/platform/other.rs @@ -3,13 +3,23 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore reflink + +use quick_error::ResultExt; use std::fs; use std::path::Path; -use quick_error::ResultExt; +#[cfg(not(windows))] +use uucore::mode::get_umask; use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode}; +#[cfg(unix)] +use std::fs::{File, OpenOptions}; +#[cfg(unix)] +use std::io; +#[cfg(unix)] +use std::os::unix::fs::OpenOptionsExt; + /// Copies `source` to `dest` for systems without copy-on-write pub(crate) fn copy_on_write( source: &Path, @@ -17,6 +27,8 @@ pub(crate) fn copy_on_write( reflink_mode: ReflinkMode, sparse_mode: SparseMode, context: &str, + #[cfg(unix)] source_is_stream: bool, + #[cfg(unix)] source_is_fifo: bool, ) -> CopyResult { if reflink_mode != ReflinkMode::Never { return Err("--reflink is only supported on linux and macOS" @@ -31,6 +43,24 @@ pub(crate) fn copy_on_write( reflink: OffloadReflinkDebug::Unsupported, sparse_detection: SparseDebug::Unsupported, }; + + #[cfg(unix)] + if source_is_stream { + let mut src_file = File::open(source)?; + let mode = 0o622 & !get_umask(); + let mut dst_file = OpenOptions::new() + .create(true) + .write(true) + .mode(mode) + .open(dest)?; + io::copy(&mut src_file, &mut dst_file).context(context)?; + + if source_is_fifo { + dst_file.set_permissions(src_file.metadata()?.permissions())?; + } + return Ok(copy_debug); + } + fs::copy(source, dest).context(context)?; Ok(copy_debug) diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index 809a1dd687a..f9cdcb3136a 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -18,18 +18,23 @@ path = "src/install.rs" [dependencies] clap = { workspace = true } -filetime = { workspace = true } file_diff = { workspace = true } +filetime = { workspace = true } libc = { workspace = true } +nix = { workspace = true } uucore = { workspace = true, features = [ "backup-control", + "entries", "fs", "mode", "perms", - "entries", + "pipes", "process", ] } +[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] +uucore = { workspace = true, features = ["splice"] } + [[bin]] name = "install" path = "src/main.rs" diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 331a50f6741..ad064e9dcf4 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -12,11 +12,10 @@ use file_diff::diff; use filetime::{set_file_times, FileTime}; use std::error::Error; use std::fmt::{Debug, Display}; -use std::fs; use std::fs::File; -use std::os::unix::fs::MetadataExt; -#[cfg(unix)] -use std::os::unix::prelude::OsStrExt; +use std::fs::{self, metadata}; +use std::io::{Read, Write}; +use std::os::fd::{AsFd, AsRawFd}; use std::path::{Path, PathBuf, MAIN_SEPARATOR}; use std::process; use uucore::backup_control::{self, BackupMode}; @@ -27,8 +26,15 @@ use uucore::fs::dir_strip_dot_for_creation; use uucore::mode::get_umask; use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel}; use uucore::process::{getegid, geteuid}; +#[cfg(any(target_os = "linux", target_os = "android"))] +use uucore::splice::write_fast_using_splice; use uucore::{format_usage, help_about, help_usage, show, show_error, show_if_err, uio_error}; +#[cfg(unix)] +use std::os::unix::fs::{FileTypeExt, MetadataExt}; +#[cfg(unix)] +use std::os::unix::prelude::OsStrExt; + const DEFAULT_MODE: u32 = 0o755; const DEFAULT_STRIP_PROGRAM: &str = "strip"; @@ -736,7 +742,64 @@ fn perform_backup(to: &Path, b: &Behavior) -> UResult> { } } -/// Copy a file from one path to another. +/// Copy a non-special file using std::fs::copy. +/// +/// # Parameters +/// * `from` - The source file path. +/// * `to` - The destination file path. +/// +/// # Returns +/// +/// Returns an empty Result or an error in case of failure. +fn copy_normal_file(from: &Path, to: &Path) -> UResult<()> { + if let Err(err) = fs::copy(from, to) { + return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into()); + } + Ok(()) +} + +/// Read from stream into specified target file. +/// +/// # Parameters +/// * `handle` - Open file handle. +/// * `to` - The destination file path. +/// +/// # Returns +/// +/// Returns an empty Result or an error in case of failure. +fn copy_stream(handle: &mut R, to: &Path) -> UResult<()> { + // Overwrite the target file. + let mut target_file = File::create(to)?; + + #[cfg(any(target_os = "linux", target_os = "android"))] + { + // If we're on Linux or Android, try to use the splice() system call + // for faster writing. If it works, we're done. + if !write_fast_using_splice(handle, &target_file.as_fd())?.1 { + return Ok(()); + } + } + // If we're not on Linux or Android, or the splice() call failed, + // fall back on slower writing. + let mut buf = [0; 1024 * 64]; + while let Ok(n) = handle.read(&mut buf) { + if n == 0 { + break; + } + target_file.write_all(&buf[..n])?; + } + + // 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. + target_file.flush()?; + Ok(()) +} + +/// Copy a file from one path to another. Handles the certain cases of special files (e.g character +/// specials) /// /// # Parameters /// @@ -760,17 +823,22 @@ fn copy_file(from: &Path, to: &Path) -> UResult<()> { } } - if from.as_os_str() == "/dev/null" { - /* workaround a limitation of fs::copy - * https://github.com/rust-lang/rust/issues/79390 - */ - if let Err(err) = File::create(to) { + let ft = match metadata(from) { + Ok(ft) => ft.file_type(), + Err(err) => { return Err( InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into(), ); } - } else if let Err(err) = fs::copy(from, to) { - return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into()); + }; + match ft { + // Stream-based copying to get around the limitations of std::fs::copy + ft if ft.is_char_device() || ft.is_block_device() || ft.is_fifo() => { + let mut handle = File::open(from)?; + + copy_stream(&mut handle, to)?; + } + _ => copy_normal_file(from, to)?, } Ok(()) } diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index a4529f3a551..2046cc54b31 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -87,6 +87,7 @@ lines = [] format = ["itertools", "quoting-style"] mode = ["libc"] perms = ["libc", "walkdir"] +splice = ["pipes"] pipes = [] process = ["libc"] proc-info = ["tty", "walkdir"] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index cf24637f7bf..298bd181643 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -49,6 +49,8 @@ pub mod pipes; pub mod proc_info; #[cfg(all(unix, feature = "process"))] pub mod process; +#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "splice"))] +pub mod splice; #[cfg(all(target_os = "linux", feature = "tty"))] pub mod tty; diff --git a/src/uu/cat/src/splice.rs b/src/uucore/src/lib/features/splice.rs similarity index 62% rename from src/uu/cat/src/splice.rs rename to src/uucore/src/lib/features/splice.rs index 13daae84d7f..a58929578ce 100644 --- a/src/uu/cat/src/splice.rs +++ b/src/uucore/src/lib/features/splice.rs @@ -2,38 +2,46 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use super::{CatResult, FdReadable, InputHandle}; + +//! This module provides the [`write_fast_using_splice`] function to leverage the `splice` system call +//! in Linux systems, thus increasing the I/O performance of copying between two file descriptors. use nix::unistd; -use std::os::{ - fd::AsFd, - unix::io::{AsRawFd, RawFd}, +use std::{ + io::Read, + os::{ + fd::AsFd, + unix::io::{AsRawFd, RawFd}, + }, }; -use uucore::pipes::{pipe, splice, splice_exact}; +#[cfg(any(target_os = "linux", target_os = "android"))] +use crate::pipes::{pipe, splice, splice_exact}; const SPLICE_SIZE: usize = 1024 * 128; const BUF_SIZE: usize = 1024 * 16; -/// This function is called from `write_fast()` on Linux and Android. The -/// function `splice()` is used to move data between two file descriptors -/// without copying between kernel and user spaces. This results in a large -/// speedup. +/// `splice` is a Linux-specific system call used to move data between two file descriptors without +/// copying between kernel and user spaces. This results in a large speedup. +/// +/// This function reads from a file/stream `handle` and directly writes to `write_fd`. Returns the +/// amount of bytes written as a `u64`. /// /// 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. #[inline] -pub(super) fn write_fast_using_splice( - handle: &InputHandle, +pub fn write_fast_using_splice( + handle: &R, write_fd: &S, -) -> CatResult { +) -> nix::Result<(usize, bool)> { let (pipe_rd, pipe_wr) = pipe()?; + let mut bytes = 0; loop { - match splice(&handle.reader, &pipe_wr, SPLICE_SIZE) { + match splice(&handle, &pipe_wr, SPLICE_SIZE) { Ok(n) => { if n == 0 { - return Ok(false); + return Ok((bytes, false)); } if splice_exact(&pipe_rd, write_fd, n).is_err() { // If the first splice manages to copy to the intermediate @@ -42,11 +50,13 @@ pub(super) fn write_fast_using_splice( // intermediate pipe to stdout using normal read/write. Then // we tell the caller to fall back. copy_exact(pipe_rd.as_raw_fd(), write_fd, n)?; - return Ok(true); + return Ok((bytes, true)); } + + bytes += n; } Err(_) => { - return Ok(true); + return Ok((bytes, true)); } } } diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 6142e688d7c..ce150f11b25 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -80,6 +80,8 @@ pub use crate::features::pipes; pub use crate::features::process; #[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))] pub use crate::features::signals; +#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "splice"))] +pub use crate::features::splice; #[cfg(all( unix, not(target_os = "android"), diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 7a0889b0fa6..35ef064bd0f 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -5920,3 +5920,19 @@ fn test_cp_no_file() { .code_is(1) .stderr_contains("error: the following required arguments were not provided:"); } + +#[test] +#[cfg(unix)] +fn test_cp_from_stdin() { + let (at, mut ucmd) = at_and_ucmd!(); + let target = "target"; + let test_string = "Hello, World!\n"; + + ucmd.arg("/dev/fd/0") + .arg(target) + .pipe_in(test_string) + .succeeds(); + + assert!(at.file_exists(target)); + assert_eq!(at.read(target), test_string); +} diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index f1e3302e138..9c6e48c7b9d 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -1717,3 +1717,53 @@ fn test_install_root_combined() { run_and_check(&["-Cv", "c", "d"], "d", 0, 0); run_and_check(&["-Cv", "c", "d"], "d", 0, 0); } + +#[test] +#[cfg(unix)] +fn test_install_from_fifo() { + use std::fs::OpenOptions; + use std::io::Write; + use std::thread; + + let pipe_name = "pipe"; + let target_name = "target"; + let test_string = "Hello, world!\n"; + + let s = TestScenario::new(util_name!()); + s.fixtures.mkfifo(pipe_name); + assert!(s.fixtures.is_fifo(pipe_name)); + + let proc = s.ucmd().arg(pipe_name).arg(target_name).run_no_wait(); + + let pipe_path = s.fixtures.plus(pipe_name); + let thread = thread::spawn(move || { + let mut pipe = OpenOptions::new() + .write(true) + .create(false) + .open(pipe_path) + .unwrap(); + pipe.write_all(test_string.as_bytes()).unwrap(); + }); + + proc.wait().unwrap(); + thread.join().unwrap(); + + assert!(s.fixtures.file_exists(target_name)); + assert_eq!(s.fixtures.read(target_name), test_string); +} + +#[test] +#[cfg(unix)] +fn test_install_from_stdin() { + let (at, mut ucmd) = at_and_ucmd!(); + let target = "target"; + let test_string = "Hello, World!\n"; + + ucmd.arg("/dev/fd/0") + .arg(target) + .pipe_in(test_string) + .succeeds(); + + assert!(at.file_exists(target)); + assert_eq!(at.read(target), test_string); +}