1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! # rpm-pkg-count
//!
//! Counts installed RPM packages using `librpm`.
//!
//! > Note: This crate does **not** make use of `librpm-sys` but links to the C
//! > library itself.
//!
//! ## Requirements
//!
//! In order to compile this crate, one must have `librpm` installed on the system.
//! It is usually provided by package managers under the name `rpm-devel` (e.g.,
//! OpenSUSE), `rpm-tools` (e.g., Arch Linux) or `librpm-dev` (e.g., Debian).
//!
//! ## Usage
//!
//! The crate provides two cargo features, exactly **one** of them must be enabled.
//!
//! 1. `compile-time`: Link to librpm during compile-time using Rust's `extern "C"`
//!    functionality. This requires librpm to be installed on every target's system
//!    for a binary to run at all.
//! 2. `runtime`: Link to librpm during runtime using the
//!    [`libloading` crate](https://crates.io/crates/libloading). This way,
//!    `count()` simply returns `None` if librpm is not installed on the target
//!    system.
//!
//! The crate then exposes exactly _one_ public function which takes no arguments
//! and returns the package count as an `Option<u32>`. An example usage is shown
//! here:
//!
//! ```
//! use rpm_pkg_count::count;
//!
//! match unsafe { count() } {
//!     Some(count) => println!("{count} packages installed."),
//!     None => println!("packages could not be counted"),
//! }
//! ```
#![warn(rust_2018_idioms)]
#![deny(missing_docs)]

#[cfg(any(
    not(any(feature = "runtime", feature = "compile-time")),
    all(feature = "runtime", feature = "compile-time")
))]
compile_error!("Exactly one of the features `runtime` or `compile-time` is required.");

#[cfg(any(feature = "runtime", feature = "compile-time"))]
mod ffi_types;

#[cfg(feature = "compile-time")]
mod ffi;

/// Return the count of installed RPM packages as a `u32`.
///
/// Code is manually translated from C as used by `fastfetch`:
/// <https://github.com/LinusDierheimer/fastfetch/blob/e837e1e1d1e5a7eba02235748cd1a20a72bc28f9/src/detection/packages/packages_linux.c#L230-L264>
#[allow(clippy::missing_safety_doc)]
#[cfg(feature = "compile-time")]
pub unsafe fn count() -> Option<u32> {
    use ffi::*;
    use ffi_types::*;

    if rpmReadConfigFiles(std::ptr::null(), std::ptr::null()) != 0 {
        return None;
    }

    let ts = rpmtsCreate();
    if ts.is_null() {
        return None;
    }

    let mi = rpmtsInitIterator(ts, rpmDbiTag_e_RPMDBI_LABEL as i32, std::ptr::null(), 0);
    if mi.is_null() {
        rpmtsFree(ts);
        return None;
    }

    let count = rpmdbGetIteratorCount(mi);

    rpmdbFreeIterator(mi);
    rpmtsFree(ts);

    Some(count as u32)
}

/// Return the count of installed RPM packages as a `u32`.
///
/// Code is manually translated from C as used by `fastfetch`:
/// <https://github.com/LinusDierheimer/fastfetch/blob/e837e1e1d1e5a7eba02235748cd1a20a72bc28f9/src/detection/packages/packages_linux.c#L230-L264>
#[allow(clippy::missing_safety_doc, non_snake_case)]
#[cfg(feature = "runtime")]
pub unsafe fn count() -> Option<u32> {
    use ffi_types::*;
    use libloading::{Library, Symbol};

    // dynamically load the `rpm` library from the system
    let lib = Library::new(libloading::library_filename("rpm")).ok()?;

    // and the required function symbols
    let rpmReadConfigFiles: Symbol<
        '_,
        unsafe extern "C" fn(
            file: *const ::std::os::raw::c_char,
            target: *const ::std::os::raw::c_char,
        ) -> ::std::os::raw::c_int,
    > = lib.get(b"rpmReadConfigFiles").ok()?;
    let rpmdbFreeIterator: Symbol<
        '_,
        unsafe extern "C" fn(mi: rpmdbMatchIterator) -> rpmdbMatchIterator,
    > = lib.get(b"rpmdbFreeIterator").ok()?;
    let rpmdbGetIteratorCount: Symbol<
        '_,
        unsafe extern "C" fn(mi: rpmdbMatchIterator) -> ::std::os::raw::c_int,
    > = lib.get(b"rpmdbGetIteratorCount").ok()?;
    let rpmtsCreate: Symbol<'_, unsafe extern "C" fn() -> rpmts> = lib.get(b"rpmtsCreate").ok()?;
    let rpmtsFree: Symbol<'_, unsafe extern "C" fn(ts: rpmts) -> rpmts> =
        lib.get(b"rpmtsFree").ok()?;
    let rpmtsInitIterator: Symbol<
        '_,
        unsafe extern "C" fn(
            ts: rpmts,
            rpmtag: rpmDbiTagVal,
            keyp: *const ::std::os::raw::c_void,
            keylen: size_t,
        ) -> rpmdbMatchIterator,
    > = lib.get(b"rpmtsInitIterator").ok()?;

    if rpmReadConfigFiles(std::ptr::null(), std::ptr::null()) != 0 {
        return None;
    }

    let ts = rpmtsCreate();
    if ts.is_null() {
        return None;
    }

    let mi = rpmtsInitIterator(ts, rpmDbiTag_e_RPMDBI_LABEL as i32, std::ptr::null(), 0);
    if mi.is_null() {
        rpmtsFree(ts);
        return None;
    }

    let count = rpmdbGetIteratorCount(mi);

    rpmdbFreeIterator(mi);
    rpmtsFree(ts);

    Some(count as u32)
}