Skip to main content

librpm/
package.rs

1/*
2 * Copyright (C) RustRPM Developers
3 *
4 * Licensed under the Mozilla Public License Version 2.0
5 * Fedora-License-Identifier: MPLv2.0
6 * SPDX-2.0-License-Identifier: MPL-2.0
7 * SPDX-3.0-License-Identifier: MPL-2.0
8 *
9 * This is free software.
10 * For more information on the license, see LICENSE.
11 * For more information on free software, see <https://www.gnu.org/philosophy/free-sw.en.html>.
12 *
13 * This Source Code Form is subject to the terms of the Mozilla Public
14 * License, v. 2.0. If a copy of the MPL was not distributed with this
15 * file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
16 */
17
18//! RPM package type: represents `.rpm` files or entries in the RPM database
19use crate::dep::Dependencies;
20use crate::files::Files;
21use crate::internal::header::Header;
22use crate::{RpmErrorKind, Tag, TagData};
23use std::convert::TryFrom;
24use std::hash::{Hash, Hasher};
25use std::{fmt, path::Path, time};
26
27/// RPM packages
28///
29/// A thin wrapper around a librpm header. Accessors perform tag lookups
30/// on demand rather than copying data out of the header up front.
31///
32/// # Ownership
33///
34/// Each `Package` owns a reference-counted link to its underlying
35/// librpm `Header`. Cloning a `Package` increments the header's
36/// refcount (`headerLink`); dropping decrements it (`headerFree`).
37/// A `Package` is fully independent of the `Db` or iterator that
38/// produced it.
39///
40/// # String lifetimes
41///
42/// Accessors like [`name()`](Package::name) and
43/// [`version()`](Package::version) return `&str` tied to `&self`.
44/// These point directly into the header's in-memory blob (via
45/// `HEADERGET_MINMEM`), so they are valid as long as the `Package`
46/// is alive. No allocation or copy occurs.
47pub struct Package {
48    header: Header,
49}
50
51impl Package {
52    pub(crate) fn from_header(h: &Header) -> Self {
53        Package { header: h.clone() }
54    }
55
56    /// Create a Package by reading an `.rpm` file
57    pub fn from_file(path: &Path) -> Result<Self, RpmErrorKind> {
58        let header = Header::from_file(path)?;
59        Ok(Package { header })
60    }
61
62    /// Look up raw tag data from the underlying RPM header
63    pub fn get(&self, tag: Tag) -> Option<TagData<'_>> {
64        self.header.get(tag)
65    }
66
67    /// Name of the package
68    pub fn name(&self) -> &str {
69        self.header
70            .get(Tag::NAME)
71            .expect("NAME tag missing")
72            .as_str()
73            .expect("NAME tag is not a string")
74    }
75
76    /// Epoch of the package
77    pub fn epoch(&self) -> Option<i32> {
78        self.header
79            .get(Tag::EPOCH)
80            .map(|d| d.as_int32().expect("EPOCH tag is not an int32"))
81    }
82
83    /// Version of the package
84    pub fn version(&self) -> &str {
85        self.header
86            .get(Tag::VERSION)
87            .expect("VERSION tag missing")
88            .as_str()
89            .expect("VERSION tag is not a string")
90    }
91
92    /// Release of the package
93    pub fn release(&self) -> &str {
94        self.header
95            .get(Tag::RELEASE)
96            .expect("RELEASE tag missing")
97            .as_str()
98            .expect("RELEASE tag is not a string")
99    }
100
101    /// Arch of the package
102    pub fn arch(&self) -> Option<&str> {
103        self.header
104            .get(Tag::ARCH)
105            .map(|d| d.as_str().expect("ARCH tag is not a string"))
106    }
107
108    /// EVR (epoch, version, release) of the package
109    pub fn evr(&self) -> String {
110        if let Some(epoch) = self.epoch() {
111            format!("{}:{}-{}", epoch, self.version(), self.release())
112        } else {
113            format!("{}-{}", self.version(), self.release())
114        }
115    }
116
117    /// NEVRA (name, epoch, version, release, arch) of the package
118    pub fn nevra(&self) -> String {
119        if let Some(arch) = self.arch() {
120            format!("{}-{}.{}", self.name(), self.evr(), arch)
121        } else {
122            format!("{}-{}", self.name(), self.evr())
123        }
124    }
125
126    /// License of the package
127    pub fn license(&self) -> &str {
128        self.header
129            .get(Tag::LICENSE)
130            .expect("LICENSE tag missing")
131            .as_str()
132            .expect("LICENSE tag is not a string")
133    }
134
135    /// Succinct description of the package
136    pub fn summary(&self) -> &str {
137        self.header
138            .get(Tag::SUMMARY)
139            .expect("SUMMARY tag missing")
140            .as_str()
141            .expect("SUMMARY tag is not a string")
142    }
143
144    /// Longer description of the package
145    pub fn description(&self) -> &str {
146        self.header
147            .get(Tag::DESCRIPTION)
148            .expect("DESCRIPTION tag missing")
149            .as_str()
150            .expect("DESCRIPTION tag is not a string")
151    }
152
153    /// File information for the package.
154    pub fn files(&self) -> Files {
155        Files::from_header(&self.header)
156    }
157
158    /// `Requires` dependencies.
159    pub fn requires(&self) -> Dependencies {
160        Dependencies::from_header(&self.header, Tag::REQUIRENAME)
161    }
162
163    /// `Provides` dependencies.
164    pub fn provides(&self) -> Dependencies {
165        Dependencies::from_header(&self.header, Tag::PROVIDENAME)
166    }
167
168    /// `Conflicts` dependencies.
169    pub fn conflicts(&self) -> Dependencies {
170        Dependencies::from_header(&self.header, Tag::CONFLICTNAME)
171    }
172
173    /// `Obsoletes` dependencies.
174    pub fn obsoletes(&self) -> Dependencies {
175        Dependencies::from_header(&self.header, Tag::OBSOLETENAME)
176    }
177
178    /// `Recommends` (weak) dependencies.
179    pub fn recommends(&self) -> Dependencies {
180        Dependencies::from_header(&self.header, Tag::RECOMMENDNAME)
181    }
182
183    /// `Suggests` (weak) dependencies.
184    pub fn suggests(&self) -> Dependencies {
185        Dependencies::from_header(&self.header, Tag::SUGGESTNAME)
186    }
187
188    /// `Supplements` (weak) dependencies.
189    pub fn supplements(&self) -> Dependencies {
190        Dependencies::from_header(&self.header, Tag::SUPPLEMENTNAME)
191    }
192
193    /// `Enhances` (weak) dependencies.
194    pub fn enhances(&self) -> Dependencies {
195        Dependencies::from_header(&self.header, Tag::ENHANCENAME)
196    }
197
198    /// Buildtime of the package
199    pub fn buildtime(&self) -> time::SystemTime {
200        let buildtime = self
201            .header
202            .get(Tag::BUILDTIME)
203            .expect("BUILDTIME tag missing")
204            .as_int32()
205            .expect("BUILDTIME tag is not an int32");
206        let buildtime = u64::try_from(buildtime).expect("negative build time");
207        time::SystemTime::UNIX_EPOCH + time::Duration::new(buildtime, 0)
208    }
209}
210
211impl Clone for Package {
212    fn clone(&self) -> Self {
213        Package {
214            header: self.header.clone(),
215        }
216    }
217}
218
219impl fmt::Debug for Package {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        f.debug_struct("Package")
222            .field("name", &self.name())
223            .field("epoch", &self.epoch())
224            .field("version", &self.version())
225            .field("release", &self.release())
226            .field("arch", &self.arch())
227            .finish()
228    }
229}
230
231impl fmt::Display for Package {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        write!(f, "{}", self.nevra())
234    }
235}
236
237impl PartialEq for Package {
238    fn eq(&self, other: &Self) -> bool {
239        self.name() == other.name()
240            && self.epoch() == other.epoch()
241            && self.version() == other.version()
242            && self.release() == other.release()
243            && self.arch() == other.arch()
244    }
245}
246
247impl Eq for Package {}
248
249impl Hash for Package {
250    fn hash<H: Hasher>(&self, state: &mut H) {
251        self.name().hash(state);
252        self.epoch().hash(state);
253        self.version().hash(state);
254        self.release().hash(state);
255        self.arch().hash(state);
256    }
257}