diff --git a/src/archive.rs b/src/archive.rs index e875124a..c7a9d980 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -22,6 +22,7 @@ pub struct Archive { pub struct ArchiveInner { pos: Cell, + mask: u32, unpack_xattrs: bool, preserve_permissions: bool, preserve_ownerships: bool, @@ -53,6 +54,7 @@ impl Archive { pub fn new(obj: R) -> Archive { Archive { inner: ArchiveInner { + mask: u32::MIN, unpack_xattrs: false, preserve_permissions: false, preserve_ownerships: false, @@ -108,6 +110,20 @@ impl Archive { me._unpack(dst.as_ref()) } + /// Set the mask of the permission bits when unpacking this entry. + /// + /// The mask will be inverted when applying against a mode, similar to how + /// `umask` works on Unix. In logical notation it looks like: + /// + /// ```text + /// new_mode = old_mode & (~mask) + /// ``` + /// + /// The mask is 0 by default and is currently only implemented on Unix. + pub fn set_mask(&mut self, mask: u32) { + self.inner.mask = mask; + } + /// Indicate whether extended file attributes (xattrs on Unix) are preserved /// when unpacking this archive. /// @@ -315,6 +331,7 @@ impl<'a> EntriesFields<'a> { long_pathname: None, long_linkname: None, pax_extensions: None, + mask: self.archive.inner.mask, unpack_xattrs: self.archive.inner.unpack_xattrs, preserve_permissions: self.archive.inner.preserve_permissions, preserve_mtime: self.archive.inner.preserve_mtime, diff --git a/src/entry.rs b/src/entry.rs index cce39d45..c81554fc 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -31,6 +31,7 @@ pub struct EntryFields<'a> { pub long_pathname: Option>, pub long_linkname: Option>, pub pax_extensions: Option>, + pub mask: u32, pub header: Header, pub size: u64, pub header_pos: u64, @@ -231,6 +232,20 @@ impl<'a, R: Read> Entry<'a, R> { self.fields.unpack_in(dst.as_ref()) } + /// Set the mask of the permission bits when unpacking this entry. + /// + /// The mask will be inverted when applying against a mode, similar to how + /// `umask` works on Unix. In logical notation it looks like: + /// + /// ```text + /// new_mode = old_mode & (~mask) + /// ``` + /// + /// The mask is 0 by default and is currently only implemented on Unix. + pub fn set_mask(&mut self, mask: u32) { + self.fields.mask = mask; + } + /// Indicate whether extended file attributes (xattrs on Unix) are preserved /// when unpacking this entry. /// @@ -449,6 +464,7 @@ impl<'a> EntryFields<'a> { dst: &Path, f: Option<&mut std::fs::File>, header: &Header, + mask: u32, perms: bool, ownerships: bool, ) -> io::Result<()> { @@ -458,7 +474,7 @@ impl<'a> EntryFields<'a> { } // ... then set permissions, SUID bits set here is kept if let Ok(mode) = header.mode() { - set_perms(dst, f, mode, perms)?; + set_perms(dst, f, mode, mask, perms)?; } Ok(()) @@ -485,6 +501,7 @@ impl<'a> EntryFields<'a> { dst, None, &self.header, + self.mask, self.preserve_permissions, self.preserve_ownerships, )?; @@ -601,6 +618,7 @@ impl<'a> EntryFields<'a> { dst, None, &self.header, + self.mask, self.preserve_permissions, self.preserve_ownerships, )?; @@ -676,6 +694,7 @@ impl<'a> EntryFields<'a> { dst, Some(&mut f), &self.header, + self.mask, self.preserve_permissions, self.preserve_ownerships, )?; @@ -760,9 +779,10 @@ impl<'a> EntryFields<'a> { dst: &Path, f: Option<&mut std::fs::File>, mode: u32, + mask: u32, preserve: bool, ) -> Result<(), TarError> { - _set_perms(dst, f, mode, preserve).map_err(|e| { + _set_perms(dst, f, mode, mask, preserve).map_err(|e| { TarError::new( format!( "failed to set permissions to {:o} \ @@ -780,11 +800,13 @@ impl<'a> EntryFields<'a> { dst: &Path, f: Option<&mut std::fs::File>, mode: u32, + mask: u32, preserve: bool, ) -> io::Result<()> { use std::os::unix::prelude::*; let mode = if preserve { mode } else { mode & 0o777 }; + let mode = mode & !mask; let perm = fs::Permissions::from_mode(mode as _); match f { Some(f) => f.set_permissions(perm), @@ -797,6 +819,7 @@ impl<'a> EntryFields<'a> { dst: &Path, f: Option<&mut std::fs::File>, mode: u32, + _mask: u32, _preserve: bool, ) -> io::Result<()> { if mode & 0o200 == 0o200 { @@ -822,6 +845,7 @@ impl<'a> EntryFields<'a> { dst: &Path, f: Option<&mut std::fs::File>, mode: u32, + mask: u32, _preserve: bool, ) -> io::Result<()> { Err(io::Error::new(io::ErrorKind::Other, "Not implemented")) diff --git a/tests/all.rs b/tests/all.rs index 90b60849..8c5359c5 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -768,6 +768,40 @@ fn backslash_treated_well() { assert!(fs::metadata(td.path().join("foo\\bar")).is_ok()); } +#[test] +#[cfg(unix)] +fn set_mask() { + use ::std::os::unix::fs::PermissionsExt; + let mut ar = tar::Builder::new(Vec::new()); + + let mut header = tar::Header::new_gnu(); + header.set_size(0); + header.set_entry_type(tar::EntryType::Regular); + t!(header.set_path("foo")); + header.set_mode(0o777); + header.set_cksum(); + t!(ar.append(&header, &[][..])); + + let mut header = tar::Header::new_gnu(); + header.set_size(0); + header.set_entry_type(tar::EntryType::Regular); + t!(header.set_path("bar")); + header.set_mode(0o421); + header.set_cksum(); + t!(ar.append(&header, &[][..])); + + let td = t!(TempBuilder::new().prefix("tar-rs").tempdir()); + let bytes = t!(ar.into_inner()); + let mut ar = tar::Archive::new(&bytes[..]); + ar.set_mask(0o211); + t!(ar.unpack(td.path())); + + let md = t!(fs::metadata(td.path().join("foo"))); + assert_eq!(md.permissions().mode(), 0o100566); + let md = t!(fs::metadata(td.path().join("bar"))); + assert_eq!(md.permissions().mode(), 0o100420); +} + #[cfg(unix)] #[test] fn nul_bytes_in_path() { diff --git a/tests/entry.rs b/tests/entry.rs index fa8eeaee..62df663e 100644 --- a/tests/entry.rs +++ b/tests/entry.rs @@ -180,6 +180,37 @@ fn directory_maintains_permissions() { assert_eq!(md.permissions().mode(), 0o40777); } +#[test] +#[cfg(unix)] +fn set_entry_mask() { + use ::std::os::unix::fs::PermissionsExt; + + let mut ar = tar::Builder::new(Vec::new()); + + let mut header = tar::Header::new_gnu(); + header.set_size(0); + header.set_entry_type(tar::EntryType::Regular); + t!(header.set_path("foo")); + header.set_mode(0o777); + header.set_cksum(); + t!(ar.append(&header, &[][..])); + + let bytes = t!(ar.into_inner()); + let mut ar = tar::Archive::new(&bytes[..]); + let td = t!(Builder::new().prefix("tar").tempdir()); + let foo_path = td.path().join("foo"); + + let mut entries = t!(ar.entries()); + let mut foo = t!(entries.next().unwrap()); + foo.set_mask(0o027); + t!(foo.unpack(&foo_path)); + + let f = t!(File::open(foo_path)); + let md = t!(f.metadata()); + assert!(md.is_file()); + assert_eq!(md.permissions().mode(), 0o100750); +} + #[test] #[cfg(not(windows))] // dangling symlinks have weird permissions fn modify_link_just_created() {