Skip to content

Commit 4f8a3af

Browse files
committed
Add a new HeaderMode variant with specific overrides
1 parent ea42424 commit 4f8a3af

3 files changed

Lines changed: 168 additions & 66 deletions

File tree

src/header.rs

Lines changed: 166 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::os::unix::prelude::*;
44
use std::os::windows::prelude::*;
55

66
use std::borrow::Cow;
7+
use std::cmp;
78
use std::fmt;
89
use std::fs;
910
use std::io;
@@ -47,11 +48,83 @@ pub enum HeaderMode {
4748

4849
/// Only metadata that is directly relevant to the identity of a file will
4950
/// be included. In particular, ownership and mod/access times are excluded.
50-
Deterministic {
51-
///If the `mtime` param is `Some(..)`, the value will be used instead of
52-
/// [DETERMINISTIC_TIMESTAMP]
53-
mtime: Option<u64>,
54-
},
51+
Deterministic,
52+
53+
/// Preserves all the original metadata except for the provided overrides.
54+
/// The default is effectively the same as Complete.
55+
Override(HeaderModeOverrides),
56+
}
57+
58+
impl HeaderMode {
59+
fn as_override(&self) -> HeaderModeOverrides {
60+
match self {
61+
Self::Complete => HeaderModeOverrides::default(),
62+
Self::Deterministic => HeaderModeOverrides::default()
63+
.with_uid(0)
64+
.with_gid(0)
65+
.override_mtime(DETERMINISTIC_TIMESTAMP)
66+
.with_deterministic_mode(),
67+
Self::Override(overrides) => *overrides,
68+
}
69+
}
70+
}
71+
72+
/// Declares the specific attributes of the header to override when filling a Header
73+
/// in [HeaderMode::Override]
74+
#[non_exhaustive]
75+
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
76+
pub struct HeaderModeOverrides {
77+
uid: Option<u64>,
78+
gid: Option<u64>,
79+
mtime: Option<MtimeOverride>,
80+
mode: Option<ModeOverride>,
81+
}
82+
83+
impl HeaderModeOverrides {
84+
/// Override the Header `uid` to the given value
85+
pub fn with_uid(mut self, uid: u64) -> Self {
86+
self.uid = Some(uid);
87+
self
88+
}
89+
90+
/// Override the Header `gid` to the given value
91+
pub fn with_gid(mut self, gid: u64) -> Self {
92+
self.gid = Some(gid);
93+
self
94+
}
95+
96+
/// Configures the `mtime` to be set to the specific value
97+
pub fn override_mtime(mut self, mtime: u64) -> Self {
98+
self.mtime = Some(MtimeOverride::Override(mtime));
99+
self
100+
}
101+
102+
/// Configures the `mtime` to be set to the minimum of either
103+
/// the actual mtime or the provided value
104+
pub fn clamp_mtime(mut self, mtime: u64) -> Self {
105+
self.mtime = Some(MtimeOverride::Clamp(mtime));
106+
self
107+
}
108+
109+
/// Configures the file permissions to be set in the same manner
110+
/// as [HeaderMode::Deterministic]
111+
pub fn with_deterministic_mode(mut self) -> Self {
112+
self.mode = Some(ModeOverride::Deterministic);
113+
self
114+
}
115+
}
116+
117+
#[non_exhaustive]
118+
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
119+
pub enum MtimeOverride {
120+
Override(u64),
121+
Clamp(u64),
122+
}
123+
124+
#[non_exhaustive]
125+
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
126+
pub enum ModeOverride {
127+
Deterministic,
55128
}
56129

57130
/// Representation of the header of an entry in an archive
@@ -774,32 +847,7 @@ impl Header {
774847

775848
#[cfg(all(unix, not(target_arch = "wasm32")))]
776849
fn fill_platform_from(&mut self, meta: &fs::Metadata, mode: HeaderMode) {
777-
match mode {
778-
HeaderMode::Complete => {
779-
self.set_mtime(meta.mtime() as u64);
780-
self.set_uid(meta.uid() as u64);
781-
self.set_gid(meta.gid() as u64);
782-
self.set_mode(meta.mode());
783-
}
784-
HeaderMode::Deterministic { mtime } => {
785-
// We could in theory default the mtime to zero here, but not all tools seem to behave
786-
// well when ingesting files with a 0 timestamp.
787-
// For example, rust-lang/cargo#9512 shows that lldb doesn't ingest files with a
788-
// zero timestamp correctly.
789-
self.set_mtime(mtime.unwrap_or(DETERMINISTIC_TIMESTAMP));
790-
791-
self.set_uid(0);
792-
self.set_gid(0);
793-
794-
// Use a default umask value, but propagate the (user) execute bit.
795-
let fs_mode = if meta.is_dir() || (0o100 & meta.mode() == 0o100) {
796-
0o755
797-
} else {
798-
0o644
799-
};
800-
self.set_mode(fs_mode);
801-
}
802-
}
850+
self.fill_platform_from_overrides(meta, mode.as_override());
803851

804852
// Note that if we are a GNU header we *could* set atime/ctime, except
805853
// the `tar` utility doesn't do that by default and it causes problems
@@ -829,36 +877,7 @@ impl Header {
829877
#[cfg(windows)]
830878
fn fill_platform_from(&mut self, meta: &fs::Metadata, mode: HeaderMode) {
831879
// There's no concept of a file mode on Windows, so do a best approximation here.
832-
match mode {
833-
HeaderMode::Complete => {
834-
self.set_uid(0);
835-
self.set_gid(0);
836-
// The dates listed in tarballs are always seconds relative to
837-
// January 1, 1970. On Windows, however, the timestamps are returned as
838-
// dates relative to January 1, 1601 (in 100ns intervals), so we need to
839-
// add in some offset for those dates.
840-
let mtime = (meta.last_write_time() / (1_000_000_000 / 100)) - 11644473600;
841-
self.set_mtime(mtime);
842-
let fs_mode = {
843-
const FILE_ATTRIBUTE_READONLY: u32 = 0x00000001;
844-
let readonly = meta.file_attributes() & FILE_ATTRIBUTE_READONLY;
845-
match (meta.is_dir(), readonly != 0) {
846-
(true, false) => 0o755,
847-
(true, true) => 0o555,
848-
(false, false) => 0o644,
849-
(false, true) => 0o444,
850-
}
851-
};
852-
self.set_mode(fs_mode);
853-
}
854-
HeaderMode::Deterministic { mtime } => {
855-
self.set_uid(0);
856-
self.set_gid(0);
857-
self.set_mtime(mtime.unwrap_or(DETERMINISTIC_TIMESTAMP)); // see above in unix
858-
let fs_mode = if meta.is_dir() { 0o755 } else { 0o644 };
859-
self.set_mode(fs_mode);
860-
}
861-
}
880+
self.fill_platform_from_overrides(meta, mode.as_override());
862881

863882
let ft = meta.file_type();
864883
self.set_entry_type(if ft.is_dir() {
@@ -872,6 +891,52 @@ impl Header {
872891
});
873892
}
874893

894+
#[cfg(all(unix, not(target_arch = "wasm32")))]
895+
fn fill_platform_from_overrides(
896+
&mut self,
897+
meta: &fs::Metadata,
898+
overrides: HeaderModeOverrides,
899+
) {
900+
let mtime = match overrides.mtime {
901+
Some(MtimeOverride::Override(mtime)) => mtime,
902+
Some(MtimeOverride::Clamp(mtime)) => cmp::min(meta.mtime() as u64, mtime),
903+
None => meta.mtime() as u64,
904+
};
905+
self.set_mtime(mtime);
906+
907+
self.set_uid(overrides.uid.unwrap_or_else(|| meta.uid() as u64));
908+
self.set_gid(overrides.gid.unwrap_or_else(|| meta.gid() as u64));
909+
910+
let mode = match overrides.mode {
911+
Some(ModeOverride::Deterministic) => deterministic_mode(meta),
912+
None => meta.mode(),
913+
};
914+
self.set_mode(mode);
915+
}
916+
917+
#[cfg(windows)]
918+
fn fill_platform_from_overrides(
919+
&mut self,
920+
meta: &fs::Metadata,
921+
overrides: HeaderModeOverrides,
922+
) {
923+
self.set_uid(0);
924+
self.set_gid(0);
925+
926+
let mtime = match overrides.mtime {
927+
Some(MtimeOverride::Override(mtime)) => mtime,
928+
Some(MtimeOverride::Clamp(mtime)) => cmp::min(extract_mtime_windows(meta), mtime),
929+
None => extract_mtime_windows(meta),
930+
};
931+
self.set_mtime(mtime);
932+
933+
let mode = match overrides.mode {
934+
Some(ModeOverride::Deterministic) => deterministic_mode(meta),
935+
None => extract_mode_windows(meta),
936+
};
937+
self.set_mode(mode);
938+
}
939+
875940
fn debug_fields(&self, b: &mut fmt::DebugStruct) {
876941
if let Ok(entry_size) = self.entry_size() {
877942
b.field("entry_size", &entry_size);
@@ -1732,3 +1797,43 @@ pub fn bytes2path(bytes: Cow<[u8]>) -> io::Result<Cow<Path>> {
17321797
fn invalid_utf8<T>(_: T) -> io::Error {
17331798
io::Error::new(io::ErrorKind::InvalidData, "Invalid utf-8")
17341799
}
1800+
1801+
#[cfg(windows)]
1802+
fn extract_mtime_windows(meta: &fs::Metadata) -> u64 {
1803+
// The dates listed in tarballs are always seconds relative to
1804+
// January 1, 1970. On Windows, however, the timestamps are returned as
1805+
// dates relative to January 1, 1601 (in 100ns intervals), so we need to
1806+
// add in some offset for those dates.
1807+
let mtime = (meta.last_write_time() / (1_000_000_000 / 100)) - 11644473600;
1808+
}
1809+
1810+
#[cfg(windows)]
1811+
fn extract_mode_windows(meta: &fs::Metadata) -> u32 {
1812+
const FILE_ATTRIBUTE_READONLY: u32 = 0x00000001;
1813+
let readonly = meta.file_attributes() & FILE_ATTRIBUTE_READONLY;
1814+
match (meta.is_dir(), readonly != 0) {
1815+
(true, false) => 0o755,
1816+
(true, true) => 0o555,
1817+
(false, false) => 0o644,
1818+
(false, true) => 0o444,
1819+
}
1820+
}
1821+
1822+
#[cfg(all(unix, not(target_arch = "wasm32")))]
1823+
fn deterministic_mode(meta: &fs::Metadata) -> u32 {
1824+
// Use a default umask value, but propagate the (user) execute bit.
1825+
if meta.is_dir() || (0o100 & meta.mode() == 0o100) {
1826+
0o755
1827+
} else {
1828+
0o644
1829+
}
1830+
}
1831+
1832+
#[cfg(windows)]
1833+
fn deterministic_mode(meta: &fs::Metadata) -> u32 {
1834+
if meta.is_dir() {
1835+
0o755
1836+
} else {
1837+
0o644
1838+
}
1839+
}

tests/all.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,7 @@ fn zero_file_times() {
804804
let td = TempBuilder::new().prefix("tar-rs").tempdir().unwrap();
805805

806806
let mut ar = Builder::new(Vec::new());
807-
ar.mode(HeaderMode::Deterministic { mtime: None });
807+
ar.mode(HeaderMode::Deterministic);
808808
let path = td.path().join("tmpfile");
809809
File::create(&path).unwrap();
810810
ar.append_path_with_name(&path, "a").unwrap();

tests/header/mod.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,7 @@ fn set_metadata_deterministic() {
188188
perms.set_readonly(readonly);
189189
fs::set_permissions(path, perms).unwrap();
190190
let mut h = Header::new_ustar();
191-
h.set_metadata_in_mode(
192-
&path.metadata().unwrap(),
193-
HeaderMode::Deterministic { mtime: None },
194-
);
191+
h.set_metadata_in_mode(&path.metadata().unwrap(), HeaderMode::Deterministic);
195192
Ok(h)
196193
}
197194

0 commit comments

Comments
 (0)