From 3bbb4047dcfe2cc6b5e44a2c416db25c34485a8c Mon Sep 17 00:00:00 2001 From: Andrew Pennebaker Date: Fri, 23 Jan 2026 15:07:27 -0600 Subject: [PATCH 1/7] isolate configuration between preserving vs overriding modes --- cpio-archive/src/odc.rs | 52 +++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/cpio-archive/src/odc.rs b/cpio-archive/src/odc.rs index 486a44c06..74dc3756f 100644 --- a/cpio-archive/src/odc.rs +++ b/cpio-archive/src/odc.rs @@ -18,6 +18,7 @@ use { std::{ collections::HashSet, ffi::CStr, + fs, io::{Read, Take, Write}, path::Path, }, @@ -70,6 +71,33 @@ fn write_octal(value: u64, writer: &mut impl Write, size: usize) -> CpioResult<( Ok(()) } +/// permissions_to_u32 converts fs::Permissions objects to chmod integers. +pub fn permissions_to_u32(permissions: fs::Permissions) -> u32 { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + permissions.mode() + } + #[cfg(windows)] + { + if permissions.readonly() { + 0o444u32 + } else { + 0o666u32 + } + } +} + +/// GENERIC_DIRECTORY_MODE applies to auto generated directories. +const GENERIC_DIRECTORY_MODE: u32 = S_IFDIR + | S_IRUSR + | S_IWUSR + | S_IXUSR + | S_IRGRP + | S_IXGRP + | S_IROTH + | S_IXOTH; + /// Parsed portable ASCII format header. #[derive(Clone, Debug)] pub struct OdcHeader { @@ -297,8 +325,8 @@ pub struct OdcBuilder { default_uid: u32, default_gid: u32, default_mtime: DateTime, - default_mode_file: u32, - default_mode_dir: u32, + default_mode_file: Option, + default_mode_dir: Option, auto_write_dirs: bool, seen_dirs: HashSet, entry_count: u32, @@ -313,15 +341,8 @@ impl OdcBuilder { default_uid: 0, default_gid: 0, default_mtime: Utc::now(), - default_mode_file: S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, - default_mode_dir: S_IFDIR - | S_IRUSR - | S_IWUSR - | S_IXUSR - | S_IRGRP - | S_IXGRP - | S_IROTH - | S_IXOTH, + default_mode_file: None, + default_mode_dir: None, auto_write_dirs: true, seen_dirs: HashSet::new(), entry_count: 0, @@ -330,12 +351,12 @@ impl OdcBuilder { } /// Set the default file mode to use for files. - pub fn default_mode_file(&mut self, mode: u32) { + pub fn default_mode_file(&mut self, mode: Option) { self.default_mode_file = mode; } /// Set the default file mode to use for directories. - pub fn default_mode_directory(&mut self, mode: u32) { + pub fn default_mode_directory(&mut self, mode: Option) { self.default_mode_dir = mode; } @@ -372,7 +393,7 @@ impl OdcBuilder { OdcHeader { dev: 0, inode, - mode: self.default_mode_file, + mode: 0u32, uid: self.default_uid, gid: self.default_gid, nlink: 0, @@ -407,7 +428,7 @@ impl OdcBuilder { if !self.seen_dirs.contains(&dir) { let mut header = self.next_header(); - header.mode = self.default_mode_dir; + header.mode = self.default_mode_file.unwrap_or(GENERIC_DIRECTORY_MODE); header.name = dir.clone(); bytes_written += header.write(&mut self.writer)?; @@ -516,6 +537,7 @@ impl OdcBuilder { let mut header = self.next_header(); header.name = archive_path; header.file_size = metadata.len(); + header.mode = self.default_mode_file.unwrap_or(permissions_to_u32(metadata.permissions())); if path.is_executable() { header.mode |= S_IXUSR | S_IXGRP | S_IXOTH; From 1d544c7f5c833aff60ef4a8b175837c6f3878c52 Mon Sep 17 00:00:00 2001 From: Andrew Pennebaker Date: Fri, 23 Jan 2026 15:16:50 -0600 Subject: [PATCH 2/7] safely preserve file permissions passthrough regardless of data --- Cargo.lock | 10 ---------- cpio-archive/Cargo.toml | 1 - cpio-archive/src/odc.rs | 19 +++++++++++++------ 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8b0d2e48..3366f953f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1145,7 +1145,6 @@ name = "cpio-archive" version = "0.10.0" dependencies = [ "chrono", - "is_executable", "simple-file-manifest", "thiserror 2.0.18", ] @@ -2359,15 +2358,6 @@ dependencies = [ "serde", ] -[[package]] -name = "is_executable" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4" -dependencies = [ - "windows-sys 0.60.2", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" diff --git a/cpio-archive/Cargo.toml b/cpio-archive/Cargo.toml index 272497b36..165fcfee6 100644 --- a/cpio-archive/Cargo.toml +++ b/cpio-archive/Cargo.toml @@ -13,6 +13,5 @@ readme = "README.md" [dependencies] chrono = "0.4.43" -is_executable = "1.0.5" simple-file-manifest = "0.11.0" thiserror = "2.0.18" diff --git a/cpio-archive/src/odc.rs b/cpio-archive/src/odc.rs index 74dc3756f..29e90a647 100644 --- a/cpio-archive/src/odc.rs +++ b/cpio-archive/src/odc.rs @@ -11,7 +11,6 @@ use { crate::{CpioHeader, CpioReader, CpioResult, Error}, chrono::{DateTime, Utc}, - is_executable::IsExecutable, simple_file_manifest::{ FileManifest, S_IFDIR, S_IRGRP, S_IROTH, S_IRUSR, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR, }, @@ -539,10 +538,6 @@ impl OdcBuilder { header.file_size = metadata.len(); header.mode = self.default_mode_file.unwrap_or(permissions_to_u32(metadata.permissions())); - if path.is_executable() { - header.mode |= S_IXUSR | S_IXGRP | S_IXOTH; - } - bytes_written += header.write(&mut self.writer)?; bytes_written += std::io::copy(&mut fh, &mut self.writer)?; @@ -554,7 +549,19 @@ impl OdcBuilder { let mut bytes_written = 0; for (path, entry) in manifest.iter_entries() { - let mode = if entry.is_executable() { 0o755 } else { 0o644 }; + let metadata = path.metadata()?; + let mut mode = permissions_to_u32(metadata.permissions()); + + if metadata.is_file() { + if let Some(m) = self.default_mode_file { + mode = m; + } + } else if metadata.is_dir() { + if let Some(m) = self.default_mode_dir { + mode = m; + } + } + let data = entry.resolve_content()?; bytes_written += self.append_file_from_data(path.display().to_string(), data, mode)?; From c64ca7ec49fc931d07d06a57441f09063c49d108 Mon Sep 17 00:00:00 2001 From: Andrew Pennebaker Date: Tue, 27 Jan 2026 19:45:05 -0600 Subject: [PATCH 3/7] promote header writing to api --- cpio-archive/src/odc.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cpio-archive/src/odc.rs b/cpio-archive/src/odc.rs index 29e90a647..629f90779 100644 --- a/cpio-archive/src/odc.rs +++ b/cpio-archive/src/odc.rs @@ -438,6 +438,18 @@ impl OdcBuilder { Ok(bytes_written) } + /// Append a raw header, such as for a directory. + /// + /// The writer is written as-is. + /// + /// Automatic directory emission is not processed in this mode. + pub fn append_header( + &mut self, + header: OdcHeader, + ) -> CpioResult { + header.write(&mut self.writer) + } + /// Append a raw header and corresponding file data to the writer. /// /// The writer and data are written as-is. From 3bf4fd6b36b04084f0e47d6cf0cb2f89ca850d65 Mon Sep 17 00:00:00 2001 From: Andrew Pennebaker Date: Wed, 28 Jan 2026 04:22:41 -0600 Subject: [PATCH 4/7] fix pax & gnu trailer --- cpio-archive/src/odc.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/cpio-archive/src/odc.rs b/cpio-archive/src/odc.rs index 629f90779..a82e2139d 100644 --- a/cpio-archive/src/odc.rs +++ b/cpio-archive/src/odc.rs @@ -330,6 +330,7 @@ pub struct OdcBuilder { seen_dirs: HashSet, entry_count: u32, finished: bool, + bytes_written: u64, } impl OdcBuilder { @@ -346,6 +347,7 @@ impl OdcBuilder { seen_dirs: HashSet::new(), entry_count: 0, finished: false, + bytes_written: 0, } } @@ -518,6 +520,7 @@ impl OdcBuilder { self.writer.write_all(data)?; bytes_written += data.len() as u64; + self.bytes_written += bytes_written; Ok(bytes_written) } @@ -553,6 +556,7 @@ impl OdcBuilder { bytes_written += header.write(&mut self.writer)?; bytes_written += std::io::copy(&mut fh, &mut self.writer)?; + self.bytes_written += bytes_written; Ok(bytes_written) } @@ -579,6 +583,7 @@ impl OdcBuilder { bytes_written += self.append_file_from_data(path.display().to_string(), data, mode)?; } + self.bytes_written += bytes_written; Ok(bytes_written) } @@ -591,11 +596,33 @@ impl OdcBuilder { pub fn finish(&mut self) -> CpioResult { if !self.finished { let mut header = self.next_header(); + header.dev = 0u32; + header.inode = 0u32; + header.mode = 0u32; + header.uid = 0u32; + header.gid = 0u32; + header.nlink = 1u32; // GNU compatibility + header.rdev = 0u32; + header.mtime = 0u32; + header.file_size = 0u64; header.name = TRAILER.to_string(); let count = header.write(&mut self.writer)?; + self.bytes_written += count; + + let mut padding = 0usize; + let rem = (self.bytes_written as usize) % 512usize; + + if rem != 0 { + padding = 512usize - rem; + let data = vec![0u8; padding]; + self.writer.write_all(&data)?; + } + self.finished = true; - Ok(count) + let bytes_written = count + (padding as u64); + self.bytes_written = bytes_written; + Ok(bytes_written) } else { Ok(0) } From baa0e5e31be49bf3aaa0f0de0cac41aab561b0aa Mon Sep 17 00:00:00 2001 From: Andrew Pennebaker Date: Wed, 28 Jan 2026 04:52:19 -0600 Subject: [PATCH 5/7] fix bytes written running total --- cpio-archive/src/odc.rs | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/cpio-archive/src/odc.rs b/cpio-archive/src/odc.rs index a82e2139d..c758a4d67 100644 --- a/cpio-archive/src/odc.rs +++ b/cpio-archive/src/odc.rs @@ -449,7 +449,10 @@ impl OdcBuilder { &mut self, header: OdcHeader, ) -> CpioResult { - header.write(&mut self.writer) + let bytes_written = header.write(&mut self.writer)?; + self.bytes_written += bytes_written; + + Ok(bytes_written) } /// Append a raw header and corresponding file data to the writer. @@ -471,10 +474,13 @@ impl OdcBuilder { return Err(Error::SizeMismatch); } - let written = header.write(&mut self.writer)?; + let bytes_written = header.write(&mut self.writer)?; + self.bytes_written += bytes_written; self.writer.write_all(data)?; + let data_len = data.len() as u64; + self.bytes_written += data_len; - Ok(written + data.len() as u64) + Ok(bytes_written + data_len) } /// Append a raw header and corresponding data from a reader to the writer. @@ -489,13 +495,16 @@ impl OdcBuilder { header: OdcHeader, reader: &mut impl Read, ) -> CpioResult { - let written = header.write(&mut self.writer)?; - let copied = std::io::copy(reader, &mut self.writer)?; + let bytes_written = header.write(&mut self.writer)?; + self.bytes_written += bytes_written; + let bytes_copied = std::io::copy(reader, &mut self.writer)?; - if copied != header.file_size { + if bytes_copied != header.file_size { Err(Error::SizeMismatch) } else { - Ok(written + copied) + self.bytes_written += bytes_copied; + + Ok(bytes_written + bytes_copied) } } @@ -609,20 +618,22 @@ impl OdcBuilder { let count = header.write(&mut self.writer)?; self.bytes_written += count; - let mut padding = 0usize; + dbg!(self.bytes_written); + + let mut padding_u64 = 0u64; let rem = (self.bytes_written as usize) % 512usize; if rem != 0 { - padding = 512usize - rem; + let padding = 512usize - rem; + padding_u64 = padding as u64; let data = vec![0u8; padding]; self.writer.write_all(&data)?; + self.bytes_written += padding_u64; } self.finished = true; - let bytes_written = count + (padding as u64); - self.bytes_written = bytes_written; - Ok(bytes_written) + Ok(count + padding_u64) } else { Ok(0) } From b1d6b5f38238a8fbb297606ed8dfe0a6180ceb14 Mon Sep 17 00:00:00 2001 From: Andrew Pennebaker Date: Wed, 28 Jan 2026 14:07:21 -0600 Subject: [PATCH 6/7] match pkgbuild trailer inode --- cpio-archive/src/odc.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cpio-archive/src/odc.rs b/cpio-archive/src/odc.rs index c758a4d67..83ee82edc 100644 --- a/cpio-archive/src/odc.rs +++ b/cpio-archive/src/odc.rs @@ -606,7 +606,6 @@ impl OdcBuilder { if !self.finished { let mut header = self.next_header(); header.dev = 0u32; - header.inode = 0u32; header.mode = 0u32; header.uid = 0u32; header.gid = 0u32; @@ -618,13 +617,12 @@ impl OdcBuilder { let count = header.write(&mut self.writer)?; self.bytes_written += count; - dbg!(self.bytes_written); - + let block_size = 512usize; let mut padding_u64 = 0u64; - let rem = (self.bytes_written as usize) % 512usize; + let rem = (self.bytes_written as usize) % block_size; if rem != 0 { - let padding = 512usize - rem; + let padding = block_size - rem; padding_u64 = padding as u64; let data = vec![0u8; padding]; self.writer.write_all(&data)?; From a10a5b1fd40f49b9e42c6cbe281c39a3f679e676 Mon Sep 17 00:00:00 2001 From: Andrew Pennebaker Date: Thu, 29 Jan 2026 16:17:19 -0600 Subject: [PATCH 7/7] update counts --- cpio-archive/src/odc.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cpio-archive/src/odc.rs b/cpio-archive/src/odc.rs index 83ee82edc..d5d14590b 100644 --- a/cpio-archive/src/odc.rs +++ b/cpio-archive/src/odc.rs @@ -564,8 +564,8 @@ impl OdcBuilder { bytes_written += header.write(&mut self.writer)?; bytes_written += std::io::copy(&mut fh, &mut self.writer)?; - self.bytes_written += bytes_written; + Ok(bytes_written) } @@ -592,7 +592,6 @@ impl OdcBuilder { bytes_written += self.append_file_from_data(path.display().to_string(), data, mode)?; } - self.bytes_written += bytes_written; Ok(bytes_written) }