@@ -4,6 +4,7 @@ use std::os::unix::prelude::*;
44use std:: os:: windows:: prelude:: * ;
55
66use std:: borrow:: Cow ;
7+ use std:: cmp;
78use std:: fmt;
89use std:: fs;
910use 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>> {
17321797fn 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+ }
0 commit comments