From 7b64e5f322f91d4d745d40ae0cb8a83bea0d2711 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 9 Jul 2026 21:14:00 -0400 Subject: [PATCH 1/2] cp: don't open FIFOs/devices when preserving timestamps set_file_times opens the destination before calling futimens; opening a FIFO with no writer blocks forever. Use the path-based utimensat variant for symlinks and other non-regular files. Should make test tests/cp/preserve-mode.sh pass --- src/uu/cp/src/cp.rs | 18 +++++++++++++++++- tests/by-util/test_cp.rs | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 7129e82522..92d4b7168c 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1904,7 +1904,23 @@ pub(crate) fn copy_attributes( handle_preserve(attributes.timestamps, || -> CopyResult<()> { let atime = FileTime::from_last_access_time(&source_metadata); let mtime = FileTime::from_last_modification_time(&source_metadata); - if dest.is_symlink() { + // `set_file_times` opens the destination (O_RDONLY) before calling + // futimens; opening a FIFO or device with no peer blocks forever, and a + // socket cannot be opened at all. For symlinks and these special files + // use the path-based, no-follow variant, which sets the times via + // utimensat without opening. + #[cfg(unix)] + let no_open = { + let ft = source_metadata.file_type(); + dest.is_symlink() + || ft.is_fifo() + || ft.is_socket() + || ft.is_char_device() + || ft.is_block_device() + }; + #[cfg(not(unix))] + let no_open = dest.is_symlink(); + if no_open { filetime::set_symlink_file_times(dest, atime, mtime)?; } else { filetime::set_file_times(dest, atime, mtime)?; diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index d973c39366..079a317d80 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -3370,6 +3370,24 @@ fn test_cp_fifo() { assert_eq!(permission, "prwx-wx--x".to_string()); } +#[test] +#[cfg(unix)] +fn test_cp_fifo_preserve_timestamps() { + // Preserving timestamps must not open the FIFO: opening a FIFO with no + // writer blocks forever. If this regresses, the test hangs instead of + // completing. See the `-a`/`--preserve=timestamps` path in copy_attributes. + let (at, mut ucmd) = at_and_ucmd!(); + at.mkfifo("pipe"); + at.set_mode("pipe", 0o624); + ucmd.arg("--preserve=timestamps") + .arg("-r") + .arg("pipe") + .arg("pipe_dup") + .succeeds() + .no_output(); + assert!(at.is_fifo("pipe_dup")); +} + #[rstest] #[case::recursive("-R")] #[case::archive("-a")] From 7ea76d36da97244bb3da1e19e2cdd563bbc8298f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 11 Jul 2026 02:53:04 +0200 Subject: [PATCH 2/2] update the ignore spell list --- 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 92d4b7168c..4a688e8a32 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -3,6 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell IRWXG IRWXO IRWXU IRWXUGO IRWXU IRWXG IRWXO IRWXUGO sflag +// spell-checker:ignore RDONLY futimens utimensat use std::cmp::Ordering; use std::collections::{HashMap, HashSet};