Skip to content

Commit 2d0bed7

Browse files
committed
🐛 Fix Windows path prefix stripping for bsdtar compat
Disable MSYS2 automatic path conversion in the pnatar wrapper to prevent mangling of Windows absolute paths (\\?\, \\.\, drive letters) passed by bsdtar's test_windows. Add cfg(windows) tests covering all 8 path prefix types from test_windows.c.
1 parent d41b42f commit 2d0bed7

2 files changed

Lines changed: 114 additions & 0 deletions

File tree

.github/scripts/pnatar-wrapper.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,11 @@ for arg in "$@"; do
2323
esac
2424
done
2525

26+
# Prevent MSYS2 from converting POSIX-style path arguments to Windows paths
27+
# when launching the native Windows pna.exe binary. Without this, paths like
28+
# /path/to/file get mangled to C:\msys64\path\to\file, breaking bsdtar's
29+
# test_windows which passes 8 varieties of Windows absolute paths.
30+
export MSYS_NO_PATHCONV=1
31+
export MSYS2_ARG_CONV_EXCL='*'
32+
2633
exec "$pna_bin" --log-level error experimental stdio --unstable "${expanded[@]}"

cli/src/command/core/path.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,4 +534,111 @@ mod tests {
534534
let editor = PathnameEditor::new(Some(2), None, false, true);
535535
assert!(editor.edit_entry_name(Path::new("./a")).is_none());
536536
}
537+
538+
// --- Windows path prefix stripping (bsdtar test_windows compat) ---
539+
//
540+
// bsdtar's strip_absolute_path() strips Windows API prefixes, drive letters,
541+
// and leading separators. On Windows, Rust's Path::components() correctly
542+
// parses these as Prefix/RootDir components, which sanitize_preserve_curdir
543+
// filters out — producing the same result as bsdtar.
544+
//
545+
// These 8 types correspond to bsdtar's test_windows.c mkfullpath() types.
546+
547+
#[cfg(windows)]
548+
#[test]
549+
fn editor_windows_type0_forward_slash_absolute() {
550+
// Type 0: /path/to/file — leading / stripped
551+
let editor = PathnameEditor::new(None, None, false, true);
552+
let name = editor
553+
.edit_entry_name(Path::new("/msys64/tmp/file"))
554+
.unwrap();
555+
assert_eq!(name.as_str(), "msys64/tmp/file");
556+
}
557+
558+
#[cfg(windows)]
559+
#[test]
560+
fn editor_windows_type1_backslash_absolute() {
561+
// Type 1: \path\to\file — leading \ stripped
562+
let editor = PathnameEditor::new(None, None, false, true);
563+
let name = editor
564+
.edit_entry_name(Path::new("\\msys64\\tmp\\file"))
565+
.unwrap();
566+
assert_eq!(name.as_str(), "msys64/tmp/file");
567+
}
568+
569+
#[cfg(windows)]
570+
#[test]
571+
fn editor_windows_type2_drive_forward_slash() {
572+
// Type 2: C:/path/to/file — C: and / stripped
573+
let editor = PathnameEditor::new(None, None, false, true);
574+
let name = editor
575+
.edit_entry_name(Path::new("C:/msys64/tmp/file"))
576+
.unwrap();
577+
assert_eq!(name.as_str(), "msys64/tmp/file");
578+
}
579+
580+
#[cfg(windows)]
581+
#[test]
582+
fn editor_windows_type3_drive_backslash() {
583+
// Type 3: C:\path\to\file — C: and \ stripped
584+
let editor = PathnameEditor::new(None, None, false, true);
585+
let name = editor
586+
.edit_entry_name(Path::new("C:\\msys64\\tmp\\file"))
587+
.unwrap();
588+
assert_eq!(name.as_str(), "msys64/tmp/file");
589+
}
590+
591+
#[cfg(windows)]
592+
#[test]
593+
fn editor_windows_type4_device_forward_slash() {
594+
// Type 4: //./C:/path/to/file — //./ and C: and / stripped
595+
let editor = PathnameEditor::new(None, None, false, true);
596+
let name = editor
597+
.edit_entry_name(Path::new("//./C:/msys64/tmp/file"))
598+
.unwrap();
599+
assert_eq!(name.as_str(), "msys64/tmp/file");
600+
}
601+
602+
#[cfg(windows)]
603+
#[test]
604+
fn editor_windows_type5_device_backslash() {
605+
// Type 5: \\.\C:\path\to\file — \\.\ and C: and \ stripped
606+
let editor = PathnameEditor::new(None, None, false, true);
607+
let name = editor
608+
.edit_entry_name(Path::new("\\\\.\\C:\\msys64\\tmp\\file"))
609+
.unwrap();
610+
assert_eq!(name.as_str(), "msys64/tmp/file");
611+
}
612+
613+
#[cfg(windows)]
614+
#[test]
615+
fn editor_windows_type6_verbatim_forward_slash() {
616+
// Type 6: //?/C:/path/to/file — //?/ and C: and / stripped
617+
let editor = PathnameEditor::new(None, None, false, true);
618+
let name = editor
619+
.edit_entry_name(Path::new("//?/C:/msys64/tmp/file"))
620+
.unwrap();
621+
assert_eq!(name.as_str(), "msys64/tmp/file");
622+
}
623+
624+
#[cfg(windows)]
625+
#[test]
626+
fn editor_windows_type7_verbatim_backslash() {
627+
// Type 7: \\?\C:\path\to\file — \\?\ and C: and \ stripped
628+
let editor = PathnameEditor::new(None, None, false, true);
629+
let name = editor
630+
.edit_entry_name(Path::new("\\\\?\\C:\\msys64\\tmp\\file"))
631+
.unwrap();
632+
assert_eq!(name.as_str(), "msys64/tmp/file");
633+
}
634+
635+
#[cfg(windows)]
636+
#[test]
637+
fn editor_windows_backslash_to_forward_slash_conversion() {
638+
// bsdtar converts \ to / in entry names; pna's join_components_forward_slash
639+
// achieves the same by reconstructing paths with / separators.
640+
let editor = PathnameEditor::new(None, None, false, true);
641+
let name = editor.edit_entry_name(Path::new("C:\\a\\b\\c")).unwrap();
642+
assert_eq!(name.as_str(), "a/b/c");
643+
}
537644
}

0 commit comments

Comments
 (0)