Skip to content

Commit

Permalink
Add modifications for the RPM-specific "stripped cpio" format
Browse files Browse the repository at this point in the history
  • Loading branch information
dralley committed May 21, 2024
1 parent db38d75 commit eb4d3a4
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 21 deletions.
26 changes: 17 additions & 9 deletions src/rpm/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -724,12 +724,17 @@ impl PackageBuilder {
let mut combined_file_sizes: u64 = 0;
let mut uses_file_capabilities = false;

for (_, entry) in self.files.iter() {
combined_file_sizes += entry.size;
}

let uses_large_files = combined_file_sizes > u32::MAX.into();

// @todo: sort entries by path?
// @todo: normalize path?
// @todo: remove duplicates?
// if we remove duplicates, remember anything already pre-computed
for (cpio_path, entry) in self.files.iter() {
combined_file_sizes += entry.size;
if entry.caps.is_some() {
uses_file_capabilities = true;
}
Expand Down Expand Up @@ -770,12 +775,17 @@ impl PackageBuilder {
// @todo: is there a use case for not performing all verifications? and are we performing those verifications currently anyway?
file_verify_flags.push(FileVerifyFlags::all().bits());
let content = entry.content.to_owned();
let mut writer = payload::Builder::new(cpio_path)
.mode(entry.mode.into())
.ino(ino_index)
.uid(self.uid.unwrap_or(0))
.gid(self.gid.unwrap_or(0))
.write(&mut archive, content.len() as u32);
let mut writer = if !uses_large_files {
payload::Builder::new(cpio_path)
.mode(entry.mode.into())
.ino(ino_index)
.uid(self.uid.unwrap_or(0))
.gid(self.gid.unwrap_or(0))
.write_cpio(&mut archive, content.len() as u32)
} else {
payload::Builder::new(cpio_path)
.write_stripped_cpio(&mut archive, content.len() as u32)
};

writer.write_all(&content)?;
writer.finish()?;
Expand All @@ -784,8 +794,6 @@ impl PackageBuilder {
}
payload::trailer(&mut archive)?;

let uses_large_files = combined_file_sizes > u32::MAX.into();

self.provides
.push(Dependency::eq(self.name.clone(), self.version.clone()));
self.provides.push(Dependency::eq(
Expand Down
73 changes: 61 additions & 12 deletions src/rpm/payload.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// This code is copied from the crate `cpio-rs` by Jonathan Creekmore (https://github.com/jcreekmore/cpio-rs)
// under the MIT license.
//! Read/write `newc` (SVR4) format archives.
// The code in this file is partially copied from the `cpio` crate by Jonathan Creekmore
// (https://github.com/jcreekmore/cpio-rs) under the MIT license.
//
// MIT License
//
Expand All @@ -19,16 +21,28 @@
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

//! Read/write `newc` (SVR4) format archives.
//
// It has been modified to match modifications which are used by the upstream RPM library.
// These modifications are described upstream as follows:
//
// As cpio is limited to 4 GB (32 bit unsigned) file sizes, RPM since version 4.12
// uses a stripped down version of cpio for packages with files > 4 GB. This format
// uses 07070X as magic bytes and the file header otherwise only contains the index
// number of the file in the RPM header as 8 byte hex string. The file metadata that
// is normally found in a cpio file header - including the file name - is completely
// omitted as it is stored in the RPM header already.

use std::io::{self, Read, Seek, SeekFrom, Write};

const HEADER_LEN: usize = 110; // 6 byte magic number + 104 bytes of metadata
const HEADER_LEN: usize = 110; // 6 byte magic + 104 bytes for metadata

const STRIPPED_CPIO_HEADER_LEN: usize = 14; // 8 bytes metadata + 6 bytes magic

const MAGIC_NUMBER_NEWASCII: &[u8] = b"070701";
const MAGIC_NUMBER_NEWCRC: &[u8] = b"070702";

const STRIPPED_CPIO_MAGIC_NUMBER: &[u8] = b"07070X"; // Magic number changed to conform with RPM

const TRAILER_NAME: &str = "TRAILER!!!";

/// Whether this header is of the "new ascii" form (without checksum) or the "crc" form which
Expand Down Expand Up @@ -283,6 +297,12 @@ impl<R: Read> Reader<R> {

// NUL-terminated name with length `name_len` (including NUL byte).
let mut name_bytes = vec![0u8; name_len];
if name_bytes.len() < 0 || name_bytes.len() > 4096 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Entry name is too long",
));
}
inner.read_exact(&mut name_bytes)?;
if name_bytes.last() != Some(&0) {
return Err(io::Error::new(
Expand Down Expand Up @@ -505,7 +525,7 @@ impl Builder {
}

/// Write out an entry to the provided writer in SVR4 "new ascii" CPIO format.
pub fn write<W: Write>(self, w: W, file_size: u32) -> Writer<W> {
pub fn write_cpio<W: Write>(self, w: W, file_size: u32) -> Writer<W> {
let header = self.into_header(file_size, None);

Writer {
Expand All @@ -530,6 +550,18 @@ impl Builder {
}
}

pub fn write_stripped_cpio<W: Write>(self, w: W, file_size: u32) -> Writer<W> {
let header = self.into_stripped_cpio_header(file_size);

Writer {
inner: w,
written: 0,
file_size,
header_size: header.len(),
header,
}
}

/// Build a newc header from the entry metadata.
fn into_header(self, file_size: u32, file_checksum: Option<u32>) -> Vec<u8> {
let mut header = Vec::with_capacity(HEADER_LEN);
Expand Down Expand Up @@ -579,6 +611,23 @@ impl Builder {

header
}

fn into_stripped_cpio_header(self, file_size: u32) -> Vec<u8> {
let mut header = Vec::with_capacity(STRIPPED_CPIO_HEADER_LEN);

// char c_magic[6];
header.extend(STRIPPED_CPIO_MAGIC_NUMBER);

// char fx[8];
header.extend(format!("{:08x}", 0).as_bytes()); // @todo: I'm pretty sure "fx" is file index, but I have no idea how those values work

// pad out to a multiple of 4 bytes
if let Some(pad) = pad(STRIPPED_CPIO_HEADER_LEN) {
header.extend(pad);
}

header
}
}

impl<W: Write> Writer<W> {
Expand Down Expand Up @@ -633,7 +682,7 @@ impl<W: Write> Write for Writer<W> {
/// Writes a trailer entry into an archive.
pub fn trailer<W: Write>(w: W) -> io::Result<W> {
let b = Builder::new(TRAILER_NAME).nlink(1);
let writer = b.write(w, 0);
let writer = b.write_cpio(w, 0);
writer.finish()
}

Expand All @@ -655,7 +704,7 @@ mod tests {
// Set up the descriptor of our input file
let b = Builder::new("./hello_world");
// and get a writer for that input file
let mut writer = b.write(output, length);
let mut writer = b.write_cpio(output, length);

// Copy the input file into our CPIO archive
copy(&mut input, &mut writer).unwrap();
Expand Down Expand Up @@ -696,7 +745,7 @@ mod tests {
.gid(1000)
.mode(0o100644);
// and get a writer for that input file
let mut writer = b.write(output, length1);
let mut writer = b.write_cpio(output, length1);

// Copy the input file into our CPIO archive
copy(&mut input1, &mut writer).unwrap();
Expand All @@ -709,7 +758,7 @@ mod tests {
.gid(1000)
.mode(0o100644);
// and get a writer for that input file
let mut writer = b.write(output, length2);
let mut writer = b.write_cpio(output, length2);

// Copy the second input file into our CPIO archive
copy(&mut input2, &mut writer).unwrap();
Expand Down Expand Up @@ -763,7 +812,7 @@ mod tests {
.gid(1000)
.mode(0o100644);
// and get a writer for that input file
let mut writer = b.write(output, length1);
let mut writer = b.write_cpio(output, length1);

// Copy the input file into our CPIO archive
copy(&mut input1, &mut writer).unwrap();
Expand All @@ -776,7 +825,7 @@ mod tests {
.gid(1000)
.mode(0o100644);
// and get a writer for that input file
let mut writer = b.write(output, length2);
let mut writer = b.write_cpio(output, length2);

// Copy the second input file into our CPIO archive
copy(&mut input2, &mut writer).unwrap();
Expand Down

0 comments on commit eb4d3a4

Please sign in to comment.