From 0d858f42762fb718001bcb9ac5ec2225bb6b6f14 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Thu, 28 Nov 2024 20:48:33 +0700 Subject: [PATCH 01/21] install: implement copying from streams --- Cargo.lock | 1 + src/uu/install/Cargo.toml | 4 +- src/uu/install/src/install.rs | 103 ++++++++++++++++++++++++++++++---- src/uu/install/src/splice.rs | 75 +++++++++++++++++++++++++ tests/by-util/test_install.rs | 50 +++++++++++++++++ 5 files changed, 220 insertions(+), 13 deletions(-) create mode 100644 src/uu/install/src/splice.rs 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/install/Cargo.toml b/src/uu/install/Cargo.toml index 809a1dd687a..c77d64b2bf4 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -18,9 +18,10 @@ 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", "fs", @@ -28,6 +29,7 @@ uucore = { workspace = true, features = [ "perms", "entries", "process", + "pipes", ] } [[bin]] diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 331a50f6741..81be14bd4c5 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -10,13 +10,16 @@ mod mode; use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use file_diff::diff; use filetime::{set_file_times, FileTime}; + +#[cfg(any(target_os = "linux", target_os = "android"))] +mod splice; + 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}; @@ -29,6 +32,11 @@ use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel}; use uucore::process::{getegid, geteuid}; 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"; @@ -127,6 +135,16 @@ impl Display for InstallError { } } +#[cfg(unix)] +pub(crate) trait FdReadable: Read + AsFd + AsRawFd {} +#[cfg(not(unix))] +trait FdReadable: Read {} + +#[cfg(unix)] +impl FdReadable for T where T: Read + AsFd + AsRawFd {} +#[cfg(not(unix))] +impl FdReadable for T where T: Read {} + #[derive(Clone, Eq, PartialEq)] pub enum MainFunction { /// Create directories @@ -736,7 +754,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 !splice::write_fast_using_splice(handle, &target_file.as_fd())? { + 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 +835,21 @@ 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/uu/install/src/splice.rs b/src/uu/install/src/splice.rs new file mode 100644 index 00000000000..e67a3f21281 --- /dev/null +++ b/src/uu/install/src/splice.rs @@ -0,0 +1,75 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use super::{FdReadable, UResult}; + +use nix::unistd; +use std::os::{ + fd::AsFd, + unix::io::{AsRawFd, RawFd}, +}; + +use uucore::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. +/// +/// 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: &R, + write_fd: &S, +) -> UResult { + let (pipe_rd, pipe_wr) = pipe()?; + + loop { + match splice(&handle, &pipe_wr, SPLICE_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.as_raw_fd(), write_fd, n)?; + return Ok(true); + } + } + Err(_) => { + return Ok(true); + } + } + } +} + +/// Move exactly `num_bytes` bytes from `read_fd` to `write_fd`. +/// +/// Panics if not enough bytes can be read. +fn copy_exact(read_fd: RawFd, write_fd: &impl AsFd, num_bytes: usize) -> nix::Result<()> { + let mut left = num_bytes; + let mut buf = [0; BUF_SIZE]; + while left > 0 { + let read = unistd::read(read_fd, &mut buf)?; + assert_ne!(read, 0, "unexpected end of pipe"); + let mut written = 0; + while written < read { + match unistd::write(write_fd, &buf[written..read])? { + 0 => panic!(), + n => written += n, + } + } + left -= read; + } + Ok(()) +} 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); +} From 63542029005bd173fe008999eeffb00c5849f25e Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 15:30:55 +0700 Subject: [PATCH 02/21] uucore: add common splice-write functionality Splice is a Linux-specific syscall that allows direct data copying from one file descriptor to another without user-space buffer. As of now, this is used by `cp`, `cat`, and `install` when compiled for Linux and Android. --- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/features.rs | 2 + src/uucore/src/lib/features/splice.rs | 83 +++++++++++++++++++++++++++ src/uucore/src/lib/lib.rs | 2 + 4 files changed, 88 insertions(+) create mode 100644 src/uucore/src/lib/features/splice.rs 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..e305cfeaf57 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(unix, feature = "splice"))] +pub mod splice; #[cfg(all(target_os = "linux", feature = "tty"))] pub mod tty; diff --git a/src/uucore/src/lib/features/splice.rs b/src/uucore/src/lib/features/splice.rs new file mode 100644 index 00000000000..3f36575297f --- /dev/null +++ b/src/uucore/src/lib/features/splice.rs @@ -0,0 +1,83 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +//! 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::{ + io::Read, + os::{ + fd::AsFd, + unix::io::{AsRawFd, RawFd}, + }, +}; + +use super::pipes::{pipe, splice, splice_exact}; + +const SPLICE_SIZE: usize = 1024 * 128; +const BUF_SIZE: usize = 1024 * 16; + +/// `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 fn write_fast_using_splice( + handle: &R, + write_fd: &S, +) -> nix::Result<(usize, bool)> { + let (pipe_rd, pipe_wr) = pipe()?; + let mut bytes = 0; + + loop { + match splice(&handle, &pipe_wr, SPLICE_SIZE) { + Ok(n) => { + if n == 0 { + return Ok((bytes, 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.as_raw_fd(), write_fd, n)?; + return Ok((bytes, true)); + } + + bytes += n; + } + Err(_) => { + return Ok((bytes, true)); + } + } + } +} + +/// Move exactly `num_bytes` bytes from `read_fd` to `write_fd`. +/// +/// Panics if not enough bytes can be read. +fn copy_exact(read_fd: RawFd, write_fd: &impl AsFd, num_bytes: usize) -> nix::Result<()> { + let mut left = num_bytes; + let mut buf = [0; BUF_SIZE]; + while left > 0 { + let read = unistd::read(read_fd, &mut buf)?; + assert_ne!(read, 0, "unexpected end of pipe"); + let mut written = 0; + while written < read { + match unistd::write(write_fd, &buf[written..read])? { + 0 => panic!(), + n => written += n, + } + } + left -= read; + } + Ok(()) +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 6142e688d7c..a6456101565 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(unix, feature = "splice"))] +pub use crate::features::splice; #[cfg(all( unix, not(target_os = "android"), From 19d30942e555925b8b911f0e82a713e57142645e Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 15:32:51 +0700 Subject: [PATCH 03/21] cat: use Linux splice functionality from uucore --- src/uu/cat/Cargo.toml | 3 ++ src/uu/cat/src/cat.rs | 13 +++---- src/uu/cat/src/splice.rs | 74 ---------------------------------------- 3 files changed, 10 insertions(+), 80 deletions(-) delete mode 100644 src/uu/cat/src/splice.rs 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..789695789a1 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"); @@ -447,14 +448,14 @@ fn get_input_type(path: &str) -> CatResult { /// Writes handle to stdout with no configuration. This allows a /// simple memory copy. -fn write_fast(handle: &mut InputHandle) -> CatResult<()> { +fn write_fast(handle: &mut InputHandle) -> CatResult<()> { let stdout = io::stdout(); let mut stdout_lock = stdout.lock(); #[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 !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/cat/src/splice.rs b/src/uu/cat/src/splice.rs deleted file mode 100644 index 13daae84d7f..00000000000 --- a/src/uu/cat/src/splice.rs +++ /dev/null @@ -1,74 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. -use super::{CatResult, FdReadable, InputHandle}; - -use nix::unistd; -use std::os::{ - fd::AsFd, - unix::io::{AsRawFd, RawFd}, -}; - -use uucore::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. -/// -/// 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, - write_fd: &S, -) -> CatResult { - let (pipe_rd, pipe_wr) = pipe()?; - - loop { - match splice(&handle.reader, &pipe_wr, SPLICE_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.as_raw_fd(), write_fd, n)?; - return Ok(true); - } - } - Err(_) => { - return Ok(true); - } - } - } -} - -/// Move exactly `num_bytes` bytes from `read_fd` to `write_fd`. -/// -/// Panics if not enough bytes can be read. -fn copy_exact(read_fd: RawFd, write_fd: &impl AsFd, num_bytes: usize) -> nix::Result<()> { - let mut left = num_bytes; - let mut buf = [0; BUF_SIZE]; - while left > 0 { - let read = unistd::read(read_fd, &mut buf)?; - assert_ne!(read, 0, "unexpected end of pipe"); - let mut written = 0; - while written < read { - match unistd::write(write_fd, &buf[written..read])? { - 0 => panic!(), - n => written += n, - } - } - left -= read; - } - Ok(()) -} From 7bc13096e8155433d6bd85013f4a0aab974ecdd3 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 15:33:28 +0700 Subject: [PATCH 04/21] install: use Linux splice functionality from uucore --- src/uu/install/Cargo.toml | 7 +++- src/uu/install/src/install.rs | 21 +++------- src/uu/install/src/splice.rs | 75 ----------------------------------- 3 files changed, 10 insertions(+), 93 deletions(-) delete mode 100644 src/uu/install/src/splice.rs diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index c77d64b2bf4..73a58b51cf0 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -24,14 +24,17 @@ libc = { workspace = true } nix = { workspace = true } uucore = { workspace = true, features = [ "backup-control", + "entries", "fs", "mode", "perms", - "entries", - "process", "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 81be14bd4c5..ad064e9dcf4 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -10,10 +10,6 @@ mod mode; use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use file_diff::diff; use filetime::{set_file_times, FileTime}; - -#[cfg(any(target_os = "linux", target_os = "android"))] -mod splice; - use std::error::Error; use std::fmt::{Debug, Display}; use std::fs::File; @@ -30,6 +26,8 @@ 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)] @@ -135,16 +133,6 @@ impl Display for InstallError { } } -#[cfg(unix)] -pub(crate) trait FdReadable: Read + AsFd + AsRawFd {} -#[cfg(not(unix))] -trait FdReadable: Read {} - -#[cfg(unix)] -impl FdReadable for T where T: Read + AsFd + AsRawFd {} -#[cfg(not(unix))] -impl FdReadable for T where T: Read {} - #[derive(Clone, Eq, PartialEq)] pub enum MainFunction { /// Create directories @@ -779,7 +767,7 @@ fn copy_normal_file(from: &Path, to: &Path) -> UResult<()> { /// # Returns /// /// Returns an empty Result or an error in case of failure. -fn copy_stream(handle: &mut R, to: &Path) -> UResult<()> { +fn copy_stream(handle: &mut R, to: &Path) -> UResult<()> { // Overwrite the target file. let mut target_file = File::create(to)?; @@ -787,7 +775,7 @@ fn copy_stream(handle: &mut R, to: &Path) -> UResult<()> { { // 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, &target_file.as_fd())? { + if !write_fast_using_splice(handle, &target_file.as_fd())?.1 { return Ok(()); } } @@ -847,6 +835,7 @@ fn copy_file(from: &Path, to: &Path) -> UResult<()> { // 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)?, diff --git a/src/uu/install/src/splice.rs b/src/uu/install/src/splice.rs deleted file mode 100644 index e67a3f21281..00000000000 --- a/src/uu/install/src/splice.rs +++ /dev/null @@ -1,75 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -use super::{FdReadable, UResult}; - -use nix::unistd; -use std::os::{ - fd::AsFd, - unix::io::{AsRawFd, RawFd}, -}; - -use uucore::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. -/// -/// 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: &R, - write_fd: &S, -) -> UResult { - let (pipe_rd, pipe_wr) = pipe()?; - - loop { - match splice(&handle, &pipe_wr, SPLICE_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.as_raw_fd(), write_fd, n)?; - return Ok(true); - } - } - Err(_) => { - return Ok(true); - } - } - } -} - -/// Move exactly `num_bytes` bytes from `read_fd` to `write_fd`. -/// -/// Panics if not enough bytes can be read. -fn copy_exact(read_fd: RawFd, write_fd: &impl AsFd, num_bytes: usize) -> nix::Result<()> { - let mut left = num_bytes; - let mut buf = [0; BUF_SIZE]; - while left > 0 { - let read = unistd::read(read_fd, &mut buf)?; - assert_ne!(read, 0, "unexpected end of pipe"); - let mut written = 0; - while written < read { - match unistd::write(write_fd, &buf[written..read])? { - 0 => panic!(), - n => written += n, - } - } - left -= read; - } - Ok(()) -} From 82887c55712ab3a2b8fc33051509aaf1b218b799 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 15:33:38 +0700 Subject: [PATCH 05/21] cp: implement copying special files for unix --- src/uu/cp/Cargo.toml | 3 ++ src/uu/cp/src/cp.rs | 46 +++++++++++++++----- src/uu/cp/src/platform/linux.rs | 74 +++++++++++++++++++++++---------- src/uu/cp/src/platform/macos.rs | 11 +++-- src/uu/cp/src/platform/other.rs | 27 +++++++++++- 5 files changed, 123 insertions(+), 38 deletions(-) diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 6801e6a0960..f652252936b 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..061797e7d8e 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,9 +1933,15 @@ 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_stream = false; + #[cfg(not(unix))] let source_is_fifo = false; match options.copy_mode { @@ -1971,7 +1977,10 @@ fn handle_copy_mode( options, context, source_is_symlink, + #[cfg(unix)] source_is_fifo, + #[cfg(unix)] + source_is_stream, symlinked_files, )?; } @@ -1991,7 +2000,10 @@ fn handle_copy_mode( options, context, source_is_symlink, + #[cfg(unix)] source_is_fifo, + #[cfg(unix)] + source_is_stream, symlinked_files, )?; } @@ -2021,7 +2033,10 @@ fn handle_copy_mode( options, context, source_is_symlink, + #[cfg(unix)] source_is_fifo, + #[cfg(unix)] + source_is_stream, symlinked_files, )?; } @@ -2034,7 +2049,10 @@ fn handle_copy_mode( options, context, source_is_symlink, + #[cfg(unix)] source_is_fifo, + #[cfg(unix)] + source_is_stream, symlinked_files, )?; } @@ -2284,8 +2302,16 @@ fn copy_file( } if options.dereference(source_in_command_line) { - if let Ok(src) = canonicalize(source, MissingHandling::Normal, ResolveMode::Physical) { - copy_attributes(&src, dest, &options.attributes)?; + // 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. + 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)?; + } } } else { copy_attributes(source, dest, &options.attributes)?; @@ -2356,7 +2382,8 @@ fn copy_helper( 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,12 +2395,7 @@ 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 { + if source_is_fifo && options.recursive && !options.copy_contents { #[cfg(unix)] copy_fifo(dest, options.overwrite, options.debug)?; } else if source_is_symlink { @@ -2385,7 +2407,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..ba383889130 100644 --- a/src/uu/cp/src/platform/other.rs +++ b/src/uu/cp/src/platform/other.rs @@ -3,13 +3,19 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore reflink -use std::fs; +use std::fs::{self, File, OpenOptions}; +use std::io; + use std::path::Path; use quick_error::ResultExt; +use uucore::mode::get_umask; use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode}; +#[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 +23,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 +39,23 @@ 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())?; + } + } + fs::copy(source, dest).context(context)?; Ok(copy_debug) From f58f5b5924216b4de9a5819a7a359b45bcf381b1 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 15:34:18 +0700 Subject: [PATCH 06/21] tests/cp: add test for copying from anonymous pipe stream --- tests/by-util/test_cp.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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); +} From 2ebcadf69b04efb7c498a87841b10323e4fb563b Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 15:36:21 +0700 Subject: [PATCH 07/21] cp: fix Cargo.toml formatting --- src/uu/cp/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index f652252936b..43a2c7746e6 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -42,7 +42,7 @@ xattr = { workspace = true } exacl = { workspace = true, optional = true } [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] -uucore = { workspace = true, features = [ "splice" ] } +uucore = { workspace = true, features = ["splice"] } [[bin]] name = "cp" From eb4506d7f07431bca00797247cd215ddf711a704 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 15:36:34 +0700 Subject: [PATCH 08/21] install: fix Cargo.toml formatting --- src/uu/install/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index 73a58b51cf0..f9cdcb3136a 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -33,7 +33,7 @@ uucore = { workspace = true, features = [ ] } [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] -uucore = { workspace = true, features = [ "splice" ] } +uucore = { workspace = true, features = ["splice"] } [[bin]] name = "install" From d800960a39525dc8b68b2b48efdd2a83bceb3350 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 15:51:15 +0700 Subject: [PATCH 09/21] cp: remove file mode functionality import for windows --- src/uu/cp/src/platform/other.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/uu/cp/src/platform/other.rs b/src/uu/cp/src/platform/other.rs index ba383889130..f0d3cbaf4c2 100644 --- a/src/uu/cp/src/platform/other.rs +++ b/src/uu/cp/src/platform/other.rs @@ -9,6 +9,8 @@ use std::io; use std::path::Path; use quick_error::ResultExt; + +#[cfg(all(not(windows)))] use uucore::mode::get_umask; use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode}; From f1e077ddafdd6088d0645df20601b21a5fc0fce2 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 15:51:53 +0700 Subject: [PATCH 10/21] cp: correctly exit for unix under copy function at platform/other --- src/uu/cp/src/platform/other.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/cp/src/platform/other.rs b/src/uu/cp/src/platform/other.rs index f0d3cbaf4c2..0db53056c47 100644 --- a/src/uu/cp/src/platform/other.rs +++ b/src/uu/cp/src/platform/other.rs @@ -56,6 +56,7 @@ pub(crate) fn copy_on_write( if source_is_fifo { dst_file.set_permissions(src_file.metadata()?.permissions())?; } + return Ok(copy_debug); } fs::copy(source, dest).context(context)?; From fc3fb384f4ea894f17e6f79fa6c7299e6c9ff2c4 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 16:03:01 +0700 Subject: [PATCH 11/21] cat: correct i/o handle trait for non-unix systems --- src/uu/cat/src/cat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 789695789a1..e8f26c757cf 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -448,7 +448,7 @@ fn get_input_type(path: &str) -> CatResult { /// Writes handle to stdout with no configuration. This allows a /// simple memory copy. -fn write_fast(handle: &mut InputHandle) -> CatResult<()> { +fn write_fast(handle: &mut InputHandle) -> CatResult<()> { let stdout = io::stdout(); let mut stdout_lock = stdout.lock(); #[cfg(any(target_os = "linux", target_os = "android"))] From 077030ed03cbb217820ec6e07c5eb90b209f21e3 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 16:04:20 +0700 Subject: [PATCH 12/21] cp: fix style by collapsing if statement --- src/uu/cp/src/cp.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 061797e7d8e..46369cf0aa1 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -2308,10 +2308,9 @@ fn copy_file( // find a more reliable way to distinguish between named pipes and anonymous pipes. 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)?; - } + } else if let Ok(src) = canonicalize(source, MissingHandling::Normal, ResolveMode::Physical) + { + copy_attributes(&src, dest, &options.attributes)?; } } else { copy_attributes(source, dest, &options.attributes)?; From 0871ecabdb2dee1affb4cd997aad5875dab6e23f Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 16:05:08 +0700 Subject: [PATCH 13/21] cp: allow too many arguments for clippy on copy_helper --- src/uu/cp/src/cp.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 46369cf0aa1..47c33be2c34 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -2375,6 +2375,7 @@ 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, From 7f400f1d252fa7733c771ab049e0563cfb950502 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 16:07:04 +0700 Subject: [PATCH 14/21] cp: only check if file is fifo on unix --- src/uu/cp/src/cp.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 47c33be2c34..849e9dbce4a 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -2395,10 +2395,13 @@ fn copy_helper( return Err(Error::NotADirectory(dest.to_path_buf())); } + #[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( From fd066f14141bdd0c577b0fc52febe0eb43b76172 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 16:07:23 +0700 Subject: [PATCH 15/21] cp: remove unnecessary borrow on copy_on_write under platform/other --- src/uu/cp/src/platform/other.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/cp/src/platform/other.rs b/src/uu/cp/src/platform/other.rs index 0db53056c47..667e6a094af 100644 --- a/src/uu/cp/src/platform/other.rs +++ b/src/uu/cp/src/platform/other.rs @@ -44,13 +44,13 @@ pub(crate) fn copy_on_write( #[cfg(unix)] if source_is_stream { - let mut src_file = File::open(&source)?; + 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)?; + .open(dest)?; io::copy(&mut src_file, &mut dst_file).context(context)?; if source_is_fifo { From c7cfd1474a812f7e4fe48d4e797e517551993333 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 16:45:40 +0700 Subject: [PATCH 16/21] cp: fix platform-dependent code --- src/uu/cp/src/cp.rs | 10 ++++++---- src/uu/cp/src/platform/other.rs | 10 +++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 849e9dbce4a..b43e3a7705b 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1939,10 +1939,6 @@ fn handle_copy_mode( || source_file_type.is_char_device(); #[cfg(unix)] let source_is_fifo = source_file_type.is_fifo(); - #[cfg(not(unix))] - let source_is_stream = false; - #[cfg(not(unix))] - let source_is_fifo = false; match options.copy_mode { CopyMode::Link => { @@ -2306,12 +2302,18 @@ fn copy_file( // 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)?; + } } else { copy_attributes(source, dest, &options.attributes)?; } diff --git a/src/uu/cp/src/platform/other.rs b/src/uu/cp/src/platform/other.rs index 667e6a094af..191767e84d8 100644 --- a/src/uu/cp/src/platform/other.rs +++ b/src/uu/cp/src/platform/other.rs @@ -3,18 +3,18 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore reflink -use std::fs::{self, File, OpenOptions}; -use std::io; - -use std::path::Path; use quick_error::ResultExt; +use std::fs::{self, File, OpenOptions}; +use std::path::Path; -#[cfg(all(not(windows)))] +#[cfg(not(windows))] use uucore::mode::get_umask; use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode}; +#[cfg(unix)] +use std::io; #[cfg(unix)] use std::os::unix::fs::OpenOptionsExt; From 011362ca360795d9b53cdbbb37555684cd561fe8 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 16:46:56 +0700 Subject: [PATCH 17/21] uucore: correct conditions for splice module exporting --- src/uucore/src/lib/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index a6456101565..ce150f11b25 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -80,7 +80,7 @@ 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(unix, feature = "splice"))] +#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "splice"))] pub use crate::features::splice; #[cfg(all( unix, From c3e7c4e364043347742aa982e1f0fc67324f10c3 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 17:02:07 +0700 Subject: [PATCH 18/21] cp: removed unused imports for windows --- src/uu/cp/src/platform/other.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/uu/cp/src/platform/other.rs b/src/uu/cp/src/platform/other.rs index 191767e84d8..6216e1f5cba 100644 --- a/src/uu/cp/src/platform/other.rs +++ b/src/uu/cp/src/platform/other.rs @@ -5,7 +5,7 @@ // spell-checker:ignore reflink use quick_error::ResultExt; -use std::fs::{self, File, OpenOptions}; +use std::fs; use std::path::Path; #[cfg(not(windows))] @@ -13,6 +13,8 @@ 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)] From 223f7fcf461ef2c6462ceb34951d9c5894407da1 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 17:02:45 +0700 Subject: [PATCH 19/21] uucore/splice: fix import name --- src/uucore/src/lib/features/splice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/splice.rs b/src/uucore/src/lib/features/splice.rs index 3f36575297f..c4193fd58a7 100644 --- a/src/uucore/src/lib/features/splice.rs +++ b/src/uucore/src/lib/features/splice.rs @@ -15,7 +15,7 @@ use std::{ }, }; -use super::pipes::{pipe, splice, splice_exact}; +use crate::pipes::{pipe, splice, splice_exact}; const SPLICE_SIZE: usize = 1024 * 128; const BUF_SIZE: usize = 1024 * 16; From 9076413437fe261fc2cafa3dd28109dd3aaa1dfb Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 17:19:05 +0700 Subject: [PATCH 20/21] uucore/splice: conditional importing for pipe functionalities --- src/uucore/src/lib/features/splice.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uucore/src/lib/features/splice.rs b/src/uucore/src/lib/features/splice.rs index c4193fd58a7..a58929578ce 100644 --- a/src/uucore/src/lib/features/splice.rs +++ b/src/uucore/src/lib/features/splice.rs @@ -15,6 +15,7 @@ use std::{ }, }; +#[cfg(any(target_os = "linux", target_os = "android"))] use crate::pipes::{pipe, splice, splice_exact}; const SPLICE_SIZE: usize = 1024 * 128; From 75099986914bff70359cf38a0366ffe79ab1fa73 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 17:58:38 +0700 Subject: [PATCH 21/21] uucore: only export splice module in linux and android --- src/uucore/src/lib/features.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index e305cfeaf57..298bd181643 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -49,7 +49,7 @@ pub mod pipes; pub mod proc_info; #[cfg(all(unix, feature = "process"))] pub mod process; -#[cfg(all(unix, feature = "splice"))] +#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "splice"))] pub mod splice; #[cfg(all(target_os = "linux", feature = "tty"))] pub mod tty;