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

Clean up and expand tests #115

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
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
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
out
**/*.rpm
Cargo.lock
!test_assets/*
!tests/assets/**
dnf-cache
.idea
.idea
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ 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).
- Added `Package::extract()` for extracting the archive contents of an RPM package to a directory on disk

## 0.16.0

Expand Down Expand Up @@ -77,6 +79,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Dependency::config()`, `Dependency::user()`, `Dependency::group()`
- `PackageBuilder::verify_script()`
- `PackageBuilder::group()` and `PackageBuilder::packager()`
- `PackageBuilder::with_file_contents()`
- Added support for the automatic user/group creation feature in rpm 4.19

### Changed
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,15 @@ 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"
serial_test = "3.0"
pretty_assertions = "1.3"
gethostname = "0.5"
hex-literal = "0.4"
bstr = "1.4.0"

[features]
default = [
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,24 @@ This library does not build software like rpmbuild. It is meant for finished art
```rust
use rpm::signature::pgp::{Signer, Verifier};

let raw_secret_key = std::fs::read("./test_assets/secret_key.asc")?;
let raw_secret_key = std::fs::read("./tests/assets/signing_keys/secret_ed25519.asc")?;
// It's recommended to use timestamp of last commit in your VCS
let source_date = 1_600_000_000;
let pkg = rpm::PackageBuilder::new("test", "1.0.0", "MIT", "x86_64", "some awesome package")
.compression(rpm::CompressionType::Gzip)
.with_file(
"./test_assets/awesome.toml",
"./tests/assets/SOURCES/example_config.toml",
rpm::FileOptions::new("/etc/awesome/config.toml")
.is_config()
.is_no_replace(),
)?
// file mode is inherited from source file
.with_file(
"./test_assets/awesome.py",
"./tests/assets/SOURCES/multiplication_tables.py",
rpm::FileOptions::new("/usr/bin/awesome"),
)?
.with_file(
"./test_assets/awesome.toml",
"./tests/assets/SOURCES/example_config.toml",
// you can set a custom mode and custom user too
rpm::FileOptions::new("/etc/awesome/second.toml")
.mode(rpm::FileMode::regular(0o644))
Expand Down Expand Up @@ -88,7 +88,7 @@ pkg.write_file("./awesome.rpm")?;

// reading
let raw_pub_key = std::fs::read("/path/to/gpg.key.pub")?;
let pkg = rpm::Package::open("test_assets/389-ds-base-devel-1.3.8.4-15.el7.x86_64.rpm")?;
let pkg = rpm::Package::open("tests/assets/RPMS/signed/noarch/rpm-basic-with-ed25519-2.3.4-5.el9.noarch.rpm")?;

let name = pkg.metadata.get_name()?;
let version = pkg.metadata.get_version()?;
Expand Down
2 changes: 1 addition & 1 deletion src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ bitflags! {
const DONOTUSE = 1 << 2; // %%donotuse
const MISSINGOK = 1 << 3; // %%config(missingok)
const NOREPLACE = 1 << 4; // %%config(noreplace)
// const SPECFILE = 1 << 5; // first file in SRPM?
const SPECFILE = 1 << 5; // specfile, which is the first file in a source RPM
const GHOST = 1 << 6; // %%ghost
const LICENSE = 1 << 7; // %%license
const README = 1 << 8; // %%readme
Expand Down
13 changes: 5 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,22 @@
//! # let _ = env_logger::Builder::new().filter_level(log::LevelFilter::Trace).is_test(true).try_init();
//! # #[cfg(feature = "signature-pgp")]
//! # {
//! let raw_secret_key = std::fs::read("./test_assets/secret_key.asc")?;
//! let raw_secret_key = std::fs::read("./tests/assets/signing_keys/secret_rsa4096.asc")?;
//! // It's recommended to use timestamp of last commit in your VCS
//! let source_date = 1_600_000_000;
//! let pkg = rpm::PackageBuilder::new("test", "1.0.0", "MIT", "x86_64", "some awesome package")
//! .compression(rpm::CompressionType::Gzip)
//! .with_file(
//! "./test_assets/awesome.toml",
//! "./tests/assets/SOURCES/example_config.toml",
//! rpm::FileOptions::new("/etc/awesome/config.toml").is_config(),
//! )?
//! // file mode is inherited from source file
//! .with_file(
//! "./test_assets/awesome.py",
//! "./tests/assets/SOURCES/multiplication_tables.py",
//! rpm::FileOptions::new("/usr/bin/awesome"),
//! )?
//! .with_file(
//! "./test_assets/awesome.toml",
//! "./tests/assets/SOURCES/example_config.toml",
//! // you can set a custom mode and custom user too
//! rpm::FileOptions::new("/etc/awesome/second.toml")
//! .mode(rpm::FileMode::regular(0o644))
Expand Down Expand Up @@ -69,7 +69,7 @@
//! pkg.write(&mut f)?;
//!
//! // reading
//! let raw_pub_key = std::fs::read("test_assets/public_key.asc")?;
//! let raw_pub_key = std::fs::read("tests/assets/signing_keys/public_rsa4096.asc")?;
//! let pkg = rpm::Package::open("/tmp/awesome.rpm")?;
//! // verifying
//! pkg.verify_signature(Verifier::load_from_asc_bytes(&raw_pub_key)?)?;
Expand All @@ -91,6 +91,3 @@ pub use crate::version::*;

mod rpm;
pub use crate::rpm::*;

#[cfg(test)]
mod tests;
35 changes: 34 additions & 1 deletion src/rpm/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,6 @@ impl PackageBuilder {
///
/// The a changelog entry consists of an entry name (which includes author, email followed by
/// a dash followed by a version number), description, and the date and time of the change.

/// ```
/// # #[cfg(feature = "chrono")]
/// # || -> Result<(), Box<dyn std::error::Error>> {
Expand Down Expand Up @@ -362,6 +361,40 @@ impl PackageBuilder {
Ok(self)
}

/// Add a file to the package without needing an existing file.
///
/// Helpful if files are being generated on-demand, and you don't want to write them to disk.
///
/// ```
/// # fn foo() -> Result<(), Box<dyn std::error::Error>> {
///
/// let pkg = rpm::PackageBuilder::new("foo", "1.0.0", "Apache-2.0", "x86_64", "some baz package")
/// .with_file_contents(
/// "
/// [check]
/// date = true
/// time = true
/// ",
/// rpm::FileOptions::new("/etc/awesome/config.toml").is_config(),
/// )?
/// .with_file_contents(
/// "./awesome-config.toml",
/// // you can set a custom mode, capabilities and custom user too
/// rpm::FileOptions::new("/etc/awesome/second.toml").mode(0o100744).caps("cap_sys_admin=pe")?.user("hugo"),
/// )?
/// .build()?;
/// # Ok(())
/// # }
/// ```
pub fn with_file_contents(
mut self,
content: impl Into<Vec<u8>>,
options: impl Into<FileOptions>,
) -> Result<Self, Error> {
self.add_data(content.into(), Timestamp::now(), options.into())?;
Ok(self)
}

fn add_data(
&mut self,
content: Vec<u8>,
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::bufread::BzDecoder::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
12 changes: 6 additions & 6 deletions src/rpm/headers/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::{
use super::*;
use crate::{constants::*, errors::*, Timestamp};

#[derive(Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub struct Header<T: Tag> {
pub(crate) index_header: IndexHeader,
pub(crate) index_entries: Vec<IndexEntry<T>>,
Expand Down Expand Up @@ -395,8 +395,8 @@ pub struct FileOwnership {

#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct FileDigest {
digest: String,
algo: DigestAlgorithm,
pub digest: String,
pub algo: DigestAlgorithm,
Comment on lines +398 to +399
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide accessor methods and mark these as pub(crate)

}

impl FileDigest {
Expand Down Expand Up @@ -508,7 +508,7 @@ fn parse_binary_entry(
}

/// A header keeping track of all other header records.
#[derive(Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct IndexHeader {
/// rpm specific magic header
pub(crate) magic: [u8; 3],
Expand Down Expand Up @@ -575,7 +575,7 @@ impl IndexHeader {
}

/// A single entry within the [`IndexHeader`](self::IndexHeader)
#[derive(PartialEq)]
#[derive(Clone, PartialEq, Eq)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should add these unless necessary, particularly because the type is internal. For external types I think they're valid addenda

pub(crate) struct IndexEntry<T: num::FromPrimitive> {
pub(crate) tag: u32,
pub(crate) data: IndexData,
Expand Down Expand Up @@ -678,7 +678,7 @@ impl<T: Tag> fmt::Display for IndexEntry<T> {
}

/// Data as present in a [`IndexEntry`](self::IndexEntry) .
#[derive(Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum IndexData {
Null,
Char(Vec<u8>),
Expand Down
2 changes: 1 addition & 1 deletion src/rpm/headers/lead.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::errors::*;
/// Used to contain valid data, now only a very limited subset is used
/// and the remaining data is set to fixed values such that compatibility is kept.
/// Only the "magic number" is still relevant as it is used to detect rpm files.
#[derive(Eq)]
#[derive(Clone, Eq)]
pub struct Lead {
magic: [u8; 4],
major: u8,
Expand Down
46 changes: 44 additions & 2 deletions src/rpm/headers/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,50 @@ pub const REGULAR_FILE_TYPE: u16 = 0o100000; // bit representation = "100000000
pub const DIR_FILE_TYPE: u16 = 0o040000; // bit representation = "0100000000000000"
pub const SYMBOLIC_LINK_FILE_TYPE: u16 = 0o120000; // bit representation = "1010000000000000"

// @todo: <https://github.com/rpm-rs/rpm/issues/52>
use bitflags::bitflags;

// typedef enum rpmFileTypes_e {
// = 1, /*!< pipe/fifo */
// CDEV = 2, /*!< character device */
// XDIR = 4, /*!< directory */
// BDEV = 6, /*!< block device */
// REG = 8, /*!< regular file */
// LINK = 10, /*!< hard link */
// SOCK = 12 /*!< socket */
// } rpmFileTypes;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we represent this as another bitfield?


bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct FileModeFlags: u16 {
const PERMISSIONS_BIT_MASK = 0b0000111111111111; // 0o007777

const SUID_BIT_MASK = 0b0000100000000000; // 0o007777
const SGID_BIT_MASK = 0b0000010000000000; // 0o007777
const STICKY_BIT_MASK = 0b0000001000000000; // 0o007777

const USER_PERM_BIT_MASK = 0b0000000111000000; // 0o007777
const GROUP_PERM_BIT_MASK = 0b0000000000111000; // 0o007777
const OTHER_PERM_BIT_MASK = 0b0000000000000111;

// The set-user-ID bit (setuid bit).

// On execution, set the process’s effective user ID to that of the file. For directories on a few systems, give files created in the directory the same owner as the directory, no matter who creates them, and set the set-user-ID bit of newly-created subdirectories.
// The set-group-ID bit (setgid bit).

// On execution, set the process’s effective group ID to that of the file. For directories on most systems, give files created in the directory the same group as the directory, no matter what group the user who creates them is in, and set the set-group-ID bit of newly-created subdirectories.
// The restricted deletion flag or sticky bit.

// Prevent unprivileged users from removing or renaming a file in a directory unless they own the file or the directory; this is commonly found on world-writable directories like /tmp. For regular files on some older systems, save the program’s text image on the swap device so it will load more quickly when run, so that the image is “sticky”.

const FILE_TYPE_BIT_MASK = 0b1111000000000000; // 0o170000
const REGULAR_FILE_TYPE = 0b1000000000000000; // 0o100000
const DIR_FILE_TYPE = 0b0100000000000000; // 0o040000
const SYMBOLIC_LINK_FILE_TYPE = 0b1010000000000000; // 0o120000
}
}



impl From<u16> for FileMode {
fn from(raw_mode: u16) -> Self {
// example
Expand Down Expand Up @@ -522,7 +565,6 @@ impl<W: std::io::Write> std::io::Write for Sha256Writer<W> {
}

/// Type-alias for a tuple containing index tags for a scriptlet type,
///
pub(crate) type ScriptletIndexTags = (IndexTag, IndexTag, IndexTag);

/// Description of a scriptlet as present in a RPM header record
Expand Down
Loading
Loading