From 85efa04caf5a9d08166e3001cab0b5830cd4ffa8 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Wed, 3 Jun 2026 13:44:03 +0200 Subject: [PATCH] tests/virtiofs-misc: add rename-overwrite fchmod subtest Add regression test for PR #700, which fixed a file descriptor leak on macOS when renaming over an existing file with open fds. The bug: macOS virtio-fs uses volfs paths (/.vol/{dev}/{ino}) which become invalid when the inode's last link is removed. When rename() replaces a file, the old target loses its directory entry, breaking subsequent operations on still-open file descriptors. This broke apt/dpkg atomic writes with errors like: "Problem closing the file /var/lib/dpkg/status - close (2: No such file or directory)" The fix stores an fd to the doomed target before rename, preserving access to the unlinked inode for tracked fds. The test verifies that fchmod() works on an open fd after the file has been atomically replaced via rename. Assisted-by: Claude Code:claude-opus-4.6 Signed-off-by: Sergio Lopez --- tests/test_cases/src/test_virtiofs_misc.rs | 63 ++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/test_cases/src/test_virtiofs_misc.rs b/tests/test_cases/src/test_virtiofs_misc.rs index cb5b3730b..c599d2be8 100644 --- a/tests/test_cases/src/test_virtiofs_misc.rs +++ b/tests/test_cases/src/test_virtiofs_misc.rs @@ -465,6 +465,68 @@ mod guest { } } + /// Test rename-overwrite with open file descriptors (PR #700 regression test). + /// + /// This verifies the fix for a macOS-specific fd leak when renaming over + /// an existing file. The macOS virtio-fs implementation uses volfs paths + /// (/.vol/{dev}/{ino}) which become invalid when the inode's last link is + /// removed. When rename() replaces a file, the old target loses its link, + /// breaking operations on still-open fds. + /// + /// This pattern broke apt/dpkg atomic writes: + /// "Problem closing the file /var/lib/dpkg/status - close (2: No such file or directory)" + fn test_rename_overwrite_fchmod() { + let target_path = "/test_rename_fstat_target"; + let temp_path = "/test_rename_fstat_temp"; + + // Create target with initial content + let mut target_file = fs::OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(target_path) + .unwrap(); + + target_file.write_all(b"data1\n").unwrap(); + target_file.flush().unwrap(); + + // Keep the target file open + let open_target = fs::OpenOptions::new() + .read(true) + .write(true) + .open(target_path) + .unwrap(); + + // Create temp file + let mut temp_file = fs::OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(temp_path) + .unwrap(); + + temp_file.write_all(b"data2\n").unwrap(); + temp_file.flush().unwrap(); + drop(temp_file); + + // Rename temp over target + fs::rename(temp_path, target_path).expect("rename failed"); + + // Try to get file stats via the still-open file descriptor. + // Before PR #700, this would fail with ENOENT on macOS. + open_target + .metadata() + .expect("fstat on open fd after rename failed - PR #700 regression!"); + + // Try to change permission bits via the still-open file descriptor. + use nix::sys::stat::{Mode, fchmod}; + fchmod( + open_target.as_raw_fd(), + Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP, + ) + .expect("fchmod on open fd after rename failed - PR #700 regression!"); + } + /// Test that creating files mid-iteration does not cause duplicates. /// /// POSIX says readdir behavior is unspecified when the directory is modified @@ -575,6 +637,7 @@ mod guest { "fallocate_punch_hole_requires_keep_size", test_fallocate_punch_hole_requires_keep_size, ), + ("rename_overwrite_fchmod", test_rename_overwrite_fchmod), ("dirstream_create", test_dirstream_create), ("dirstream_unlink", test_dirstream_unlink), ("dirstream_mkdir_rmdir", test_dirstream_mkdir_rmdir),