Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop cpio-rs, replace with vendored code #109

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -47,6 +48,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
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
37 changes: 22 additions & 15 deletions src/rpm/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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");
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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,
Expand Down
36 changes: 34 additions & 2 deletions src/rpm/compressor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::io::Write;
use std::io;

use crate::errors::*;

Expand All @@ -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<Self, Self::Err> {
Expand Down Expand Up @@ -80,7 +92,7 @@ impl TryFrom<CompressionWithLevel> for Compressor {
}
}

impl Write for Compressor {
impl io::Write for Compressor {
fn write(&mut self, content: &[u8]) -> Result<usize, std::io::Error> {
match self {
Compressor::None(data) => data.write(content),
Expand Down Expand Up @@ -192,3 +204,23 @@ impl std::fmt::Display for CompressionWithLevel {
}
}
}

pub(crate) fn decompress_stream(
value: CompressionType,
reader: impl io::BufRead + 'static,
) -> Result<Box<dyn io::Read>, 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())),
}
}
8 changes: 8 additions & 0 deletions src/rpm/filecaps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ const CAPS: &[&str; 41] = &[
#[derive(Debug, Clone)]
pub struct FileCaps(String);

impl FileCaps {
pub fn new(input: String) -> Result<Self, Error> {
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)
Expand Down
68 changes: 67 additions & 1 deletion src/rpm/package.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<FileIterator, Error> {
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<S>(&mut self, signer: S) -> Result<(), Error>
Expand Down Expand Up @@ -1019,3 +1036,52 @@ impl PackageMetadata {
}
}
}

pub struct FileIterator<'a> {
file_entries: Vec<FileEntry>,
archive: Box<dyn io::Read + 'a>,
count: usize,
}

#[derive(Debug)]
pub struct RpmFile {
pub file_entry: FileEntry,
pub content: Vec<u8>,
}

impl<'a> Iterator for FileIterator<'a> {
type Item = Result<RpmFile, Error>;

fn next(&mut self) -> Option<Self::Item> {
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()?))
}
}
Loading