Skip to content

Commit 24b49c3

Browse files
committed
fix(mv): prevent hang when moving directories containing FIFO files
Fixes #9656 When moving a directory containing FIFO files, copy_dir_contents_recursive() would call fs::copy() on the FIFO, causing an indefinite hang. This fix adds FIFO detection before the copy operation and uses make_fifo() instead, matching the behavior of rename_fifo_fallback(). - Add FIFO detection in copy_dir_contents_recursive() - Create new FIFO at destination using make_fifo() - Add test with timeout protection to prevent future regressions
1 parent 06d843f commit 24b49c3

File tree

2 files changed

+70
-7
lines changed

2 files changed

+70
-7
lines changed

src/uu/mv/src/mv.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,15 +1082,28 @@ fn copy_dir_contents_recursive(
10821082
display_manager,
10831083
)?;
10841084
} else {
1085-
// Copy file with or without hardlink support based on platform
1085+
// Check if this is a FIFO to avoid blocking on fs::copy (issue #9656)
10861086
#[cfg(unix)]
10871087
{
1088-
copy_file_with_hardlinks_helper(
1089-
&from_path,
1090-
&to_path,
1091-
hardlink_tracker,
1092-
hardlink_scanner,
1093-
)?;
1088+
let metadata = from_path.symlink_metadata()?;
1089+
let file_type = metadata.file_type();
1090+
1091+
if is_fifo(file_type) {
1092+
// Handle FIFO specially to avoid blocking on fs::copy
1093+
if to_path.try_exists()? {
1094+
fs::remove_file(&to_path)?;
1095+
}
1096+
make_fifo(&to_path)?;
1097+
fs::remove_file(&from_path)?;
1098+
} else {
1099+
// Copy file with hardlink support
1100+
copy_file_with_hardlinks_helper(
1101+
&from_path,
1102+
&to_path,
1103+
hardlink_tracker,
1104+
hardlink_scanner,
1105+
)?;
1106+
}
10941107
}
10951108
#[cfg(not(unix))]
10961109
{

tests/by-util/test_mv.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2537,6 +2537,56 @@ fn test_special_file_different_filesystem() {
25372537
std::fs::remove_dir_all("/dev/shm/tmp").unwrap();
25382538
}
25392539

2540+
/// Test moving a directory containing a FIFO file across different filesystems (issue #9656)
2541+
/// Without proper FIFO handling, this test will hang indefinitely when
2542+
/// copy_dir_contents_recursive tries to fs::copy() the FIFO
2543+
#[cfg(unix)]
2544+
#[test]
2545+
fn test_mv_dir_containing_fifo_cross_filesystem() {
2546+
use std::time::Duration;
2547+
2548+
// Skip if /dev/shm not available
2549+
if !Path::new("/dev/shm").exists() {
2550+
return;
2551+
}
2552+
2553+
let (at, mut ucmd) = at_and_ucmd!();
2554+
at.mkdir("a");
2555+
at.mkfifo("a/f");
2556+
2557+
// This will hang without the fix, so use timeout
2558+
// Move to /dev/shm which is typically a different filesystem (tmpfs)
2559+
ucmd.args(&["a", "/dev/shm/test_mv_fifo_dir"])
2560+
.timeout(Duration::from_secs(2))
2561+
.succeeds();
2562+
2563+
assert!(!at.dir_exists("a"));
2564+
assert!(Path::new("/dev/shm/test_mv_fifo_dir").exists());
2565+
assert!(Path::new("/dev/shm/test_mv_fifo_dir/f").exists());
2566+
std::fs::remove_dir_all("/dev/shm/test_mv_fifo_dir").ok();
2567+
}
2568+
2569+
/// Test moving a directory containing a FIFO file (same filesystem - issue #9656)
2570+
/// This tests FIFO handling doesn't fail even on same filesystem
2571+
#[cfg(unix)]
2572+
#[test]
2573+
fn test_mv_dir_containing_fifo_same_filesystem() {
2574+
use std::time::Duration;
2575+
2576+
let (at, mut ucmd) = at_and_ucmd!();
2577+
at.mkdir("a");
2578+
at.mkfifo("a/f");
2579+
2580+
// This will hang without the fix, so use timeout
2581+
ucmd.args(&["a", "b"])
2582+
.timeout(Duration::from_secs(2))
2583+
.succeeds();
2584+
2585+
assert!(!at.dir_exists("a"));
2586+
assert!(at.dir_exists("b"));
2587+
assert!(at.is_fifo("b/f"));
2588+
}
2589+
25402590
/// Test cross-device move with permission denied error
25412591
/// This test mimics the scenario from the GNU part-fail test where
25422592
/// a cross-device move fails due to permission errors when removing the target file

0 commit comments

Comments
 (0)