From 20762ff3027cfd1d5c9c0b8e435410a6603b78c7 Mon Sep 17 00:00:00 2001 From: Tym Lipari <2223848+tymlipari@users.noreply.github.com> Date: Mon, 25 Mar 2024 22:43:09 -0700 Subject: [PATCH] container-encapsulate: build content mapping from rpm fileset --- rpmostree-cxxrs.cxx | 72 ++++++------- rpmostree-cxxrs.h | 4 +- rust/src/container.rs | 178 ++++++++++++++++++++------------ rust/src/fsutil.rs | 140 +++++++++++++++++++++++++ rust/src/lib.rs | 5 +- src/libpriv/rpmostree-refts.cxx | 129 +++++++++++++---------- src/libpriv/rpmostree-refts.h | 47 ++++----- 7 files changed, 389 insertions(+), 186 deletions(-) create mode 100644 rust/src/fsutil.rs diff --git a/rpmostree-cxxrs.cxx b/rpmostree-cxxrs.cxx index f237f326d3..9c973e5fdd 100644 --- a/rpmostree-cxxrs.cxx +++ b/rpmostree-cxxrs.cxx @@ -1216,6 +1216,11 @@ template <> class impl final str.repr = repr; return str; } + static repr::Fat + repr (Str str) noexcept + { + return str.repr; + } }; template class impl > final @@ -2811,6 +2816,9 @@ extern "C" ::rust::repr::PtrLen rpmostreecxx$cxxbridge1$complete_rpm_layering (::std::int32_t rootfs) noexcept; + ::rust::repr::PtrLen + rpmostreecxx$cxxbridge1$deduplicate_tmpfiles_entries (::std::int32_t rootfs) noexcept; + ::rust::repr::PtrLen rpmostreecxx$cxxbridge1$passwd_cleanup (::std::int32_t rootfs) noexcept; ::rust::repr::PtrLen rpmostreecxx$cxxbridge1$migrate_group_except_root ( @@ -2940,9 +2948,6 @@ extern "C" void rpmostreecxx$cxxbridge1$cache_branch_to_nevra (::rust::Str nevra, ::rust::String *return$) noexcept; - ::rust::repr::PtrLen - rpmostreecxx$cxxbridge1$deduplicate_tmpfiles_entries (::std::int32_t rootfs) noexcept; - ::std::uint32_t rpmostreecxx$cxxbridge1$CxxGObjectArray$length (::rpmostreecxx::CxxGObjectArray &self) noexcept { @@ -3237,24 +3242,6 @@ extern "C" return throw$; } - ::rust::repr::PtrLen - rpmostreecxx$cxxbridge1$RpmTs$packages_providing_file ( - ::rpmostreecxx::RpmTs const &self, ::rust::Str path, - ::rust::Vec< ::rust::String> *return$) noexcept - { - ::rust::Vec< ::rust::String> (::rpmostreecxx::RpmTs::*packages_providing_file$) (::rust::Str) - const - = &::rpmostreecxx::RpmTs::packages_providing_file; - ::rust::repr::PtrLen throw$; - ::rust::behavior::trycatch ( - [&] { - new (return$)::rust::Vec< ::rust::String> ((self.*packages_providing_file$) (path)); - throw$.ptr = nullptr; - }, - ::rust::detail::Fail (throw$)); - return throw$; - } - ::rust::repr::PtrLen rpmostreecxx$cxxbridge1$RpmTs$package_meta (::rpmostreecxx::RpmTs const &self, ::rust::Str name, ::rpmostreecxx::PackageMeta **return$) noexcept @@ -3297,12 +3284,28 @@ extern "C" new (return$)::rust::Vec< ::std::uint64_t> ((self.*changelogs$) ()); } - ::std::string const * + ::rust::repr::Fat rpmostreecxx$cxxbridge1$PackageMeta$src_pkg (::rpmostreecxx::PackageMeta const &self) noexcept { - ::std::string const &(::rpmostreecxx::PackageMeta::*src_pkg$) () const + ::rust::Str (::rpmostreecxx::PackageMeta::*src_pkg$) () const = &::rpmostreecxx::PackageMeta::src_pkg; - return &(self.*src_pkg$) (); + return ::rust::impl< ::rust::Str>::repr ((self.*src_pkg$) ()); + } + + ::rust::repr::PtrLen + rpmostreecxx$cxxbridge1$PackageMeta$provided_paths ( + ::rpmostreecxx::PackageMeta const &self, ::rust::Vec< ::rust::String> *return$) noexcept + { + ::rust::Vec< ::rust::String> (::rpmostreecxx::PackageMeta::*provided_paths$) () const + = &::rpmostreecxx::PackageMeta::provided_paths; + ::rust::repr::PtrLen throw$; + ::rust::behavior::trycatch ( + [&] { + new (return$)::rust::Vec< ::rust::String> ((self.*provided_paths$) ()); + throw$.ptr = nullptr; + }, + ::rust::detail::Fail (throw$)); + return throw$; } ::rust::repr::PtrLen @@ -5688,6 +5691,16 @@ complete_rpm_layering (::std::int32_t rootfs) } } +void +deduplicate_tmpfiles_entries (::std::int32_t rootfs) +{ + ::rust::repr::PtrLen error$ = rpmostreecxx$cxxbridge1$deduplicate_tmpfiles_entries (rootfs); + if (error$.ptr) + { + throw ::rust::impl< ::rust::Error>::error (error$); + } +} + void passwd_cleanup (::std::int32_t rootfs) { @@ -6094,16 +6107,6 @@ cache_branch_to_nevra (::rust::Str nevra) noexcept rpmostreecxx$cxxbridge1$cache_branch_to_nevra (nevra, &return$.value); return ::std::move (return$.value); } - -void -deduplicate_tmpfiles_entries (::std::int32_t rootfs) -{ - ::rust::repr::PtrLen error$ = rpmostreecxx$cxxbridge1$deduplicate_tmpfiles_entries (rootfs); - if (error$.ptr) - { - throw ::rust::impl< ::rust::Error>::error (error$); - } -} } // namespace rpmostreecxx extern "C" @@ -6818,6 +6821,5 @@ Vec< ::rpmostreecxx::LockedPackage>::truncate (::std::size_t len) { return cxxbridge1$rust_vec$rpmostreecxx$LockedPackage$truncate (this, len); } - } // namespace cxxbridge1 } // namespace rust diff --git a/rpmostree-cxxrs.h b/rpmostree-cxxrs.h index 5ec26d72d0..b484653c37 100644 --- a/rpmostree-cxxrs.h +++ b/rpmostree-cxxrs.h @@ -2015,6 +2015,8 @@ bool prepare_rpm_layering (::std::int32_t rootfs, ::rust::Str merge_passwd_dir); void complete_rpm_layering (::std::int32_t rootfs); +void deduplicate_tmpfiles_entries (::std::int32_t rootfs); + void passwd_cleanup (::std::int32_t rootfs); void migrate_group_except_root (::std::int32_t rootfs, @@ -2057,6 +2059,4 @@ ::rpmostreecxx::GKeyFile *treefile_to_origin (::rpmostreecxx::Treefile const &tf void origin_validate_roundtrip (::rpmostreecxx::GKeyFile const &kf) noexcept; ::rust::String cache_branch_to_nevra (::rust::Str nevra) noexcept; - -void deduplicate_tmpfiles_entries (::std::int32_t rootfs); } // namespace rpmostreecxx diff --git a/rust/src/container.rs b/rust/src/container.rs index 958dd57136..a681b93830 100644 --- a/rust/src/container.rs +++ b/rust/src/container.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::fmt::Debug; use std::fs::File; use std::io::BufReader; use std::num::NonZeroU32; @@ -27,6 +28,7 @@ use ostree_ext::prelude::*; use ostree_ext::{gio, oci_spec, ostree}; use crate::cxxrsutil::FFIGObjectReWrap; +use crate::fsutil::{self, FileHelpers, ResolvedOstreePaths}; use crate::progress::progress_task; use crate::CxxResult; @@ -90,13 +92,15 @@ struct ContainerEncapsulateOpts { struct MappingBuilder { /// Maps from package ID to metadata packagemeta: ObjectMetaSet, - /// Mapping from content object sha256 to package numeric ID - content: ObjectMetaMap, - /// Mapping from content object sha256 to package numeric ID - duplicates: BTreeMap>, - multi_provider: Vec, - unpackaged_id: Rc, + /// Maps from object checksum to absolute filesystem path + checksum_paths: BTreeMap>, + + /// Maps from absolute filesystem path to the package IDs that + /// provide it + path_packages: HashMap>, + + unpackaged_id: ContentID, /// Files that were processed before the global tree walk skip: HashSet, @@ -110,35 +114,53 @@ impl MappingBuilder { /// In the future though if we support e.g. containers in /usr/share/containers or the /// like, this will need to change. const UNPACKAGED_ID: &'static str = "rpmostree-unpackaged-content"; + + fn duplicate_objects(&self) -> impl Iterator)> { + self.checksum_paths + .iter() + .filter(|(_, paths)| paths.len() > 1) + } + + fn multiple_owners(&self) -> impl Iterator)> { + self.path_packages.iter().filter(|(_, pkgs)| pkgs.len() > 1) + } } impl From for ObjectMeta { fn from(b: MappingBuilder) -> ObjectMeta { + let mut content = ObjectMetaMap::default(); + for (checksum, paths) in b.checksum_paths { + // Use the first package name found for one of the paths (if multiple). These + // are held in sorted data structures, so this should be deterministic. + // + // If not found, use the unpackaged name. + let pkg = paths + .iter() + .filter_map(|p| b.path_packages.get(p).map(|pkgs| pkgs.first().unwrap())) + .next() + .unwrap_or(&b.unpackaged_id); + + content.insert(checksum, pkg.clone()); + } + ObjectMeta { - map: b.content, + map: content, set: b.packagemeta, } } } /// Walk over the whole filesystem, and generate mappings from content object checksums -/// to the package that owns them. -/// -/// In the future, we could compute this much more efficiently by walking that -/// instead. But this design is currently oriented towards accepting a single ostree -/// commit as input. -fn build_mapping_recurse( +/// to the path that provides them. +fn build_fs_mapping_recurse( path: &mut Utf8PathBuf, dir: &gio::File, - ts: &crate::ffi::RpmTs, state: &mut MappingBuilder, ) -> Result<()> { - use std::collections::btree_map::Entry; - let cancellable = gio::Cancellable::NONE; let e = dir.enumerate_children( "standard::name,standard::type", gio::FileQueryInfoFlags::NOFOLLOW_SYMLINKS, - cancellable, + gio::Cancellable::NONE, )?; for child in e { let childi = child?; @@ -155,44 +177,19 @@ fn build_mapping_recurse( continue; } - let mut pkgs = ts.packages_providing_file(path.as_str())?; - // Let's be deterministic (but _unstable because we don't care about behavior of equal strings) - pkgs.sort_unstable(); - // For now, we pick the alphabetically first package providing a file - let mut pkgs = pkgs.into_iter(); - let pkgid = pkgs - .next() - .map(|v| -> Result<_> { - // Safety: we should have the package in metadata - let meta = state.packagemeta.get(v.as_str()).ok_or_else(|| { - anyhow::anyhow!("Internal error: missing pkgmeta for {}", &v) - })?; - Ok(Rc::clone(&meta.identifier)) - }) - .transpose()? - .unwrap_or_else(|| Rc::clone(&state.unpackaged_id)); - // Track cases of duplicate owners - match pkgs.len() { - 0 => {} - _ => { - state.multi_provider.push(path.clone()); - } - } - + // Ensure there's a checksum -> path entry. If it was previously + // accounted for by a package, this is essentially a no-op. If not, + // there'll be no corresponding path -> package entry, and the packaging + // operation will treat the file as being "unpackaged". let checksum = child.checksum().to_string(); - match state.content.entry(checksum) { - Entry::Vacant(v) => { - v.insert(pkgid); - } - Entry::Occupied(_) => { - let checksum = child.checksum().to_string(); - let v = state.duplicates.entry(checksum).or_default(); - v.push(pkgid); - } - } + state + .checksum_paths + .entry(checksum) + .or_default() + .insert(path.clone()); } gio::FileType::Directory => { - build_mapping_recurse(path, &child, ts, state)?; + build_fs_mapping_recurse(path, &child, state)?; } o => anyhow::bail!("Unhandled file type: {}", o), } @@ -253,9 +250,8 @@ pub fn container_encapsulate(args: Vec) -> CxxResult<()> { let mut state = MappingBuilder { unpackaged_id: Rc::from(MappingBuilder::UNPACKAGED_ID), packagemeta: Default::default(), - content: Default::default(), - duplicates: Default::default(), - multi_provider: Default::default(), + checksum_paths: Default::default(), + path_packages: Default::default(), skip: Default::default(), rpmsize: Default::default(), }; @@ -333,7 +329,7 @@ pub fn container_encapsulate(args: Vec) -> CxxResult<()> { state.packagemeta.insert(ObjectSourceMeta { identifier: Rc::clone(nevra), name: Rc::from(libdnf_sys::hy_split_nevra(&nevra)?.name), - srcid: Rc::from(pkgmeta.src_pkg().to_str().unwrap()), + srcid: Rc::from(pkgmeta.src_pkg()), change_time_offset, change_frequency: pruned_changelogs.len() as u32, }); @@ -358,9 +354,17 @@ pub fn container_encapsulate(args: Vec) -> CxxResult<()> { let name = format!("initramfs"); let identifier = format!("{} (kernel {})", name, kernel_ver).into_boxed_str(); let identifier = Rc::from(identifier); + state - .content - .insert(checksum.to_string(), Rc::clone(&identifier)); + .checksum_paths + .entry(checksum.to_string()) + .or_default() + .insert(path.clone()); + state + .path_packages + .entry(path.clone()) + .or_default() + .insert(Rc::clone(&identifier)); state.packagemeta.insert(ObjectSourceMeta { identifier: Rc::clone(&identifier), name: Rc::from(name), @@ -377,9 +381,42 @@ pub fn container_encapsulate(args: Vec) -> CxxResult<()> { // TODO add mapping for rpmdb } - // Walk the filesystem progress_task("Building package mapping", || { - build_mapping_recurse(&mut Utf8PathBuf::from("/"), &root, &q, &mut state) + // Walk each package, adding mappings for each of the files it provides + let mut dir_cache: HashMap = HashMap::new(); + for (nevra, pkgmeta) in package_meta.iter() { + for path in pkgmeta.provided_paths()? { + let path = Utf8PathBuf::from(path); + + // Resolve the path to its ostree file + if let Some(ostree_paths) = fsutil::resolve_ostree_paths( + &path, + root.downcast_ref::().unwrap(), + &mut dir_cache, + ) { + if ostree_paths.path.is_regular() || ostree_paths.path.is_symlink() { + let real_path = + Utf8PathBuf::from_path_buf(ostree_paths.path.peek_path().unwrap()) + .unwrap(); + let checksum = ostree_paths.path.checksum().to_string(); + + state + .checksum_paths + .entry(checksum) + .or_default() + .insert(real_path.clone()); + state + .path_packages + .entry(real_path) + .or_default() + .insert(Rc::clone(nevra)); + } + } + } + } + + // Then, walk the file system marking any remainders as unpackaged + build_fs_mapping_recurse(&mut Utf8PathBuf::from("/"), &root, &mut state) })?; let src_pkgs: HashSet<_> = state.packagemeta.iter().map(|p| &p.srcid).collect(); @@ -387,7 +424,7 @@ pub fn container_encapsulate(args: Vec) -> CxxResult<()> { // Print out information about what we found println!( "{} objects in {} packages ({} source)", - state.content.len(), + state.checksum_paths.len(), state.packagemeta.len(), src_pkgs.len(), ); @@ -398,17 +435,26 @@ pub fn container_encapsulate(args: Vec) -> CxxResult<()> { Utc.timestamp_opt(lowest_change_time.try_into().unwrap(), 0) .unwrap() ); - println!("{} duplicates", state.duplicates.len()); - if !state.multi_provider.is_empty() { + println!("{} duplicates", state.duplicate_objects().count()); + let multiple_owners = { + let mut mo: Vec<&Utf8Path> = state + .multiple_owners() + .map(|(path, _)| path.as_path()) + .collect(); + mo.sort_unstable(); + mo + }; + if !multiple_owners.is_empty() { println!("Multiple owners:"); - for v in state.multi_provider.iter() { - println!(" {}", v); + for path in multiple_owners { + println!(" {}", path); } } // Convert our build state into the state that ostree consumes, discarding // transient data such as the cases of files owned by multiple packages. let meta: ObjectMeta = state.into(); + // Now generate the sized version let meta = ObjectMetaSized::compute_sizes(repo, meta)?; if let Some(v) = opt.write_contentmeta_json { diff --git a/rust/src/fsutil.rs b/rust/src/fsutil.rs new file mode 100644 index 0000000000..2d98639448 --- /dev/null +++ b/rust/src/fsutil.rs @@ -0,0 +1,140 @@ +//! Helpers for working with an ostree filesystem + +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use camino::{Utf8Path, Utf8PathBuf}; +use ostree_ext::{gio, ostree, prelude::*}; +use std::{collections::HashMap, path::Component}; + +#[derive(Debug, Clone)] +pub struct ResolvedOstreePaths { + pub path: ostree::RepoFile, + pub symlink_target: Option, +} + +impl ResolvedOstreePaths { + /// If the resolved file is itself a symlink, returns the + /// file it points to. Otherwise just returns the resolved file. + pub fn real_file(&self) -> &ostree::RepoFile { + self.symlink_target.as_ref().unwrap_or(&self.path) + } +} + +/// Given a path and an ostree filesystem root, resolves +/// the path to a real file on the filesystem, including +/// resolving symlinks in the path's directory tree. +/// +/// Returns a pair of the resolved path and in the case where +/// the path points to a symlink, it also includes the resolved +/// symlink target. +pub fn resolve_ostree_paths<'a>( + path: &Utf8Path, + fsroot: &ostree::RepoFile, + cache: &'a mut HashMap, +) -> Option { + assert!(path.is_absolute()); + + // Recurse until root + if path.parent() == None { + return Some(ResolvedOstreePaths { + path: fsroot.clone(), + symlink_target: None, + }); + } + + // Check the cache for this path + if let Some(cache_hit) = cache.get(path) { + return Some(cache_hit.clone()); + } + + // Resolve our parent, then resolve ourselves as a direct child + if let Some(parent) = resolve_ostree_paths(path.parent().unwrap(), fsroot, cache) { + let child_file = parent + .real_file() + .child(path.file_name().unwrap()) + .downcast::() + .unwrap(); + + if !child_file.query_exists(gio::Cancellable::NONE) { + return None; + } + + let child_info = child_file + .query_info("*", gio::FileQueryInfoFlags::NONE, gio::Cancellable::NONE) + .expect("failed to get fs info"); + + // If this path is a symlink, figure out what it points to + let remapped_target = + if child_info.has_attribute("standard::is-symlink") && child_info.is_symlink() { + let link_target = child_info.symlink_target().unwrap(); + + // Due to a bug in OSTree's Gio.File implementation, we cannot + // just do `parent.resolve_relative_path` here as it doesn't correctly + // resolve to an absolute path. + // So instead we'll handle '.' and '..' chunks ourselves and normalize the + // resulting path. + if !link_target.is_absolute() { + let mut target_path = path.parent().unwrap().to_owned(); + + for item in link_target.components() { + match item { + Component::ParentDir => { + target_path.pop(); + } + Component::Normal(name) => { + target_path.push(Utf8Path::new(&name.to_string_lossy())); + } + Component::CurDir => {} + _ => panic!("unhandled component type"), + }; + } + + resolve_ostree_paths(&target_path, fsroot, cache) + } else { + resolve_ostree_paths(Utf8Path::from_path(&link_target).unwrap(), fsroot, cache) + } + } else { + None + }; + + let result = ResolvedOstreePaths { + path: child_file.clone(), + symlink_target: remapped_target.map(|f| f.real_file().clone()), + }; + + // If this path is or points to a directory, add it to the cache to speed up future lookups + if result.real_file().is_dir() { + cache.insert(path.to_owned(), result.clone()); + } + + return Some(result); + } + + None +} + +pub trait FileHelpers { + fn is_dir(&self) -> bool; + fn is_regular(&self) -> bool; + fn is_symlink(&self) -> bool; +} + +impl FileHelpers for T +where + T: IsA, +{ + fn is_dir(&self) -> bool { + self.query_file_type(gio::FileQueryInfoFlags::NONE, gio::Cancellable::NONE) + == gio::FileType::SymbolicLink + } + + fn is_regular(&self) -> bool { + self.query_file_type(gio::FileQueryInfoFlags::NONE, gio::Cancellable::NONE) + == gio::FileType::Regular + } + + fn is_symlink(&self) -> bool { + self.query_file_type(gio::FileQueryInfoFlags::NONE, gio::Cancellable::NONE) + == gio::FileType::SymbolicLink + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 6ec96d8ff3..d041532a3c 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -906,14 +906,14 @@ pub mod ffi { fn rpmdb_package_name_list(dfd: i32, path: String) -> Result>; // Methods on RpmTs - fn packages_providing_file(self: &RpmTs, path: &str) -> Result>; fn package_meta(self: &RpmTs, name: &str) -> Result>; // Methods on PackageMeta fn size(self: &PackageMeta) -> u64; fn buildtime(self: &PackageMeta) -> u64; fn changelogs(self: &PackageMeta) -> Vec; - fn src_pkg(self: &PackageMeta) -> &CxxString; + fn src_pkg(self: &PackageMeta) -> &str; + fn provided_paths(self: &PackageMeta) -> Result>; } // rpmostree-package-variants.h @@ -959,6 +959,7 @@ mod extensions; pub(crate) use extensions::*; #[cfg(feature = "fedora-integration")] mod fedora_integration; +mod fsutil; mod history; pub use self::history::*; mod importer; diff --git a/src/libpriv/rpmostree-refts.cxx b/src/libpriv/rpmostree-refts.cxx index 4a0a77b0f0..46f636212b 100644 --- a/src/libpriv/rpmostree-refts.cxx +++ b/src/libpriv/rpmostree-refts.cxx @@ -77,30 +77,78 @@ rpmostree_refts_unref (RpmOstreeRefTs *rts) namespace rpmostreecxx { -RpmTs::RpmTs (RpmOstreeRefTs *ts) { _ts = ts; } +PackageMeta::PackageMeta (::Header h) { _h = headerLink (h); } -RpmTs::~RpmTs () { rpmostree_refts_unref (_ts); } +PackageMeta::~PackageMeta () { headerFree (_h); } + +uint64_t +PackageMeta::size () const +{ + return headerGetNumber (_h, RPMTAG_LONGARCHIVESIZE); +} + +uint64_t +PackageMeta::buildtime () const +{ + return headerGetNumber (_h, RPMTAG_BUILDTIME); +} + +rust::Str +PackageMeta::src_pkg () const +{ + return headerGetString (_h, RPMTAG_SOURCERPM); +} + +rust::String +PackageMeta::nevra () const +{ + return header_get_nevra (_h); +} + +rust::Vec +PackageMeta::changelogs () const +{ + // Get the changelogs + struct rpmtd_s nchanges_date_s; + _cleanup_rpmtddata_ rpmtd nchanges_date = NULL; + nchanges_date = &nchanges_date_s; + headerGet (_h, RPMTAG_CHANGELOGTIME, nchanges_date, HEADERGET_MINMEM); + int ncnum = rpmtdCount (nchanges_date); + rust::Vec epochs; + for (int i = 0; i < ncnum; i++) + { + uint64_t nchange_date = 0; + rpmtdNext (nchanges_date); + nchange_date = rpmtdGetNumber (nchanges_date); + epochs.push_back (nchange_date); + } + return epochs; +} rust::Vec -RpmTs::packages_providing_file (const rust::Str path) const +PackageMeta::provided_paths () const { - auto path_c = std::string (path); - g_auto (rpmdbMatchIterator) mi - = rpmtsInitIterator (_ts->ts, RPMDBI_INSTFILENAMES, path_c.c_str (), 0); - if (mi == NULL) - mi = rpmtsInitIterator (_ts->ts, RPMDBI_PROVIDENAME, path_c.c_str (), 0); - rust::Vec ret; - if (mi != NULL) + g_auto (rpmfi) fi = rpmfiNew (NULL, _h, 0, 0); + if (fi == NULL) + throw std::runtime_error ("Failed to allocate file iterator"); + + rust::Vec paths; + + rpmfiInit (fi, 0); + while (rpmfiNext (fi) >= 0) { - Header h; - while ((h = rpmdbNextIterator (mi)) != NULL) - { - ret.push_back (rpmostreecxx::header_get_nevra (h)); - } + // Only include files that are marked as installed + if (RPMFILE_IS_INSTALLED (rpmfiFState (fi))) + paths.push_back (rust::String (rpmfiFN (fi))); } - return ret; + + return paths; } +RpmTs::RpmTs (RpmOstreeRefTs *ts) { _ts = ts; } + +RpmTs::~RpmTs () { rpmostree_refts_unref (_ts); } + std::unique_ptr RpmTs::package_meta (const rust::Str name) const { @@ -112,48 +160,23 @@ RpmTs::package_meta (const rust::Str name) const throw std::runtime_error (err); } Header h; - std::optional previous; - auto retval = std::make_unique (); + std::unique_ptr retval; while ((h = rpmdbNextIterator (mi)) != NULL) { - auto nevra = rpmostreecxx::header_get_nevra (h); - if (!previous.has_value ()) - { - previous = std::move (nevra); - retval->_size = headerGetNumber (h, RPMTAG_LONGARCHIVESIZE); - retval->_buildtime = headerGetNumber (h, RPMTAG_BUILDTIME); - retval->_src_pkg = headerGetString (h, RPMTAG_SOURCERPM); - - // Get the changelogs - struct rpmtd_s nchanges_date_s; - _cleanup_rpmtddata_ rpmtd nchanges_date = NULL; - nchanges_date = &nchanges_date_s; - headerGet (h, RPMTAG_CHANGELOGTIME, nchanges_date, HEADERGET_MINMEM); - int ncnum = rpmtdCount (nchanges_date); - rust::Vec epochs; - for (int i = 0; i < ncnum; i++) - { - uint64_t nchange_date = 0; - rpmtdNext (nchanges_date); - nchange_date = rpmtdGetNumber (nchanges_date); - epochs.push_back (nchange_date); - } - retval->_changelogs = std::move (epochs); - } - else + // TODO: Somehow we get two `libgcc-8.5.0-10.el8.x86_64` in current RHCOS, I don't + // understand that. + if (retval != nullptr) { - // TODO: Somehow we get two `libgcc-8.5.0-10.el8.x86_64` in current RHCOS, I don't - // understand that. - if (previous != nevra) - { - g_autofree char *buf - = g_strdup_printf ("Multiple installed '%s' (%s, %s)", name_c.c_str (), - previous.value ().c_str (), nevra.c_str ()); - throw std::runtime_error (buf); - } + auto nevra = header_get_nevra (h); + g_autofree char *buf + = g_strdup_printf ("Multiple installed '%s' (%s, %s)", name_c.c_str (), + retval->nevra ().c_str (), nevra.c_str ()); + throw std::runtime_error (buf); } + + retval = std::make_unique (h); } - if (!previous) + if (retval == nullptr) g_assert_not_reached (); return retval; } diff --git a/src/libpriv/rpmostree-refts.h b/src/libpriv/rpmostree-refts.h index ff018cd03f..a3e820871e 100644 --- a/src/libpriv/rpmostree-refts.h +++ b/src/libpriv/rpmostree-refts.h @@ -48,34 +48,26 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (RpmOstreeRefTs, rpmostree_refts_unref); namespace rpmostreecxx { -struct PackageMeta +class PackageMeta { - uint64_t _size; - uint64_t _buildtime; - rust::Vec _changelogs; - std::string _src_pkg; - - uint64_t - size () const - { - return _size; - }; - uint64_t - buildtime () const - { - return _buildtime; - }; - rust::Vec - changelogs () const - { - return _changelogs; - }; - - const std::string & - src_pkg () const - { - return _src_pkg; - }; +public: + PackageMeta (::Header h); + ~PackageMeta (); + + uint64_t size () const; + + uint64_t buildtime () const; + + rust::Vec changelogs () const; + + rust::Str src_pkg () const; + + rust::String nevra () const; + + rust::Vec provided_paths () const; + +private: + ::Header _h; }; // A simple C++ wrapper for a librpm C type, so we can expose it to Rust via cxx.rs. @@ -85,7 +77,6 @@ class RpmTs RpmTs (::RpmOstreeRefTs *ts); ~RpmTs (); rpmts get_ts () const; - rust::Vec packages_providing_file (const rust::Str path) const; std::unique_ptr package_meta (const rust::Str package) const; private: