diff --git a/src/archive.rs b/src/archive.rs index e875124a..d572ef01 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -222,7 +222,16 @@ impl Archive { file.unpack_in(dst)?; } } - for mut dir in directories { + + // Work from leaves up, with the assumption* that entries for + // parent directories preceed their children. Otherwise when + // a parent directory has no write permission, any empty + // subdirectories beneath it cannot be created. + // + // [*] If sufficiently motivated, one can create an archive + // which violates this. In practice, archives typically are + // structured this way. + for mut dir in directories.into_iter().rev() { dir.unpack_in(dst)?; } diff --git a/tests/all.rs b/tests/all.rs index fa38ef62..dad8e9a1 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -1317,6 +1317,33 @@ fn read_only_directory_containing_files() { assert!(ar.unpack(td.path()).is_ok()); } +#[test] +fn read_only_directory_containing_empty_directory() { + let td = t!(TempBuilder::new().prefix("tar-rs").tempdir()); + + let mut b = Builder::new(Vec::::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/subdir")); + h.set_size(0); + h.set_entry_type(EntryType::dir()); + h.set_mode(0o755); + h.set_cksum(); + t!(b.append(&h, "".as_bytes())); + + let contents = t!(b.into_inner()); + let mut ar = Archive::new(&contents[..]); + assert!(ar.unpack(td.path()).is_ok()); +} + // This test was marked linux only due to macOS CI can't handle `set_current_dir` correctly #[test] #[cfg(target_os = "linux")]