Repository: kornelski/cargo-deb (Rust)

Issue:
file modes in top-level ar archive don't match dpkg
first bytes in a `.deb` created by dpkg:
```
!<arch>
debian-binary   1611345884  0     0     100644  4         `
2.0
control.tar.xz  1611345884  0     0     100644  10384     `
```

first bytes in a `.deb` created by cargo-deb:
```
!<arch>
debian-binary   1712732414  0     0     644     4         `
2.0
control.tar.gz  1712732414  0     0     644     1435      `
```

notice how the dpkg version has mode `100644`, where the initial `10` indicates that they're files:
https://unix.stackexchange.com/questions/450480/file-permission-with-six-octal-digits-in-git-what-does-it-mean/450488#450488

I encountered this while trying to figure out why `file-roller` couldn't extract the `control.tar.gz` or `data.tar.gz` files...


Discussion:
Please make a PR with a fix.



Patch:
diff --git a/src/debarchive.rs b/src/debarchive.rs
index cb09386..00d2d31 100644
--- a/src/debarchive.rs
+++ b/src/debarchive.rs
@@ -32,7 +32,7 @@ impl DebArchive {
 
     pub fn add_data(&mut self, dest_path: String, mtime_timestamp: u64, data: &[u8]) -> CDResult<()> {
         let mut header = Header::new(dest_path.into(), data.len() as u64);
-        header.set_mode(0o644);
+        header.set_mode(0o100644); // dpkg uses 100644
         header.set_mtime(mtime_timestamp);
         header.set_uid(0);
         header.set_gid(0);


Test patch:
diff --git a/tests/command.rs b/tests/command.rs
index a4eacb6..c3ae031 100644
--- a/tests/command.rs
+++ b/tests/command.rs
@@ -6,6 +6,7 @@ use std::path::{Path, PathBuf};
 use std::process::Command;
 
 use tempfile::TempDir;
+use std::io::{BufReader, BufRead, Read, Seek};
 
 /// file extension of the compression format cargo-deb uses unless explicitly specified.
 const DEFAULT_COMPRESSION_EXT: &str = "xz";
@@ -55,8 +56,73 @@ fn extract_built_package_from_manifest(manifest_path: &str, ext: &str, args: &[&
     extract_package(&deb_path, ext)
 }
 
+#[track_caller]
+fn check_ar(deb_path: &Path) {
+    let mut file = BufReader::new(fs::File::open(deb_path).unwrap());
+    let mut line = String::new();
+    file.read_line(&mut line).unwrap();
+    assert_eq!(line, "!<arch>\n");
+    struct Expected {
+        name_prefix: &'static str,
+        data: Option<&'static [u8]>,
+    }
+    const EXPECTED: &'static [Expected] = &[
+        Expected {
+            name_prefix: "debian-binary   ",
+            data: Some(b"2.0\n"),
+        },
+        Expected {
+            name_prefix: "control.tar.",
+            data: None,
+        },
+        Expected {
+            name_prefix: "data.tar.",
+            data: None,
+        },
+    ];
+    let mut data = Vec::new();
+    for expected in EXPECTED {
+        if file.stream_position().unwrap() % 2 != 0 {
+            line.clear();
+            file.read_line(&mut line).unwrap();
+            assert_eq!(line, "\n");
+        }
+        line.clear();
+        file.read_line(&mut line).unwrap();
+        assert_eq!(line.len(), 60);
+        let name = &line[..16];
+        assert!(name.starts_with(expected.name_prefix));
+        let mtime_str = &line[16..28];
+        let mtime: u64 = mtime_str.trim().parse().unwrap();
+        assert_eq!(mtime_str, format!("{mtime:<12}"));
+        let owner_id = &line[28..34];
+        assert_eq!(owner_id, "0     ");
+        let group_id = &line[34..40];
+        assert_eq!(group_id, "0     ");
+        let file_type_and_mode = &line[40..48];
+        assert_eq!(file_type_and_mode, "100644  "); // dpkg uses 100644
+        let file_size_str = &line[48..58];
+        let file_size: u64 = file_size_str.trim().parse().unwrap();
+        assert_eq!(file_size_str, format!("{file_size:<10}"));
+        data.resize(file_size.try_into().unwrap(), 0);
+        file.read_exact(&mut data).unwrap();
+        if let Some(expected_data) = expected.data {
+            assert_eq!(data, expected_data);
+        }
+    }
+    let allowed_trailing_nl = file.stream_position().unwrap() % 2 != 0;
+    data.clear();
+    file.read_to_end(&mut data).unwrap();
+    match &*data {
+        [] => {}
+        b"\n" if allowed_trailing_nl => {}
+        _ => panic!("unexpected trailing data"),
+    }
+}
+
 #[track_caller]
 fn extract_package(deb_path: &Path, ext: &str) -> (TempDir, TempDir) {
+    check_ar(deb_path);
     let ardir = tempfile::tempdir().expect("testdir");
     assert!(ardir.path().exists());
     assert!(Command::new("ar")
@@ -106,7 +172,7 @@ fn cargo_deb(manifest_path: &str, args: &[&str]) -> (TempDir, PathBuf) {
         .args(args)
         .output()
         .unwrap();
-    assert!(output.status.success(), 
+    assert!(output.status.success(),
             "Cmd failed: {}\n{}",
             String::from_utf8_lossy(&output.stdout),
             String::from_utf8_lossy(&output.stderr)