Skip to content

Commit

Permalink
Extract directory entries after all others when using Archive::unpack. (
Browse files Browse the repository at this point in the history
#249)

If they are extracted in order, they may fail if a directory's
permissions prevent descendant files from being written. So extracting
them after all other entries ensures that any files that need to be
created can be without being subject to the final permissions.

Closes #242.

Co-authored-by: Alex Franchuk <afranchuk@caci.com>
  • Loading branch information
afranchuk and afranchuk committed May 21, 2021
1 parent 326e990 commit 7f2a355
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 1 deletion.
14 changes: 13 additions & 1 deletion src/archive.rs
Expand Up @@ -172,10 +172,22 @@ impl<'a> Archive<dyn Read + 'a> {
// NotFound exception.
let dst = &dst.canonicalize().unwrap_or(dst.to_path_buf());

// Delay any directory entries until the end (they will be created if needed by
// descendants), to ensure that directory permissions do not interfer with descendant
// extraction.
let mut directories = Vec::new();
for entry in self._entries()? {
let mut file = entry.map_err(|e| TarError::new("failed to iterate over archive", e))?;
file.unpack_in(dst)?;
if file.header().entry_type() == crate::EntryType::Directory {
directories.push(file);
} else {
file.unpack_in(dst)?;
}
}
for mut dir in directories {
dir.unpack_in(dst)?;
}

Ok(())
}

Expand Down
26 changes: 26 additions & 0 deletions tests/all.rs
Expand Up @@ -1149,3 +1149,29 @@ fn append_long_multibyte() {
name.pop();
}
}

#[test]
fn read_only_directory_containing_files() {
let td = t!(TempBuilder::new().prefix("tar-rs").tempdir());

let mut b = Builder::new(Vec::<u8>::new());

let mut h = Header::new_gnu();
t!(h.set_path("dir/"));
h.set_size(0);
h.set_entry_type(EntryType::dir());
h.set_mode(0o444);
h.set_cksum();
t!(b.append(&h, "".as_bytes()));

let mut h = Header::new_gnu();
t!(h.set_path("dir/file"));
h.set_size(2);
h.set_entry_type(EntryType::file());
h.set_cksum();
t!(b.append(&h, "hi".as_bytes()));

let contents = t!(b.into_inner());
let mut ar = Archive::new(&contents[..]);
assert!(ar.unpack(td.path()).is_ok());
}

0 comments on commit 7f2a355

Please sign in to comment.