Skip to content

Commit 438c3c9

Browse files
authored
Merge pull request #6965 from DaringCuteSeal/install-stream
install: implement copying from streams
2 parents 954117f + a45731e commit 438c3c9

File tree

4 files changed

+91
-14
lines changed

4 files changed

+91
-14
lines changed

src/uu/install/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ file_diff = { workspace = true }
2323
libc = { workspace = true }
2424
uucore = { workspace = true, features = [
2525
"backup-control",
26+
"buf-copy",
2627
"fs",
2728
"mode",
2829
"perms",

src/uu/install/src/install.rs

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@ use file_diff::diff;
1212
use filetime::{set_file_times, FileTime};
1313
use std::error::Error;
1414
use std::fmt::{Debug, Display};
15-
use std::fs;
1615
use std::fs::File;
17-
use std::os::unix::fs::MetadataExt;
18-
#[cfg(unix)]
19-
use std::os::unix::prelude::OsStrExt;
16+
use std::fs::{self, metadata};
2017
use std::path::{Path, PathBuf, MAIN_SEPARATOR};
2118
use std::process;
2219
use uucore::backup_control::{self, BackupMode};
20+
use uucore::buf_copy::copy_stream;
2321
use uucore::display::Quotable;
2422
use uucore::entries::{grp2gid, usr2uid};
2523
use uucore::error::{FromIo, UError, UIoError, UResult, UUsageError};
@@ -29,6 +27,11 @@ use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel};
2927
use uucore::process::{getegid, geteuid};
3028
use uucore::{format_usage, help_about, help_usage, show, show_error, show_if_err, uio_error};
3129

30+
#[cfg(unix)]
31+
use std::os::unix::fs::{FileTypeExt, MetadataExt};
32+
#[cfg(unix)]
33+
use std::os::unix::prelude::OsStrExt;
34+
3235
const DEFAULT_MODE: u32 = 0o755;
3336
const DEFAULT_STRIP_PROGRAM: &str = "strip";
3437

@@ -736,7 +739,24 @@ fn perform_backup(to: &Path, b: &Behavior) -> UResult<Option<PathBuf>> {
736739
}
737740
}
738741

739-
/// Copy a file from one path to another.
742+
/// Copy a non-special file using std::fs::copy.
743+
///
744+
/// # Parameters
745+
/// * `from` - The source file path.
746+
/// * `to` - The destination file path.
747+
///
748+
/// # Returns
749+
///
750+
/// Returns an empty Result or an error in case of failure.
751+
fn copy_normal_file(from: &Path, to: &Path) -> UResult<()> {
752+
if let Err(err) = fs::copy(from, to) {
753+
return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into());
754+
}
755+
Ok(())
756+
}
757+
758+
/// Copy a file from one path to another. Handles the certain cases of special
759+
/// files (e.g character specials).
740760
///
741761
/// # Parameters
742762
///
@@ -760,18 +780,26 @@ fn copy_file(from: &Path, to: &Path) -> UResult<()> {
760780
}
761781
}
762782

763-
if from.as_os_str() == "/dev/null" {
764-
/* workaround a limitation of fs::copy
765-
* https://github.com/rust-lang/rust/issues/79390
766-
*/
767-
if let Err(err) = File::create(to) {
783+
let ft = match metadata(from) {
784+
Ok(ft) => ft.file_type(),
785+
Err(err) => {
768786
return Err(
769787
InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into(),
770788
);
771789
}
772-
} else if let Err(err) = fs::copy(from, to) {
773-
return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into());
790+
};
791+
792+
// Stream-based copying to get around the limitations of std::fs::copy
793+
#[cfg(unix)]
794+
if ft.is_char_device() || ft.is_block_device() || ft.is_fifo() {
795+
let mut handle = File::open(from)?;
796+
let mut dest = File::create(to)?;
797+
copy_stream(&mut handle, &mut dest)?;
798+
return Ok(());
774799
}
800+
801+
copy_normal_file(from, to)?;
802+
775803
Ok(())
776804
}
777805

src/uucore/src/lib/features/buf_copy/linux.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,6 @@ impl From<nix::Error> for Error {
5858
///
5959
/// Result of operation and bytes successfully written (as a `u64`) when
6060
/// operation is successful.
61-
///
62-
6361
pub fn copy_stream<R, S>(src: &mut R, dest: &mut S) -> UResult<u64>
6462
where
6563
R: Read + AsFd + AsRawFd,

tests/by-util/test_install.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,3 +1717,53 @@ fn test_install_root_combined() {
17171717
run_and_check(&["-Cv", "c", "d"], "d", 0, 0);
17181718
run_and_check(&["-Cv", "c", "d"], "d", 0, 0);
17191719
}
1720+
1721+
#[test]
1722+
#[cfg(unix)]
1723+
fn test_install_from_fifo() {
1724+
use std::fs::OpenOptions;
1725+
use std::io::Write;
1726+
use std::thread;
1727+
1728+
let pipe_name = "pipe";
1729+
let target_name = "target";
1730+
let test_string = "Hello, world!\n";
1731+
1732+
let s = TestScenario::new(util_name!());
1733+
s.fixtures.mkfifo(pipe_name);
1734+
assert!(s.fixtures.is_fifo(pipe_name));
1735+
1736+
let proc = s.ucmd().arg(pipe_name).arg(target_name).run_no_wait();
1737+
1738+
let pipe_path = s.fixtures.plus(pipe_name);
1739+
let thread = thread::spawn(move || {
1740+
let mut pipe = OpenOptions::new()
1741+
.write(true)
1742+
.create(false)
1743+
.open(pipe_path)
1744+
.unwrap();
1745+
pipe.write_all(test_string.as_bytes()).unwrap();
1746+
});
1747+
1748+
proc.wait().unwrap();
1749+
thread.join().unwrap();
1750+
1751+
assert!(s.fixtures.file_exists(target_name));
1752+
assert_eq!(s.fixtures.read(target_name), test_string);
1753+
}
1754+
1755+
#[test]
1756+
#[cfg(unix)]
1757+
fn test_install_from_stdin() {
1758+
let (at, mut ucmd) = at_and_ucmd!();
1759+
let target = "target";
1760+
let test_string = "Hello, World!\n";
1761+
1762+
ucmd.arg("/dev/fd/0")
1763+
.arg(target)
1764+
.pipe_in(test_string)
1765+
.succeeds();
1766+
1767+
assert!(at.file_exists(target));
1768+
assert_eq!(at.read(target), test_string);
1769+
}

0 commit comments

Comments
 (0)