rpm_pkg_count/
lib.rs

1//! # rpm-pkg-count
2//!
3//! Counts installed RPM packages using `librpm`.
4//!
5//! > Note: This crate does **not** make use of `librpm-sys` but links to the C
6//! > library itself.
7//!
8//! ## Requirements
9//!
10//! In order to compile this crate, one must have `librpm` installed on the system.
11//! It is usually provided by package managers under the name `rpm-devel` (e.g.,
12//! OpenSUSE), `rpm-tools` (e.g., Arch Linux) or `librpm-dev` (e.g., Debian).
13//!
14//! ## Usage
15//!
16//! The crate provides two cargo features, exactly **one** of them must be enabled.
17//!
18//! 1. `compile-time`: Link to librpm during compile-time using Rust's `extern "C"`
19//!    functionality. This requires librpm to be installed on every target's system
20//!    for a binary to run at all.
21//! 2. `runtime`: Link to librpm during runtime using the
22//!    [`libloading` crate](https://crates.io/crates/libloading). This way,
23//!    `count()` simply returns `None` if librpm is not installed on the target
24//!    system.
25//!
26//! The crate then exposes exactly _one_ public function which takes no arguments
27//! and returns the package count as an `Option<u32>`. An example usage is shown
28//! here:
29//!
30//! ```
31//! use rpm_pkg_count::count;
32//!
33//! match unsafe { count() } {
34//!     Some(count) => println!("{count} packages installed."),
35//!     None => println!("packages could not be counted"),
36//! }
37//! ```
38#![warn(rust_2018_idioms)]
39#![deny(missing_docs)]
40
41#[cfg(any(
42    not(any(feature = "runtime", feature = "compile-time")),
43    all(feature = "runtime", feature = "compile-time")
44))]
45compile_error!("Exactly one of the features `runtime` or `compile-time` is required.");
46
47#[cfg(any(feature = "runtime", feature = "compile-time"))]
48mod ffi_types;
49
50#[cfg(feature = "compile-time")]
51mod ffi;
52
53/// Return the count of installed RPM packages as a `u32`.
54///
55/// Code is manually translated from C as used by `fastfetch`:
56/// <https://github.com/LinusDierheimer/fastfetch/blob/e837e1e1d1e5a7eba02235748cd1a20a72bc28f9/src/detection/packages/packages_linux.c#L230-L264>
57#[allow(clippy::missing_safety_doc)]
58#[cfg(feature = "compile-time")]
59pub unsafe fn count() -> Option<u32> {
60    use ffi::*;
61    use ffi_types::*;
62
63    if rpmReadConfigFiles(std::ptr::null(), std::ptr::null()) != 0 {
64        return None;
65    }
66
67    let ts = rpmtsCreate();
68    if ts.is_null() {
69        return None;
70    }
71
72    let mi = rpmtsInitIterator(ts, rpmDbiTag_e_RPMDBI_LABEL as i32, std::ptr::null(), 0);
73    if mi.is_null() {
74        rpmtsFree(ts);
75        return None;
76    }
77
78    let count = rpmdbGetIteratorCount(mi);
79
80    rpmdbFreeIterator(mi);
81    rpmtsFree(ts);
82
83    Some(count as u32)
84}
85
86/// Return the count of installed RPM packages as a `u32`.
87///
88/// Code is manually translated from C as used by `fastfetch`:
89/// <https://github.com/LinusDierheimer/fastfetch/blob/e837e1e1d1e5a7eba02235748cd1a20a72bc28f9/src/detection/packages/packages_linux.c#L230-L264>
90#[allow(clippy::missing_safety_doc, non_snake_case)]
91#[cfg(feature = "runtime")]
92pub unsafe fn count() -> Option<u32> {
93    use ffi_types::*;
94    use libloading::{Library, Symbol};
95
96    // dynamically load the `rpm` library from the system
97    let lib = Library::new(libloading::library_filename("rpm")).ok()?;
98
99    // and the required function symbols
100    let rpmReadConfigFiles: Symbol<
101        '_,
102        unsafe extern "C" fn(
103            file: *const ::std::os::raw::c_char,
104            target: *const ::std::os::raw::c_char,
105        ) -> ::std::os::raw::c_int,
106    > = lib.get(b"rpmReadConfigFiles").ok()?;
107    let rpmdbFreeIterator: Symbol<
108        '_,
109        unsafe extern "C" fn(mi: rpmdbMatchIterator) -> rpmdbMatchIterator,
110    > = lib.get(b"rpmdbFreeIterator").ok()?;
111    let rpmdbGetIteratorCount: Symbol<
112        '_,
113        unsafe extern "C" fn(mi: rpmdbMatchIterator) -> ::std::os::raw::c_int,
114    > = lib.get(b"rpmdbGetIteratorCount").ok()?;
115    let rpmtsCreate: Symbol<'_, unsafe extern "C" fn() -> rpmts> = lib.get(b"rpmtsCreate").ok()?;
116    let rpmtsFree: Symbol<'_, unsafe extern "C" fn(ts: rpmts) -> rpmts> =
117        lib.get(b"rpmtsFree").ok()?;
118    let rpmtsInitIterator: Symbol<
119        '_,
120        unsafe extern "C" fn(
121            ts: rpmts,
122            rpmtag: rpmDbiTagVal,
123            keyp: *const ::std::os::raw::c_void,
124            keylen: size_t,
125        ) -> rpmdbMatchIterator,
126    > = lib.get(b"rpmtsInitIterator").ok()?;
127
128    if rpmReadConfigFiles(std::ptr::null(), std::ptr::null()) != 0 {
129        return None;
130    }
131
132    let ts = rpmtsCreate();
133    if ts.is_null() {
134        return None;
135    }
136
137    let mi = rpmtsInitIterator(ts, rpmDbiTag_e_RPMDBI_LABEL as i32, std::ptr::null(), 0);
138    if mi.is_null() {
139        rpmtsFree(ts);
140        return None;
141    }
142
143    let count = rpmdbGetIteratorCount(mi);
144
145    rpmdbFreeIterator(mi);
146    rpmtsFree(ts);
147
148    Some(count as u32)
149}