1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 11:07:44 +00:00

Merge pull request #6965 from DaringCuteSeal/install-stream

install: implement copying from streams
This commit is contained in:
Sylvestre Ledru 2024-12-29 16:42:24 +01:00 committed by GitHub
commit 438c3c93b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 91 additions and 14 deletions

View file

@ -23,6 +23,7 @@ file_diff = { workspace = true }
libc = { workspace = true }
uucore = { workspace = true, features = [
"backup-control",
"buf-copy",
"fs",
"mode",
"perms",

View file

@ -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<Option<PathBuf>> {
}
}
/// 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(())
}

View file

@ -58,8 +58,6 @@ impl From<nix::Error> for Error {
///
/// Result of operation and bytes successfully written (as a `u64`) when
/// operation is successful.
///
pub fn copy_stream<R, S>(src: &mut R, dest: &mut S) -> UResult<u64>
where
R: Read + AsFd + AsRawFd,

View file

@ -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);
}