pull: create the addon directory when unwrapping a multi-orig component

unwrap_into pointed the component's unwrap at '<root>/<addon>' but
never created that directory: unwrap_wrapper moves the wrapper's
entries into it by rename, and a rename whose target parent is
missing fails with ENOENT - every entry, aborting the whole pull.
The multi-orig unit test exercised unwrap_wrapper directly and could
not see it (dovecot).

Also exercise the component through unwrap_into in the test, and
assert the wrapper's own layout does not leak to the root - the
shape dpkg-source -b rejects as unexpected upstream changes.
This commit is contained in:
2026-10-02 01:43:54 +02:00
parent 5e4b9cb231
commit 14704db777
+23 -9
View File
@@ -173,6 +173,7 @@ fn unwrap_into(dir: &Path, wrapper: &Path, addon: Option<&str>) -> Result<(), Bo
if target.is_dir() { if target.is_dir() {
std::fs::remove_dir_all(&target)?; std::fs::remove_dir_all(&target)?;
} }
std::fs::create_dir_all(&target)?;
target target
} }
None => dir.to_path_buf(), None => dir.to_path_buf(),
@@ -1396,19 +1397,26 @@ mod tests {
#[test] #[test]
fn test_unwrap_wrapper_of_second_multi_orig_component() { fn test_unwrap_wrapper_of_second_multi_orig_component() {
// Regression: multi-orig packages (e.g. dovecot) extract a second // Regression: multi-orig packages (e.g. dovecot) extract a second
// component into a tree root the first component already filled; // component into a tree root the first component already filled,
// recognizing the wrapper from the shape of the root skipped the // and dpkg-source expects the component's content at
// second component's unwrap and left its sources nested one level // '<root>/<addon>' ('pigeonhole' for '...orig-pigeonhole.tar.gz'),
// too deep // not the wrapper's own layout at the root. The addon directory
// used to never be created: every move into it failed with ENOENT
// and the pull aborted.
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let tree = temp_dir.path().join("tree"); let tree = temp_dir.path().join("tree");
let main = temp_dir.path().join("main.orig.tar"); let main = temp_dir.path().join("main.orig.tar");
std::fs::write(&main, build_tar(&[("dovecot-2.4.5/README", b"main")])).unwrap(); std::fs::write(&main, build_tar(&[("dovecot-2.4.5/README", b"main")])).unwrap();
let extracted = extract_tar_archive(&main, &tree, None, |f| f).unwrap(); let extracted = extract_tar_archive(&main, &tree, None, |f| f).unwrap();
unwrap_wrapper(&tree, &extracted.wrapper.unwrap()).unwrap(); unwrap_into(
&tree,
&extracted.wrapper.unwrap(),
component_addon("main.orig.tar"),
)
.unwrap();
let component = temp_dir.path().join("component.orig.tar"); let component = temp_dir.path().join("pkg_1.0.orig-pigeonhole.tar.gz");
std::fs::write( std::fs::write(
&component, &component,
build_tar(&[ build_tar(&[
@@ -1418,20 +1426,26 @@ mod tests {
) )
.unwrap(); .unwrap();
let extracted = extract_tar_archive(&component, &tree, None, |f| f).unwrap(); let extracted = extract_tar_archive(&component, &tree, None, |f| f).unwrap();
unwrap_wrapper(&tree, &extracted.wrapper.unwrap()).unwrap(); unwrap_into(
&tree,
&extracted.wrapper.unwrap(),
component_addon("pkg_1.0.orig-pigeonhole.tar.gz"),
)
.unwrap();
assert_eq!( assert_eq!(
std::fs::read_to_string(tree.join("README")).unwrap(), std::fs::read_to_string(tree.join("README")).unwrap(),
"main" "main"
); );
assert_eq!( assert_eq!(
std::fs::read_to_string(tree.join("update-version.sh")).unwrap(), std::fs::read_to_string(tree.join("pigeonhole/update-version.sh")).unwrap(),
"sh" "sh"
); );
assert_eq!( assert_eq!(
std::fs::read_to_string(tree.join("pigeonhole/x")).unwrap(), std::fs::read_to_string(tree.join("pigeonhole/pigeonhole/x")).unwrap(),
"ph" "ph"
); );
assert!(!tree.join("update-version.sh").exists());
assert!(!tree.join("dovecot-2.4.5").exists()); assert!(!tree.join("dovecot-2.4.5").exists());
assert!(!tree.join("dovecot-pigeonhole-2.4.5").exists()); assert!(!tree.join("dovecot-pigeonhole-2.4.5").exists());
} }