From 200e4b10324e3e2d6c6405dac291e12ee61e8b04 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 20:03:44 +0700 Subject: [PATCH 1/3] install: implement copying from streams --- src/uu/install/Cargo.toml | 1 + src/uu/install/src/install.rs | 52 +++++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index 809a1dd68..88614cf11 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -23,6 +23,7 @@ file_diff = { workspace = true } libc = { workspace = true } uucore = { workspace = true, features = [ "backup-control", + "buf-copy", "fs", "mode", "perms", diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 331a50f67..cf8109377 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -12,14 +12,12 @@ 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::path::{Path, PathBuf, MAIN_SEPARATOR}; use std::process; use uucore::backup_control::{self, BackupMode}; +use uucore::buf_copy::copy_stream; use uucore::display::Quotable; use uucore::entries::{grp2gid, usr2uid}; use uucore::error::{FromIo, UError, UIoError, UResult, UUsageError}; @@ -29,6 +27,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"; @@ -736,7 +739,24 @@ 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(()) +} + +/// Copy a file from one path to another. Handles the certain cases of special +/// files (e.g character specials). /// /// # Parameters /// @@ -760,18 +780,26 @@ 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()); + }; + + // Stream-based copying to get around the limitations of std::fs::copy + #[cfg(unix)] + if ft.is_char_device() || ft.is_block_device() || ft.is_fifo() { + let mut handle = File::open(from)?; + let mut dest = File::create(to)?; + copy_stream(&mut handle, &mut dest)?; + return Ok(()); } + + copy_normal_file(from, to)?; + Ok(()) } From e07cc67b30699451b686e6bcc156193df41c054e Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 20:04:14 +0700 Subject: [PATCH 2/3] tests/install: add tests to install from stdin --- tests/by-util/test_install.rs | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index f1e3302e1..9c6e48c7b 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 a45731eed853280f030c3dce3ac88032f2d204c5 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Sun, 29 Dec 2024 20:10:53 +0700 Subject: [PATCH 3/3] uucore/buf_copy: delete empty doc-string --- src/uucore/src/lib/features/buf_copy/linux.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/uucore/src/lib/features/buf_copy/linux.rs b/src/uucore/src/lib/features/buf_copy/linux.rs index 77d25e44b..7c41f14a5 100644 --- a/src/uucore/src/lib/features/buf_copy/linux.rs +++ b/src/uucore/src/lib/features/buf_copy/linux.rs @@ -58,8 +58,6 @@ impl From for Error { /// /// Result of operation and bytes successfully written (as a `u64`) when /// operation is successful. -/// - pub fn copy_stream(src: &mut R, dest: &mut S) -> UResult where R: Read + AsFd + AsRawFd,