Skip to content

Commit 62ee267

Browse files
committed
parse: Add tests for mixed PAX+GNU extension ordering
Test the PAX 'x' -> GNU 'L' -> real entry ordering, which is what tar-rs's builder produces when you call append_pax_extensions() followed by append_data() with a long path. This matters for ecosystem compatibility -- bootc's copy_entry (bootc-dev/bootc#2073) generates exactly this layout when filtering PAX extensions during path remapping. The parser already handles this correctly via PendingMetadata accumulation across recursive parse_header calls, but the reversed ordering was untested. Also test that PAX path still wins over GNU long name regardless of which comes first in the byte stream. Assisted-by: OpenCode (Claude Opus 4) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent ac1e86a commit 62ee267

1 file changed

Lines changed: 73 additions & 0 deletions

File tree

src/parse.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2563,6 +2563,79 @@ mod tests {
25632563
}
25642564
}
25652565

2566+
#[test]
2567+
fn test_parser_pax_before_gnu_long_name() {
2568+
// PAX 'x' -> GNU 'L' -> real entry: this is what tar-rs's builder
2569+
// produces when you call append_pax_extensions() (e.g. for xattrs)
2570+
// followed by append_data() with a long path. The PAX metadata
2571+
// should still be associated with the real entry, and PAX path
2572+
// (if present) should take precedence over the GNU long name.
2573+
//
2574+
// This ordering matters for ecosystem compatibility with bootc
2575+
// (see bootc-dev/bootc#2073).
2576+
let gnu_name =
2577+
"gnu/long/name/that/exceeds/one/hundred/bytes/".to_string() + &"g".repeat(60);
2578+
let xattr_value = b"some xattr value";
2579+
2580+
let mut archive = Vec::new();
2581+
// PAX header first (with xattr but no path -- simulating bootc's
2582+
// copy_entry which strips path/linkpath from PAX)
2583+
archive.extend(make_pax_header(&[(
2584+
"SCHILY.xattr.user.test",
2585+
xattr_value.as_slice(),
2586+
)]));
2587+
// GNU long name second
2588+
archive.extend(make_gnu_long_name(gnu_name.as_bytes()));
2589+
// Real entry last
2590+
archive.extend_from_slice(&make_header(b"placeholder", 0, b'0'));
2591+
archive.extend(zeroes(1024));
2592+
2593+
let mut parser = Parser::new(Limits::default());
2594+
let event = parser.parse(&archive).unwrap();
2595+
2596+
match event {
2597+
ParseEvent::Entry { entry, .. } => {
2598+
// GNU long name should be used (no PAX path to override it)
2599+
assert_eq!(entry.path.as_ref(), gnu_name.as_bytes());
2600+
// PAX xattr should still be preserved
2601+
assert!(entry.pax.is_some());
2602+
let pax = PaxExtensions::new(entry.pax.unwrap());
2603+
let xattr = pax
2604+
.filter_map(|e| e.ok())
2605+
.find(|e| e.key_bytes().starts_with(b"SCHILY.xattr."));
2606+
assert!(xattr.is_some(), "xattr should be preserved");
2607+
assert_eq!(xattr.unwrap().value_bytes(), xattr_value);
2608+
}
2609+
other => panic!("Expected Entry, got {:?}", other),
2610+
}
2611+
}
2612+
2613+
#[test]
2614+
fn test_parser_pax_path_overrides_gnu_long_name_reversed_order() {
2615+
// Same as test_parser_combined_gnu_pax but with reversed ordering:
2616+
// PAX 'x' (with path) -> GNU 'L' -> real entry.
2617+
// PAX path should still win regardless of order.
2618+
let gnu_name = "gnu/long/name/".to_string() + &"g".repeat(100);
2619+
let pax_path = "pax/should/still/win/file.txt";
2620+
2621+
let mut archive = Vec::new();
2622+
// PAX first this time (reversed from test_parser_combined_gnu_pax)
2623+
archive.extend(make_pax_header(&[("path", pax_path.as_bytes())]));
2624+
archive.extend(make_gnu_long_name(gnu_name.as_bytes()));
2625+
archive.extend_from_slice(&make_header(b"header.txt", 0, b'0'));
2626+
archive.extend(zeroes(1024));
2627+
2628+
let mut parser = Parser::new(Limits::default());
2629+
let event = parser.parse(&archive).unwrap();
2630+
2631+
match event {
2632+
ParseEvent::Entry { entry, .. } => {
2633+
assert_eq!(entry.path.as_ref(), pax_path.as_bytes());
2634+
}
2635+
other => panic!("Expected Entry, got {:?}", other),
2636+
}
2637+
}
2638+
25662639
#[test]
25672640
fn test_parser_gnu_long_name_and_link_combined() {
25682641
// Both GNU long name and long link for the same entry

0 commit comments

Comments
 (0)