Skip to content

Commit

Permalink
feat: setting permission mask in Entry and Archive (#330)
Browse files Browse the repository at this point in the history
This is convenience when you want to disable some permission bits
during unpacking. Currently only support Unix.
  • Loading branch information
weihanglo committed Jul 5, 2023
1 parent 7025986 commit 1fd8b4e
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 2 deletions.
17 changes: 17 additions & 0 deletions src/archive.rs
Expand Up @@ -22,6 +22,7 @@ pub struct Archive<R: ?Sized + Read> {

pub struct ArchiveInner<R: ?Sized> {
pos: Cell<u64>,
mask: u32,
unpack_xattrs: bool,
preserve_permissions: bool,
preserve_ownerships: bool,
Expand Down Expand Up @@ -53,6 +54,7 @@ impl<R: Read> Archive<R> {
pub fn new(obj: R) -> Archive<R> {
Archive {
inner: ArchiveInner {
mask: u32::MIN,
unpack_xattrs: false,
preserve_permissions: false,
preserve_ownerships: false,
Expand Down Expand Up @@ -108,6 +110,20 @@ impl<R: Read> Archive<R> {
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.
///
Expand Down Expand Up @@ -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,
Expand Down
28 changes: 26 additions & 2 deletions src/entry.rs
Expand Up @@ -31,6 +31,7 @@ pub struct EntryFields<'a> {
pub long_pathname: Option<Vec<u8>>,
pub long_linkname: Option<Vec<u8>>,
pub pax_extensions: Option<Vec<u8>>,
pub mask: u32,
pub header: Header,
pub size: u64,
pub header_pos: u64,
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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<()> {
Expand All @@ -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(())
Expand All @@ -485,6 +501,7 @@ impl<'a> EntryFields<'a> {
dst,
None,
&self.header,
self.mask,
self.preserve_permissions,
self.preserve_ownerships,
)?;
Expand Down Expand Up @@ -601,6 +618,7 @@ impl<'a> EntryFields<'a> {
dst,
None,
&self.header,
self.mask,
self.preserve_permissions,
self.preserve_ownerships,
)?;
Expand Down Expand Up @@ -676,6 +694,7 @@ impl<'a> EntryFields<'a> {
dst,
Some(&mut f),
&self.header,
self.mask,
self.preserve_permissions,
self.preserve_ownerships,
)?;
Expand Down Expand Up @@ -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} \
Expand All @@ -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),
Expand All @@ -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 {
Expand All @@ -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"))
Expand Down
34 changes: 34 additions & 0 deletions tests/all.rs
Expand Up @@ -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() {
Expand Down
31 changes: 31 additions & 0 deletions tests/entry.rs
Expand Up @@ -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() {
Expand Down

0 comments on commit 1fd8b4e

Please sign in to comment.