diff --git a/src/header.rs b/src/header.rs index 00e2eab0..473cc5b5 100644 --- a/src/header.rs +++ b/src/header.rs @@ -49,6 +49,13 @@ pub enum HeaderMode { /// Only metadata that is directly relevant to the identity of a file will /// be included. In particular, ownership and mod/access times are excluded. Deterministic, + + #[cfg(unix)] + /// All supported metadata, including mod tims and ownership will + /// be included, but mod times greater than the specified value will + /// use the specified value instead. This mimics GNU tar's + /// `--clamp-mtime` option. + ClampMtime(u64), } /// Representation of the header of an entry in an archive @@ -796,6 +803,17 @@ impl Header { }; self.set_mode(fs_mode); } + #[cfg(unix)] + HeaderMode::ClampMtime(clamp) => { + self.set_mtime(if meta.mtime() as u64 > clamp { + clamp + } else { + meta.mtime() as u64 + }); + self.set_uid(meta.uid() as u64); + self.set_gid(meta.gid() as u64); + self.set_mode(meta.mode()); + } } // Note that if we are a GNU header we *could* set atime/ctime, except diff --git a/tests/all.rs b/tests/all.rs index 57a77404..1b546f20 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -799,6 +799,40 @@ fn zero_file_times() { assert!(atime.unix_seconds() != 0); } +#[test] +#[cfg(unix)] +fn clamp_mtime() { + let td = t!(TempBuilder::new().prefix("tar-rs").tempdir()); + + let clamp = 1000000000; + let max_clamp = u64::MAX; + + let mut ar = Builder::new(Vec::new()); + ar.mode(HeaderMode::ClampMtime(clamp)); + let path = td.path().join("tmpfile"); + t!(File::create(&path)); + t!(ar.append_path_with_name(&path, "a")); + + ar.mode(HeaderMode::ClampMtime(max_clamp)); + t!(ar.append_path_with_name(&path, "b")); + + let data = t!(ar.into_inner()); + let mut ar = Archive::new(&data[..]); + assert!(ar.unpack(td.path()).is_ok()); + + let meta = fs::metadata(td.path().join("a")).unwrap(); + let mtime = FileTime::from_last_modification_time(&meta); + let atime = FileTime::from_last_access_time(&meta); + assert!(mtime.unix_seconds() as u64 == clamp); + assert!(atime.unix_seconds() as u64 == clamp); + + let meta = fs::metadata(td.path().join("b")).unwrap(); + let mtime = FileTime::from_last_modification_time(&meta); + let atime = FileTime::from_last_access_time(&meta); + assert!((mtime.unix_seconds() as u64) < max_clamp); + assert!((atime.unix_seconds() as u64) < max_clamp); +} + #[test] fn backslash_treated_well() { // Insert a file into an archive with a backslash