mirror of
https://github.com/uutils/coreutils.git
synced 2025-12-23 08:47:37 +00:00
Merge 68a35de0c7 into 836e5d19c4
This commit is contained in:
commit
e7d4d5f4b0
4 changed files with 140 additions and 7 deletions
|
|
@ -91,8 +91,10 @@ microbenchmarks
|
|||
microbenchmarking
|
||||
multibyte
|
||||
multicall
|
||||
newfs
|
||||
nmerge
|
||||
noatime
|
||||
nomount
|
||||
nocache
|
||||
nocreat
|
||||
noctty
|
||||
|
|
@ -175,6 +177,7 @@ inacc
|
|||
maint
|
||||
proc
|
||||
procs
|
||||
ramdisk
|
||||
|
||||
# * constants
|
||||
xffff
|
||||
|
|
|
|||
|
|
@ -1082,15 +1082,24 @@ fn copy_dir_contents_recursive(
|
|||
display_manager,
|
||||
)?;
|
||||
} else {
|
||||
// Copy file with or without hardlink support based on platform
|
||||
// Check if this is a FIFO to avoid blocking on fs::copy (issue #9656)
|
||||
#[cfg(unix)]
|
||||
{
|
||||
copy_file_with_hardlinks_helper(
|
||||
&from_path,
|
||||
&to_path,
|
||||
hardlink_tracker,
|
||||
hardlink_scanner,
|
||||
)?;
|
||||
let metadata = from_path.symlink_metadata()?;
|
||||
let file_type = metadata.file_type();
|
||||
|
||||
if is_fifo(file_type) {
|
||||
// Handle FIFO specially to avoid blocking on fs::copy
|
||||
rename_fifo_fallback(&from_path, &to_path)?;
|
||||
} else {
|
||||
// Copy file with hardlink support
|
||||
copy_file_with_hardlinks_helper(
|
||||
&from_path,
|
||||
&to_path,
|
||||
hardlink_tracker,
|
||||
hardlink_scanner,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2557,6 +2557,51 @@ fn test_special_file_different_filesystem() {
|
|||
std::fs::remove_dir_all("/dev/shm/tmp").unwrap();
|
||||
}
|
||||
|
||||
/// Test moving a directory containing a FIFO file across different filesystems (issue #9656)
|
||||
/// Without proper FIFO handling, this test will hang indefinitely when
|
||||
/// copy_dir_contents_recursive tries to fs::copy() the FIFO
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_mv_dir_containing_fifo_cross_filesystem() {
|
||||
use std::time::Duration;
|
||||
|
||||
let mut scene = TestScenario::new(util_name!());
|
||||
|
||||
// Test must be run as root (or with `sudo -E`)
|
||||
if scene.cmd("whoami").run().stdout_str() != "root\n" {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
let at = &scene.fixtures;
|
||||
at.mkdir("a");
|
||||
at.mkfifo("a/f");
|
||||
at.mkdir("mnt");
|
||||
}
|
||||
|
||||
// Prepare the mount
|
||||
let mountpoint_path = scene.fixtures.plus_as_string("mnt");
|
||||
scene
|
||||
.mount_temp_fs(&mountpoint_path)
|
||||
.expect("mounting tmpfs failed");
|
||||
|
||||
// This will hang without the fix, so use timeout
|
||||
// Move to the mounted tmpfs which is a different filesystem
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["a", "mnt/dest"])
|
||||
.timeout(Duration::from_secs(2))
|
||||
.succeeds();
|
||||
|
||||
// Ditch the mount before the asserts
|
||||
scene.umount_temp_fs();
|
||||
|
||||
let at = &scene.fixtures;
|
||||
assert!(!at.dir_exists("a"));
|
||||
assert!(at.dir_exists("mnt/dest"));
|
||||
assert!(at.is_fifo("mnt/dest/f"));
|
||||
}
|
||||
|
||||
/// Test cross-device move with permission denied error
|
||||
/// This test mimics the scenario from the GNU part-fail test where
|
||||
/// a cross-device move fails due to permission errors when removing the target file
|
||||
|
|
|
|||
|
|
@ -1339,6 +1339,8 @@ pub struct TestScenario {
|
|||
tmpd: Rc<TempDir>,
|
||||
#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
|
||||
tmp_fs_mountpoint: Option<String>,
|
||||
#[cfg(target_vendor = "apple")]
|
||||
tmp_fs_ramdisk: Option<String>,
|
||||
}
|
||||
|
||||
impl TestScenario {
|
||||
|
|
@ -1355,6 +1357,8 @@ impl TestScenario {
|
|||
tmpd,
|
||||
#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
|
||||
tmp_fs_mountpoint: None,
|
||||
#[cfg(target_vendor = "apple")]
|
||||
tmp_fs_ramdisk: None,
|
||||
};
|
||||
let mut fixture_path_builder = env::current_dir().unwrap();
|
||||
fixture_path_builder.push(TESTS_DIR);
|
||||
|
|
@ -1422,6 +1426,64 @@ impl TestScenario {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Mounts a temporary filesystem at the specified mount point (macOS).
|
||||
#[cfg(target_vendor = "apple")]
|
||||
pub fn mount_temp_fs(&mut self, mount_point: &str) -> core::result::Result<(), String> {
|
||||
if self.tmp_fs_ramdisk.is_some() {
|
||||
return Err("already mounted".to_string());
|
||||
}
|
||||
|
||||
// Create a 10MB ramdisk using hdiutil (10 * 2048 = 20480 512-byte sectors)
|
||||
let attach_result = self
|
||||
.cmd("hdiutil")
|
||||
.args(&["attach", "-nomount", "ram://20480"])
|
||||
.run();
|
||||
|
||||
if !attach_result.succeeded() {
|
||||
return Err("Failed to create ramdisk".to_string());
|
||||
}
|
||||
|
||||
let ramdisk_device = attach_result.stdout_str().trim().to_string();
|
||||
if ramdisk_device.is_empty() {
|
||||
return Err("hdiutil returned empty device name".to_string());
|
||||
}
|
||||
|
||||
// Format the ramdisk with HFS+ filesystem
|
||||
let format_result = self
|
||||
.cmd("newfs_hfs")
|
||||
.arg("-M")
|
||||
.arg("700")
|
||||
.arg(&ramdisk_device)
|
||||
.run();
|
||||
|
||||
if !format_result.succeeded() {
|
||||
// Clean up ramdisk on failure
|
||||
let _ = self.cmd("hdiutil").args(&["detach", &ramdisk_device]).run();
|
||||
return Err(format!(
|
||||
"Failed to format ramdisk: {}",
|
||||
format_result.stderr_str()
|
||||
));
|
||||
}
|
||||
|
||||
// Mount the ramdisk at the specified mount point
|
||||
let mount_result = self
|
||||
.cmd("mount")
|
||||
.args(&["-t", "hfs", &ramdisk_device, mount_point])
|
||||
.run();
|
||||
|
||||
if !mount_result.succeeded() {
|
||||
// Clean up ramdisk on failure
|
||||
let _ = self.cmd("hdiutil").args(&["detach", &ramdisk_device]).run();
|
||||
return Err(format!(
|
||||
"Failed to mount ramdisk: {}",
|
||||
mount_result.stderr_str()
|
||||
));
|
||||
}
|
||||
|
||||
self.tmp_fs_ramdisk = Some(ramdisk_device);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
|
||||
/// Unmounts the temporary filesystem if it is currently mounted.
|
||||
pub fn umount_temp_fs(&mut self) {
|
||||
|
|
@ -1430,12 +1492,26 @@ impl TestScenario {
|
|||
self.tmp_fs_mountpoint = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
/// Unmounts and detaches the temporary ramdisk (macOS).
|
||||
pub fn umount_temp_fs(&mut self) {
|
||||
if let Some(ramdisk_device) = self.tmp_fs_ramdisk.as_ref() {
|
||||
// hdiutil detach will unmount automatically
|
||||
self.cmd("hdiutil")
|
||||
.args(&["detach", ramdisk_device])
|
||||
.succeeds();
|
||||
self.tmp_fs_ramdisk = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestScenario {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
|
||||
self.umount_temp_fs();
|
||||
#[cfg(target_vendor = "apple")]
|
||||
self.umount_temp_fs();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue