From 28eeeb2ffe57c770f0565236f83eca9a543e2d8e Mon Sep 17 00:00:00 2001 From: Daniel Alley Date: Thu, 11 Jan 2024 17:24:33 -0500 Subject: [PATCH 1/2] Large file packages need to declare rpmlib dependency --- CHANGELOG.md | 4 ++++ src/rpm/builder.rs | 37 ++++++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ba8202..b273614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `FileOptions::verify()` - Added `Evr` and `Nevra` structs and `rpm_evr_compare` function for comparing RPM versions. +### Fixed + +- RPM packages that use large files (>4gb) now correctly declare rpmlib() dependency + ### Changed - As RHEL 7 (thus, CentOS 7 and other derivatives) goes out-of-support on June 30, 2024, support for legacy diff --git a/src/rpm/builder.rs b/src/rpm/builder.rs index 915a50c..210be02 100644 --- a/src/rpm/builder.rs +++ b/src/rpm/builder.rs @@ -738,6 +738,8 @@ impl PackageBuilder { } cpio::newc::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( @@ -763,6 +765,12 @@ impl PackageBuilder { self.requires .push(Dependency::rpmlib("FileCaps", "4.6.1-1".to_owned())); } + + if uses_large_files { + self.requires + .push(Dependency::rpmlib("LargeFiles", "4.12.0-1".to_owned())); + } + // TODO: as per https://rpm-software-management.github.io/rpm/manual/users_and_groups.html, // at some point in the future this might make sense as hard requirements, but since it's a new feature, // they have to be weak requirements to avoid breaking things. @@ -855,7 +863,6 @@ impl PackageBuilder { } let offset = 0; - let small_package = combined_file_sizes <= u32::MAX.into(); let mut actual_records = vec![ // Existence of this tag is how rpm decides whether or not a package is a source rpm or binary rpm @@ -906,7 +913,13 @@ impl PackageBuilder { offset, IndexData::I18NString(vec![self.summary]), ), - if small_package { + if uses_large_files { + IndexEntry::new( + IndexTag::RPMTAG_LONGSIZE, + offset, + IndexData::Int64(vec![combined_file_sizes]), + ) + } else { let combined_file_sizes = combined_file_sizes .try_into() .expect("combined_file_sizes should be smaller than 4 GiB"); @@ -915,12 +928,6 @@ impl PackageBuilder { offset, IndexData::Int32(vec![combined_file_sizes]), ) - } else { - IndexEntry::new( - IndexTag::RPMTAG_LONGSIZE, - offset, - IndexData::Int64(vec![combined_file_sizes]), - ) }, IndexEntry::new( IndexTag::RPMTAG_LICENSE, @@ -977,7 +984,13 @@ impl PackageBuilder { // if we have an empty RPM, we have to leave out all file related index entries. if !self.files.is_empty() { - let size_entry = if small_package { + let size_entry = if uses_large_files { + IndexEntry::new( + IndexTag::RPMTAG_LONGFILESIZES, + offset, + IndexData::Int64(file_sizes), + ) + } else { let file_sizes = file_sizes .into_iter() .map(u32::try_from) @@ -991,12 +1004,6 @@ impl PackageBuilder { offset, IndexData::Int32(file_sizes), ) - } else { - IndexEntry::new( - IndexTag::RPMTAG_LONGFILESIZES, - offset, - IndexData::Int64(file_sizes), - ) }; actual_records.extend([ size_entry, From e73395719060a1322daa23db326bbcd6eaabf535 Mon Sep 17 00:00:00 2001 From: Daniel Alley Date: Wed, 22 May 2024 09:11:03 -0400 Subject: [PATCH 2/2] Users should be able to read files from the RPM payload closes #222 --- CHANGELOG.md | 1 + Cargo.toml | 2 +- src/rpm/compressor.rs | 36 +++++++++++++++++++++-- src/rpm/filecaps.rs | 8 +++++ src/rpm/package.rs | 68 ++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 111 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b273614..39b8bd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added support for ecdsa signatures +- Added `Package::files()` for iterating over the files of an RPM package (metadata & contents). ## 0.16.0 diff --git a/Cargo.toml b/Cargo.toml index e81a559..535758a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ itertools = "0.13" hex = { version = "0.4", features = ["std"] } zstd = { version = "0.13", optional = true } xz2 = { version = "0.1", optional = true } -bzip2 = { version = "0.4.4", optional = true } +bzip2 = { version = "0.5.0", optional = true } [dev-dependencies] env_logger = "0.11" diff --git a/src/rpm/compressor.rs b/src/rpm/compressor.rs index 5d8e1fc..befebbe 100644 --- a/src/rpm/compressor.rs +++ b/src/rpm/compressor.rs @@ -1,4 +1,4 @@ -use std::io::Write; +use std::io; use crate::errors::*; @@ -13,6 +13,18 @@ pub enum CompressionType { Bzip2, } +impl std::fmt::Display for CompressionType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::None => write!(f, "none"), + Self::Gzip => write!(f, "gzip"), + Self::Zstd => write!(f, "zstd"), + Self::Xz => write!(f, "xz"), + Self::Bzip2 => write!(f, "bzip2"), + } + } +} + impl std::str::FromStr for CompressionType { type Err = Error; fn from_str(raw: &str) -> Result { @@ -80,7 +92,7 @@ impl TryFrom for Compressor { } } -impl Write for Compressor { +impl io::Write for Compressor { fn write(&mut self, content: &[u8]) -> Result { match self { Compressor::None(data) => data.write(content), @@ -192,3 +204,23 @@ impl std::fmt::Display for CompressionWithLevel { } } } + +pub(crate) fn decompress_stream( + value: CompressionType, + reader: impl io::BufRead + 'static, +) -> Result, Error> { + match value { + CompressionType::None => Ok(Box::new(reader)), + #[cfg(feature = "gzip-compression")] + CompressionType::Gzip => Ok(Box::new(flate2::bufread::GzDecoder::new(reader))), + #[cfg(feature = "zstd-compression")] + CompressionType::Zstd => Ok(Box::new(zstd::stream::Decoder::new(reader)?)), + #[cfg(feature = "xz-compression")] + CompressionType::Xz => Ok(Box::new(xz2::bufread::XzDecoder::new(reader))), + #[cfg(feature = "bzip2-compression")] + CompressionType::Bzip2 => Ok(Box::new(bzip2::read::BzipDecoder::new(reader))), + // This is an issue when building with all compression types enabled + #[allow(unreachable_patterns)] + _ => Err(Error::UnsupportedCompressorType(value.to_string())), + } +} diff --git a/src/rpm/filecaps.rs b/src/rpm/filecaps.rs index cb493bd..c65d81c 100644 --- a/src/rpm/filecaps.rs +++ b/src/rpm/filecaps.rs @@ -50,6 +50,14 @@ const CAPS: &[&str; 41] = &[ #[derive(Debug, Clone)] pub struct FileCaps(String); +impl FileCaps { + pub fn new(input: String) -> Result { + validate_caps_text(&input)?; + + Ok(Self(input)) + } +} + impl Display for FileCaps { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) diff --git a/src/rpm/package.rs b/src/rpm/package.rs index 78424e7..449652d 100644 --- a/src/rpm/package.rs +++ b/src/rpm/package.rs @@ -1,13 +1,15 @@ use std::{ fs, io, + io::Read, path::{Path, PathBuf}, str::FromStr, }; +use cpio; use digest::Digest; use num_traits::FromPrimitive; -use crate::{constants::*, errors::*, CompressionType}; +use crate::{constants::*, decompress_stream, errors::*, CompressionType}; #[cfg(feature = "signature-pgp")] use crate::signature::pgp::Verifier; @@ -61,6 +63,21 @@ impl Package { self.write(&mut io::BufWriter::new(fs::File::create(path)?)) } + /// Iterate over the file contents of the package payload + pub fn files(&self) -> Result { + let file_entries = self.metadata.get_file_entries()?; + let archive = decompress_stream( + self.metadata.get_payload_compressor()?, + io::Cursor::new(self.content.clone()), + )?; + + Ok(FileIterator { + file_entries, + archive, + count: 0, + }) + } + /// Create package signatures using an external key and add them to the signature header #[cfg(feature = "signature-meta")] pub fn sign(&mut self, signer: S) -> Result<(), Error> @@ -1019,3 +1036,52 @@ impl PackageMetadata { } } } + +pub struct FileIterator<'a> { + file_entries: Vec, + archive: Box, + count: usize, +} + +#[derive(Debug)] +pub struct RpmFile { + pub file_entry: FileEntry, + pub content: Vec, +} + +impl<'a> Iterator for FileIterator<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + if self.count >= self.file_entries.len() { + return None; + } + + let file_entry = self.file_entries[self.count].clone(); + self.count += 1; + + let reader = cpio::NewcReader::new(&mut self.archive); + + if let Ok(mut entry_reader) = reader { + if entry_reader.entry().is_trailer() { + return None; + } + + let mut content = Vec::new(); + + if let Err(e) = entry_reader.read_to_end(&mut content) { + return Some(Err(Error::Io(e))); + } + if let Err(e) = entry_reader.finish() { + return Some(Err(Error::Io(e))); + } + + return Some(Ok(RpmFile { + file_entry, + content, + })); + } + + Some(Err(reader.map_err(|e| Error::Io(e)).err()?)) + } +}