This commit is contained in:
Cả thế giới là Rust 2025-12-23 00:42:17 +07:00 committed by GitHub
commit e7d4d5f4b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 140 additions and 7 deletions

View file

@ -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

View file

@ -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))]
{

View file

@ -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

View 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();
}
}