diff --git a/src/context/unshare.rs b/src/context/unshare.rs index 81afaa0..c63dc2d 100644 --- a/src/context/unshare.rs +++ b/src/context/unshare.rs @@ -77,24 +77,70 @@ fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> { // Recursively copy subdirectories copy_dir_recursive(&src_path, &dest_path)?; } else { - // Copy regular files - std::fs::copy(&src_path, &dest_path).map_err(|e| { - io::Error::new( - e.kind(), - format!( - "Failed to copy '{}' to '{}': {}", - src_path.display(), - dest_path.display(), - e - ), - ) - })?; + // Copy regular files, preserving the source modification and + // access times. This is important for autotools/gnulib-based + // packages (e.g. 'hello' from Debian sid) that ship pre-generated + // files alongside their prerequisites: if the copy resets the + // mtime to "now", the prerequisites appear as new as the + // generated targets and `make` tries to regenerate them using + // tools (like gperf) that are not declared build-dependencies. + copy_file_with_times(&src_path, &dest_path)?; } } Ok(()) } +/// Copy a single regular file from `src` to `dest`, preserving the source's +/// modification and access times. +/// +/// `std::fs::copy` resets the destination mtime to "now", which breaks +/// timestamp-based build systems (autotools/gnulib) that ship pre-generated +/// files alongside their prerequisites. Restoring the original timestamps +/// prevents `make` from needlessly regenerating those files with tools that +/// may not be installed (e.g. `gperf`). +fn copy_file_with_times(src: &Path, dest: &Path) -> io::Result<()> { + std::fs::copy(src, dest).map_err(|e| { + io::Error::new( + e.kind(), + format!( + "Failed to copy '{}' to '{}': {}", + src.display(), + dest.display(), + e + ), + ) + })?; + + // Restore the original timestamps on the destination file. + let metadata = std::fs::metadata(src).map_err(|e| { + io::Error::new( + e.kind(), + format!("Failed to read metadata of '{}': {}", src.display(), e), + ) + })?; + let mut times = std::fs::FileTimes::new(); + let mut have_times = false; + if let Ok(mtime) = metadata.modified() { + times = times.set_modified(mtime); + have_times = true; + } + if let Ok(atime) = metadata.accessed() { + times = times.set_accessed(atime); + have_times = true; + } + if have_times && let Ok(dest_file) = std::fs::File::open(dest) { + let _ = dest_file.set_times(times).map_err(|e| { + io::Error::new( + e.kind(), + format!("Failed to set times on '{}': {}", dest.display(), e), + ) + }); + } + + Ok(()) +} + impl ContextDriver for UnshareDriver { fn ensure_available(&self, src: &Path, dest_root: &str) -> io::Result { // Construct the destination path inside the chroot @@ -125,7 +171,7 @@ impl ContextDriver for UnshareDriver { dest_path.display() ); } else { - std::fs::copy(src, &dest_path)?; + copy_file_with_times(src, &dest_path)?; debug!("Copied file {} to {}", src.display(), dest_path.display()); } diff --git a/src/pull.rs b/src/pull.rs index 520f316..adef83f 100644 --- a/src/pull.rs +++ b/src/pull.rs @@ -113,13 +113,46 @@ fn copy_dir_all(src: &Path, dst: &Path) -> Result<(), Box> { } else if src_path.is_dir() { copy_dir_all(&src_path, &dst_path)?; } else { + // Copy the file, preserving the source modification and access + // times. This is important for autotools/gnulib-based packages + // (e.g. 'hello' from Debian sid) that ship pre-generated files + // alongside their prerequisites: if the copy resets the mtime to + // "now", the prerequisites appear as new as the generated targets + // and `make` tries to regenerate them using tools (like gperf) + // that are not declared build-dependencies. std::fs::copy(&src_path, &dst_path)?; + copy_file_times(&src_path, &dst_path)?; } } Ok(()) } +/// Restore the modification and access times of `dest` to match `src`. +/// +/// `std::fs::copy` resets the destination mtime to "now", which breaks +/// timestamp-based build systems (autotools/gnulib) that ship pre-generated +/// files alongside their prerequisites. Restoring the original timestamps +/// prevents `make` from needlessly regenerating those files with tools that +/// may not be installed (e.g. `gperf`). +fn copy_file_times(src: &Path, dest: &Path) -> Result<(), Box> { + let metadata = std::fs::metadata(src)?; + let mut times = std::fs::FileTimes::new(); + let mut have_times = false; + if let Ok(mtime) = metadata.modified() { + times = times.set_modified(mtime); + have_times = true; + } + if let Ok(atime) = metadata.accessed() { + times = times.set_accessed(atime); + have_times = true; + } + if have_times && let Ok(dest_file) = std::fs::File::open(dest) { + let _ = dest_file.set_times(times); + } + Ok(()) +} + /// Helper function to extract tar archive with progress tracking fn extract_tar_archive( file_path: &Path, @@ -467,6 +500,7 @@ async fn fetch_archive_sources( copy_dir_all(&sub_path, &target_path)?; } else { std::fs::copy(&sub_path, &target_path)?; + copy_file_times(&sub_path, &target_path)?; } } std::fs::remove_dir_all(&src_dir)?;