From 4f95f1cf049bb72b595424873e6d2b6b780b05a3 Mon Sep 17 00:00:00 2001 From: Daniel Alley Date: Fri, 22 Dec 2023 11:03:13 -0500 Subject: [PATCH] Add support for rpm 4.19 auto-creation of users and groups closes #206 --- CHANGELOG.md | 5 +++ src/rpm/builder.rs | 30 ++++++++++++- tests/building.rs | 109 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 136 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b91b8a98..4411c925 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- `Dependency::script_pre()`, `Dependency::script_post()`, `Dependency::script_preun()`, `Dependency::script_postun()` +- Added support for the automatic user/group creation feature in rpm 4.19 + ## 0.13.1 ### Added diff --git a/src/rpm/builder.rs b/src/rpm/builder.rs index 9a49a17f..4a64467b 100644 --- a/src/rpm/builder.rs +++ b/src/rpm/builder.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, BTreeSet, HashSet}; use std::convert::TryInto; use std::fs; @@ -663,6 +663,8 @@ impl PackageBuilder { let mut file_verify_flags = Vec::with_capacity(files_len); let mut dir_indixes = Vec::with_capacity(files_len); let mut base_names = Vec::with_capacity(files_len); + let mut users_to_create = HashSet::new(); + let mut groups_to_create = HashSet::new(); let mut combined_file_sizes: u64 = 0; let mut uses_file_capabilities = false; @@ -672,6 +674,12 @@ impl PackageBuilder { if entry.caps.is_some() { uses_file_capabilities = true; } + if &entry.user != "root" { + users_to_create.insert(entry.user.clone()); + } + if &entry.group != "root" { + groups_to_create.insert(entry.group.clone()); + } file_sizes.push(entry.size); file_modes.push(entry.mode.into()); file_caps.push(entry.caps.to_owned()); @@ -752,6 +760,24 @@ impl PackageBuilder { "4.6.1-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. + for user in &users_to_create { + self.recommends.push(Dependency::new( + format!("user({})", user), + DependencyFlags::SCRIPT_PRE | DependencyFlags::SCRIPT_POSTUN, + "".to_owned(), + )) + } + + for group in &groups_to_create { + self.recommends.push(Dependency::new( + format!("group({})", group), + DependencyFlags::SCRIPT_PRE | DependencyFlags::SCRIPT_POSTUN, + "".to_owned(), + )) + } let mut provide_names = Vec::new(); let mut provide_flags = Vec::new(); @@ -837,6 +863,8 @@ impl PackageBuilder { 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 + // If the SOURCERPM tag is set, then the package is seen as a binary rpm. IndexEntry::new( IndexTag::RPMTAG_SOURCERPM, offset, diff --git a/tests/building.rs b/tests/building.rs index 341b928e..a2e62800 100644 --- a/tests/building.rs +++ b/tests/building.rs @@ -103,8 +103,7 @@ However, it does nothing.", pkg.write(&mut buff)?; - // check that generated packages has source rpm tag - // to be more compatibly recognized as RPM binary packages + // check that generated packages has source rpm tag to be more compatibly recognized as RPM binary packages pkg.metadata.get_source_rpm()?; pkg.verify_digests()?; @@ -164,14 +163,110 @@ However, it does nothing.", Ok(()) } +#[test] +#[ignore = "missing config() dependencies and some other stuff, need to flesh out files"] +fn test_rpm_file_attrs_equivalent() -> Result<(), Box> { + let mut buff = std::io::Cursor::new(Vec::::new()); + + let pkg = PackageBuilder::new( + "rpm-file-attrs", + "1.0", + "MIT", + "noarch", + "Test RPM file attributes", + ) + .release("1") + .description("Test RPM file attributes") + .compression(rpm::CompressionType::None) + .build_host("localhost") + .with_file( + "./tests/assets/RPMS/noarch/rpm-file-attrs-1.0-1.noarch.rpm", + FileOptions::new("/bin/test").caps("cap_sys_admin,cap_sys_ptrace=pe")?, + )? + // TODO files + .build()?; + + let metadata = &pkg.metadata; + + assert_eq!( + metadata.get_provides().unwrap(), + vec![ + // TODO: understand what this means and why it is both provided and required + Dependency::config("config(rpm-file-attrs)".to_owned(), "1.0-1".to_owned()), + Dependency::eq("rpm-file-attrs".to_owned(), "1.0-1".to_owned()), + ] + ); + assert_eq!( + metadata.get_requires().unwrap(), + vec![ + Dependency::config("config(rpm-file-attrs)".to_owned(), "1.0-1".to_owned()), + Dependency::rpmlib( + "rpmlib(CompressedFileNames)".to_owned(), + "3.0.4-1".to_owned() + ), + Dependency::rpmlib("rpmlib(FileCaps)".to_owned(), "4.6.1-1".to_owned(),), + Dependency::rpmlib("rpmlib(FileDigests)".to_owned(), "4.6.0-1".to_owned()), + Dependency::rpmlib( + "rpmlib(PayloadFilesHavePrefix)".to_owned(), + "4.0-1".to_owned() + ), + ] + ); + assert_eq!(metadata.get_conflicts().unwrap(), vec![]); + assert_eq!(metadata.get_obsoletes().unwrap(), vec![]); + assert_eq!(metadata.get_supplements().unwrap(), vec![]); + assert_eq!(metadata.get_suggests().unwrap(), vec![]); + assert_eq!(metadata.get_enhances().unwrap(), vec![]); + // These are soft requirements because of the build-time policy when these packages were created on Fedora 39 + // Probably if built on some later version they would be hard requirements? + // https://github.com/rpm-software-management/rpm/blob/bb4aaaa2e8e4bdfc02f9d98ab2982074051c4eb2/docs/manual/users_and_groups.md?plain=1#L36C11-L36C11 + assert_eq!( + metadata.get_recommends().unwrap(), + vec![ + Dependency::new( + "group(jane)".to_string(), + DependencyFlags::SCRIPT_PRE | DependencyFlags::SCRIPT_POSTUN, + "".to_owned() + ), + Dependency::new( + "user(jane)".to_string(), + DependencyFlags::SCRIPT_PRE | DependencyFlags::SCRIPT_POSTUN, + "".to_owned() + ), + ] + ); + + pkg.write(&mut buff)?; + + // check that generated packages has source rpm tag to be more compatibly recognized as RPM binary packages + pkg.metadata.get_source_rpm()?; + + pkg.verify_digests()?; + + Ok(()) +} + /// Read a package, write it, and read it back - check for equivalence. #[test] fn test_rpm_roundtrip() -> Result<(), Box> { - let mut buf = Vec::new(); - let rpm = Package::open(common::rpm_basic_pkg_path())?; - rpm.write(&mut buf)?; - let roundtripped_rpm = Package::parse(&mut Cursor::new(buf))?; + let paths = vec![ + common::rpm_basic_pkg_path(), + common::rpm_basic_pkg_path_eddsa_signed(), + common::rpm_basic_pkg_path_rsa_signed(), + common::rpm_basic_source_path(), + common::rpm_empty_path(), + common::rpm_empty_source_path(), + common::rpm_file_attrs_path(), + common::rpm_with_patch_path(), + ]; + + for path in paths { + let mut buf = Vec::new(); + let rpm = Package::open(path)?; + rpm.write(&mut buf)?; + let roundtripped_rpm = Package::parse(&mut Cursor::new(buf))?; - assert_eq!(rpm, roundtripped_rpm); + assert_eq!(rpm, roundtripped_rpm); + } Ok(()) }