Skip to content

Commit

Permalink
Add support for rpm 4.19 auto-creation of users and groups
Browse files Browse the repository at this point in the history
closes #206
  • Loading branch information
dralley committed Dec 30, 2023
1 parent 80d1fbc commit 4f95f1c
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 8 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 29 additions & 1 deletion src/rpm/builder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::{BTreeMap, BTreeSet};
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::convert::TryInto;

use std::fs;
Expand Down Expand Up @@ -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;
Expand All @@ -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());
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand Down
109 changes: 102 additions & 7 deletions tests/building.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?;
Expand Down Expand Up @@ -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<dyn std::error::Error>> {
let mut buff = std::io::Cursor::new(Vec::<u8>::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<dyn std::error::Error>> {
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(())
}

0 comments on commit 4f95f1c

Please sign in to comment.