diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 7256b62074..39e46d71ee 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -30,6 +30,8 @@ use std::borrow::Cow; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use filetime::FileTime; +#[cfg(unix)] +use libc::mkfifo; use quick_error::ResultExt; use std::collections::HashSet; use std::env; @@ -43,6 +45,10 @@ use std::fs::OpenOptions; use std::io; use std::io::{stdin, stdout, Write}; use std::mem; +#[cfg(unix)] +use std::os::unix::ffi::OsStrExt; +#[cfg(unix)] +use std::os::unix::fs::{FileTypeExt, PermissionsExt}; #[cfg(target_os = "linux")] use std::os::unix::io::AsRawFd; #[cfg(windows)] @@ -55,9 +61,6 @@ use uucore::error::{set_exit_code, ExitCode, UError, UResult}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; use walkdir::WalkDir; -#[cfg(unix)] -use std::os::unix::fs::PermissionsExt; - #[cfg(target_os = "linux")] ioctl!(write ficlone with 0x94, 9; std::os::raw::c_int); @@ -150,7 +153,7 @@ pub type Target = PathBuf; pub type TargetSlice = Path; /// Specifies whether when overwrite files -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq)] pub enum ClobberMode { Force, RemoveDestination, @@ -158,7 +161,7 @@ pub enum ClobberMode { } /// Specifies whether when overwrite files -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq)] pub enum OverwriteMode { /// [Default] Always overwrite existing files Clobber(ClobberMode), @@ -1385,12 +1388,23 @@ fn copy_helper( let parent = dest.parent().unwrap_or(dest); fs::create_dir_all(parent)?; } - let is_symlink = fs::symlink_metadata(&source)?.file_type().is_symlink(); + + let file_type = fs::symlink_metadata(&source)?.file_type(); + let is_symlink = file_type.is_symlink(); + + #[cfg(unix)] + let is_fifo = file_type.is_fifo(); + #[cfg(not(unix))] + let is_fifo = false; + 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 is_fifo && options.recursive { + #[cfg(unix)] + copy_fifo(dest, options.overwrite)?; } else if is_symlink { copy_link(source, dest, symlinked_files)?; } else if options.reflink_mode != ReflinkMode::Never { @@ -1410,6 +1424,23 @@ fn copy_helper( Ok(()) } +// "Copies" a FIFO by creating a new one. This workaround is because Rust's +// built-in fs::copy does not handle FIFOs (see rust-lang/rust/issues/79390). +#[cfg(unix)] +fn copy_fifo(dest: &Path, overwrite: OverwriteMode) -> CopyResult<()> { + if dest.exists() { + overwrite.verify(dest)?; + fs::remove_file(&dest)?; + } + + let name = CString::new(dest.as_os_str().as_bytes()).unwrap(); + let err = unsafe { mkfifo(name.as_ptr(), 0o666) }; + if err == -1 { + return Err(format!("cannot create fifo {}: File exists", dest.quote()).into()); + } + Ok(()) +} + fn copy_link( source: &Path, dest: &Path, @@ -1493,7 +1524,6 @@ fn copy_on_write_macos( // Extract paths in a form suitable to be passed to a syscall. // The unwrap() is safe because they come from the command-line and so contain non nul // character. - use std::os::unix::ffi::OsStrExt; let src = CString::new(source.as_os_str().as_bytes()).unwrap(); let dst = CString::new(dest.as_os_str().as_bytes()).unwrap(); diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index cfa946d471..8ae358b313 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1463,6 +1463,20 @@ fn test_cp_archive_on_nonexistent_file() { ); } +#[test] +#[cfg(unix)] +fn test_cp_fifo() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkfifo("fifo"); + ucmd.arg("-r") + .arg("fifo") + .arg("fifo2") + .succeeds() + .no_stderr() + .no_stdout(); + assert!(at.is_fifo("fifo2")); +} + #[test] fn test_dir_recursive_copy() { let scene = TestScenario::new(util_name!());