1use std::path::Path;
23
24use crate::parser_warn as warn;
25
26use crate::models::{DatasourceId, PackageData, PackageType};
27use crate::models::{Dependency, FileReference};
28use crate::parsers::utils::{MAX_ITERATION_COUNT, MAX_MANIFEST_SIZE, truncate_field};
29
30use super::PackageParser;
31use super::rpm_db_native::{InstalledRpmDbKind, InstalledRpmPackage, read_installed_rpm_packages};
32use super::rpm_parser::infer_rpm_namespace;
33use super::rpm_parser::infer_rpm_namespace_from_filename;
34
35const PACKAGE_TYPE: PackageType = PackageType::Rpm;
36const RPM_BDB_PATH_SUFFIXES: &[&str] = &["var/lib/rpm/Packages", "usr/lib/sysimage/rpm/Packages"];
37const RPM_NDB_PATH_SUFFIXES: &[&str] = &[
38 "var/lib/rpm/Packages.db",
39 "usr/lib/sysimage/rpm/Packages.db",
40];
41const RPM_SQLITE_PATH_SUFFIXES: &[&str] = &[
42 "var/lib/rpm/rpmdb.sqlite",
43 "usr/lib/sysimage/rpm/rpmdb.sqlite",
44];
45
46#[derive(Debug)]
47struct RpmQueryPackage {
48 name: Option<String>,
49 epoch: Option<String>,
50 version: Option<String>,
51 release: Option<String>,
52 vendor: Option<String>,
53 distribution: Option<String>,
54 arch: Option<String>,
55 platform: Option<String>,
56 size: Option<u64>,
57 license: Option<String>,
58 source_rpm: Option<String>,
59 requires: Vec<String>,
60 file_names: Vec<Option<String>>,
61 dir_indexes: Vec<u32>,
62 base_names: Vec<Option<String>>,
63 dir_names: Vec<String>,
64}
65
66fn default_package_data(datasource_id: DatasourceId) -> PackageData {
67 PackageData {
68 package_type: Some(PACKAGE_TYPE),
69 datasource_id: Some(datasource_id),
70 ..Default::default()
71 }
72}
73
74pub struct RpmBdbDatabaseParser;
75
76impl PackageParser for RpmBdbDatabaseParser {
77 const PACKAGE_TYPE: PackageType = PACKAGE_TYPE;
78
79 fn is_match(path: &Path) -> bool {
80 path_matches_any_suffix(path, RPM_BDB_PATH_SUFFIXES)
81 }
82
83 fn extract_packages(path: &Path) -> Vec<PackageData> {
84 match parse_rpm_database(path, DatasourceId::RpmInstalledDatabaseBdb) {
85 Ok(pkgs) if !pkgs.is_empty() => pkgs,
86 Ok(_) => vec![default_package_data(DatasourceId::RpmInstalledDatabaseBdb)],
87 Err(e) => {
88 warn!("Failed to parse RPM BDB database {:?}: {}", path, e);
89 vec![default_package_data(DatasourceId::RpmInstalledDatabaseBdb)]
90 }
91 }
92 }
93}
94
95pub struct RpmNdbDatabaseParser;
96
97impl PackageParser for RpmNdbDatabaseParser {
98 const PACKAGE_TYPE: PackageType = PACKAGE_TYPE;
99
100 fn is_match(path: &Path) -> bool {
101 path_matches_any_suffix(path, RPM_NDB_PATH_SUFFIXES)
102 }
103
104 fn extract_packages(path: &Path) -> Vec<PackageData> {
105 match parse_rpm_database(path, DatasourceId::RpmInstalledDatabaseNdb) {
106 Ok(pkgs) if !pkgs.is_empty() => pkgs,
107 Ok(_) => vec![default_package_data(DatasourceId::RpmInstalledDatabaseNdb)],
108 Err(e) => {
109 warn!("Failed to parse RPM NDB database {:?}: {}", path, e);
110 vec![default_package_data(DatasourceId::RpmInstalledDatabaseNdb)]
111 }
112 }
113 }
114}
115
116#[cfg(feature = "rpm-sqlite")]
117pub struct RpmSqliteDatabaseParser;
118
119#[cfg(feature = "rpm-sqlite")]
120impl PackageParser for RpmSqliteDatabaseParser {
121 const PACKAGE_TYPE: PackageType = PACKAGE_TYPE;
122
123 fn is_match(path: &Path) -> bool {
124 path_matches_any_suffix(path, RPM_SQLITE_PATH_SUFFIXES)
125 }
126
127 fn extract_packages(path: &Path) -> Vec<PackageData> {
128 match parse_rpm_database(path, DatasourceId::RpmInstalledDatabaseSqlite) {
129 Ok(pkgs) if !pkgs.is_empty() => pkgs,
130 Ok(_) => vec![default_package_data(
131 DatasourceId::RpmInstalledDatabaseSqlite,
132 )],
133 Err(e) => {
134 warn!("Failed to parse RPM SQLite database {:?}: {}", path, e);
135 vec![default_package_data(
136 DatasourceId::RpmInstalledDatabaseSqlite,
137 )]
138 }
139 }
140 }
141}
142
143fn parse_rpm_database(
144 path: &Path,
145 datasource_id: DatasourceId,
146) -> Result<Vec<PackageData>, String> {
147 let metadata = std::fs::metadata(path)
148 .map_err(|e| format!("Cannot stat RPM database file {:?}: {}", path, e))?;
149
150 if metadata.len() > MAX_MANIFEST_SIZE {
151 return Err(format!(
152 "RPM database file {:?} is {} bytes, exceeding the {} byte limit",
153 path,
154 metadata.len(),
155 MAX_MANIFEST_SIZE
156 ));
157 }
158
159 let native_kind = native_kind_for_datasource(datasource_id)?;
160 match read_installed_rpm_packages(path, native_kind) {
161 Ok(packages) => Ok(packages
162 .into_iter()
163 .take(MAX_ITERATION_COUNT)
164 .map(native_package_to_query_package)
165 .map(|pkg| build_package_data(pkg, datasource_id))
166 .collect()),
167 Err(native_error) => Err(format!(
168 "native installed RPM reader failed for {:?}: {}",
169 path, native_error
170 )),
171 }
172}
173
174fn path_matches_suffix(path: &Path, suffix: &str) -> bool {
175 path.to_string_lossy().replace('\\', "/").ends_with(suffix)
176}
177
178fn path_matches_any_suffix(path: &Path, suffixes: &[&str]) -> bool {
179 suffixes
180 .iter()
181 .any(|suffix| path_matches_suffix(path, suffix))
182}
183
184fn native_kind_for_datasource(datasource_id: DatasourceId) -> Result<InstalledRpmDbKind, String> {
185 match datasource_id {
186 DatasourceId::RpmInstalledDatabaseBdb => Ok(InstalledRpmDbKind::Bdb),
187 DatasourceId::RpmInstalledDatabaseNdb => Ok(InstalledRpmDbKind::Ndb),
188 DatasourceId::RpmInstalledDatabaseSqlite => Ok(InstalledRpmDbKind::Sqlite),
189 other => Err(format!(
190 "unexpected datasource for installed RPM DB: {other:?}"
191 )),
192 }
193}
194
195fn native_package_to_query_package(package: InstalledRpmPackage) -> RpmQueryPackage {
196 RpmQueryPackage {
197 name: truncate_optional_string(Some(package.name)),
198 epoch: Some(package.epoch.to_string()),
199 version: truncate_optional_string(Some(package.version)),
200 release: truncate_optional_string(Some(package.release)),
201 vendor: truncate_optional_string(Some(package.vendor)),
202 distribution: truncate_optional_string(Some(package.distribution)),
203 arch: truncate_optional_string(Some(package.arch)),
204 platform: truncate_optional_string(Some(package.platform)),
205 size: (package.size > 0).then_some(u64::from(package.size)),
206 license: truncate_optional_string(Some(package.license)),
207 source_rpm: truncate_optional_string(Some(package.source_rpm)),
208 requires: package
209 .requires
210 .into_iter()
211 .take(MAX_ITERATION_COUNT)
212 .map(truncate_field)
213 .collect(),
214 file_names: package
215 .file_names
216 .into_iter()
217 .take(MAX_ITERATION_COUNT)
218 .map(|s| Some(truncate_field(s)))
219 .collect(),
220 dir_indexes: package.dir_indexes,
221 base_names: package
222 .base_names
223 .into_iter()
224 .take(MAX_ITERATION_COUNT)
225 .map(|s| Some(truncate_field(s)))
226 .collect(),
227 dir_names: package
228 .dir_names
229 .into_iter()
230 .take(MAX_ITERATION_COUNT)
231 .map(truncate_field)
232 .collect(),
233 }
234}
235
236fn truncate_optional_string(value: Option<String>) -> Option<String> {
237 value
238 .map(truncate_field)
239 .and_then(|v| normalize_optional_string(Some(v)))
240}
241
242fn build_evr_version(epoch: u32, version: &str, release: &str) -> Option<String> {
243 if version.is_empty() {
244 return None;
245 }
246
247 let mut evr = String::new();
248
249 if epoch > 0 {
250 evr.push_str(&format!("{}:", epoch));
251 }
252
253 evr.push_str(version);
254
255 if !release.is_empty() {
256 evr.push('-');
257 evr.push_str(release);
258 }
259
260 Some(evr)
261}
262
263fn build_file_references(
264 base_names: &[Option<String>],
265 dir_indexes: &[u32],
266 dir_names: &[String],
267) -> Vec<FileReference> {
268 if base_names.is_empty() || dir_names.is_empty() {
269 return Vec::new();
270 }
271
272 base_names
273 .iter()
274 .zip(dir_indexes.iter())
275 .take(MAX_ITERATION_COUNT)
276 .filter_map(|(basename, &dir_idx)| {
277 let dirname = dir_names.get(dir_idx as usize)?;
278 let basename = basename.as_deref().unwrap_or_default();
279 let path = format!("{}{}", dirname, basename);
280 if path.is_empty() || path == "/" {
281 return None;
282 }
283 Some(FileReference {
284 path,
285 size: None,
286 sha1: None,
287 md5: None,
288 sha256: None,
289 sha512: None,
290 extra_data: None,
291 })
292 })
293 .collect()
294}
295
296fn build_file_references_from_paths(paths: &[Option<String>]) -> Vec<FileReference> {
297 paths
298 .iter()
299 .take(MAX_ITERATION_COUNT)
300 .filter_map(|path| {
301 let path = path.as_deref()?.trim();
302 if path.is_empty() || path == "/" {
303 return None;
304 }
305
306 Some(FileReference {
307 path: path.to_string(),
308 size: None,
309 sha1: None,
310 md5: None,
311 sha256: None,
312 sha512: None,
313 extra_data: None,
314 })
315 })
316 .collect()
317}
318
319fn build_package_data(pkg: RpmQueryPackage, datasource_id: DatasourceId) -> PackageData {
320 let name = normalize_optional_string(pkg.name).map(truncate_field);
321 let version_raw = normalize_optional_string(pkg.version).map(truncate_field);
322 let release = normalize_optional_string(pkg.release).map(truncate_field);
323 let version = build_evr_version(
324 parse_epoch(pkg.epoch),
325 version_raw.as_deref().unwrap_or_default(),
326 release.as_deref().unwrap_or_default(),
327 );
328
329 let vendor = normalize_optional_string(pkg.vendor)
330 .map(truncate_field)
331 .or_else(|| normalize_optional_string(pkg.distribution).map(truncate_field));
332 let source_rpm = normalize_optional_string(pkg.source_rpm).map(truncate_field);
333 let namespace =
334 infer_rpm_namespace(None, vendor.as_deref(), release.as_deref(), None).or_else(|| {
335 source_rpm
336 .as_deref()
337 .and_then(|source_rpm| infer_rpm_namespace_from_filename(Path::new(source_rpm)))
338 });
339
340 let architecture = normalize_optional_string(pkg.arch)
341 .map(truncate_field)
342 .or_else(|| infer_platform_architecture(pkg.platform.as_deref()));
343 let dependencies = pkg
344 .requires
345 .into_iter()
346 .take(MAX_ITERATION_COUNT)
347 .filter_map(|require| build_dependency(&require))
348 .collect();
349 let extracted_license_statement = normalize_optional_string(pkg.license).map(truncate_field);
350 let source_packages = source_rpm.clone().into_iter().collect();
351 let file_references = {
352 let from_dir_components =
353 build_file_references(&pkg.base_names, &pkg.dir_indexes, &pkg.dir_names);
354 if from_dir_components.is_empty() {
355 build_file_references_from_paths(&pkg.file_names)
356 } else {
357 from_dir_components
358 }
359 };
360 let purl = build_package_purl(
361 name.as_deref(),
362 namespace.as_deref(),
363 version.as_deref(),
364 architecture.as_deref(),
365 );
366
367 PackageData {
368 datasource_id: Some(datasource_id),
369 package_type: Some(PACKAGE_TYPE),
370 namespace,
371 name,
372 version,
373 qualifiers: architecture.as_ref().map(|arch| {
374 let mut q = std::collections::HashMap::new();
375 q.insert("arch".to_string(), arch.clone());
376 q
377 }),
378 subpath: None,
379 primary_language: None,
380 description: None,
381 release_date: None,
382 parties: Vec::new(),
383 keywords: Vec::new(),
384 homepage_url: None,
385 download_url: None,
386 size: pkg.size.filter(|size| *size > 0),
387 sha1: None,
388 md5: None,
389 sha256: None,
390 sha512: None,
391 bug_tracking_url: None,
392 code_view_url: None,
393 vcs_url: None,
394 copyright: None,
395 holder: None,
396 declared_license_expression: None,
397 declared_license_expression_spdx: None,
398 license_detections: Vec::new(),
399 other_license_expression: None,
400 other_license_expression_spdx: None,
401 other_license_detections: Vec::new(),
402 extracted_license_statement,
403 notice_text: None,
404 source_packages,
405 file_references,
406 is_private: false,
407 is_virtual: false,
408 extra_data: None,
409 dependencies,
410 repository_homepage_url: None,
411 repository_download_url: None,
412 api_data_url: None,
413 purl,
414 }
415}
416
417fn build_dependency(require: &str) -> Option<Dependency> {
418 let require = require.trim();
419 if require.is_empty() || require.starts_with("rpmlib(") || require.starts_with("config(") {
420 return None;
421 }
422
423 let purl = packageurl::PackageUrl::new(PACKAGE_TYPE.as_str(), require)
424 .ok()
425 .map(|p| p.to_string());
426
427 Some(Dependency {
428 purl,
429 extracted_requirement: None,
430 scope: Some("requires".to_string()),
431 is_runtime: Some(true),
432 is_optional: Some(false),
433 is_pinned: Some(false),
434 is_direct: Some(true),
435 resolved_package: None,
436 extra_data: None,
437 })
438}
439
440fn build_package_purl(
441 name: Option<&str>,
442 namespace: Option<&str>,
443 version: Option<&str>,
444 arch: Option<&str>,
445) -> Option<String> {
446 let name = name?;
447 let mut purl = packageurl::PackageUrl::new(PACKAGE_TYPE.as_str(), name).ok()?;
448
449 if let Some(namespace) = namespace {
450 purl.with_namespace(namespace).ok()?;
451 }
452
453 if let Some(version) = version {
454 purl.with_version(version).ok()?;
455 }
456
457 if let Some(arch) = arch {
458 purl.add_qualifier("arch", arch).ok()?;
459 }
460
461 Some(purl.to_string())
462}
463
464fn normalize_optional_string(value: Option<String>) -> Option<String> {
465 value.and_then(|value| {
466 let trimmed = value.trim();
467 if trimmed.is_empty() || trimmed == "(none)" || trimmed == "[]" {
468 None
469 } else {
470 Some(trimmed.to_string())
471 }
472 })
473}
474
475fn parse_epoch(value: Option<String>) -> u32 {
476 normalize_optional_string(value)
477 .and_then(|value| value.parse::<u32>().ok())
478 .unwrap_or(0)
479}
480
481fn infer_platform_architecture(platform: Option<&str>) -> Option<String> {
482 let platform = platform?.trim();
483 if platform.is_empty() {
484 return None;
485 }
486
487 platform
488 .split_once('-')
489 .map(|(arch, _)| arch)
490 .filter(|arch| !arch.is_empty())
491 .map(|arch| arch.to_string())
492}
493
494#[cfg(test)]
495mod tests {
496 use super::*;
497
498 use crate::models::DatasourceId;
499 use std::path::PathBuf;
500
501 #[test]
502 fn test_bdb_parser_is_match() {
503 assert!(RpmBdbDatabaseParser::is_match(&PathBuf::from(
504 "/var/lib/rpm/Packages"
505 )));
506 assert!(RpmBdbDatabaseParser::is_match(&PathBuf::from(
507 "rootfs/var/lib/rpm/Packages"
508 )));
509 assert!(RpmBdbDatabaseParser::is_match(&PathBuf::from(
510 "/usr/lib/sysimage/rpm/Packages"
511 )));
512 assert!(!RpmBdbDatabaseParser::is_match(&PathBuf::from(
513 "/var/lib/rpm/Packages.db"
514 )));
515 assert!(!RpmBdbDatabaseParser::is_match(&PathBuf::from(
516 "lib/modules/datasource/deb/__fixtures__/Packages"
517 )));
518 assert!(!RpmBdbDatabaseParser::is_match(&PathBuf::from("Packages")));
519 assert!(!RpmBdbDatabaseParser::is_match(&PathBuf::from(
520 "testdata/rpm/var/lib/rpm/Packages.expected.json"
521 )));
522 }
523
524 #[test]
525 fn test_ndb_parser_is_match() {
526 assert!(RpmNdbDatabaseParser::is_match(&PathBuf::from(
527 "usr/lib/sysimage/rpm/Packages.db"
528 )));
529 assert!(RpmNdbDatabaseParser::is_match(&PathBuf::from(
530 "/rootfs/usr/lib/sysimage/rpm/Packages.db"
531 )));
532 assert!(!RpmNdbDatabaseParser::is_match(&PathBuf::from(
533 "usr/lib/rpm/Packages"
534 )));
535 assert!(RpmNdbDatabaseParser::is_match(&PathBuf::from(
536 "var/lib/rpm/Packages.db"
537 )));
538 assert!(!RpmNdbDatabaseParser::is_match(&PathBuf::from(
539 "testdata/rpm/usr/lib/sysimage/rpm/Packages.db.expected.json"
540 )));
541 }
542
543 #[cfg(feature = "rpm-sqlite")]
544 #[test]
545 fn test_sqlite_parser_is_match() {
546 assert!(RpmSqliteDatabaseParser::is_match(&PathBuf::from(
547 "var/lib/rpm/rpmdb.sqlite"
548 )));
549 assert!(RpmSqliteDatabaseParser::is_match(&PathBuf::from(
550 "/rootfs/var/lib/rpm/rpmdb.sqlite"
551 )));
552 assert!(RpmSqliteDatabaseParser::is_match(&PathBuf::from(
553 "/rootfs/usr/lib/sysimage/rpm/rpmdb.sqlite"
554 )));
555 assert!(!RpmSqliteDatabaseParser::is_match(&PathBuf::from(
556 "/var/lib/rpm/Packages"
557 )));
558 assert!(!RpmSqliteDatabaseParser::is_match(&PathBuf::from(
559 "testdata/rpm/rpmdb.sqlite.expected.json"
560 )));
561 assert!(!RpmSqliteDatabaseParser::is_match(&PathBuf::from(
562 "testdata/rpm/rpmdb.sqlite-shm"
563 )));
564 assert!(!RpmSqliteDatabaseParser::is_match(&PathBuf::from(
565 "testdata/rpm/rpmdb.sqlite-wal"
566 )));
567 }
568
569 #[test]
570 fn test_build_evr_version_full() {
571 assert_eq!(
572 build_evr_version(2, "1.0.0", "1.el7"),
573 Some("2:1.0.0-1.el7".to_string())
574 );
575 }
576
577 #[test]
578 fn test_build_evr_version_no_epoch() {
579 assert_eq!(
580 build_evr_version(0, "1.0.0", "1.el7"),
581 Some("1.0.0-1.el7".to_string())
582 );
583 }
584
585 #[test]
586 fn test_build_evr_version_no_release() {
587 assert_eq!(build_evr_version(0, "1.0.0", ""), Some("1.0.0".to_string()));
588 }
589
590 #[test]
591 fn test_build_evr_version_empty() {
592 assert_eq!(build_evr_version(0, "", ""), None);
593 }
594
595 #[cfg(feature = "rpm-sqlite")]
596 #[test]
597 fn test_parse_rpm_database_sqlite() {
598 let test_file = PathBuf::from("testdata/rpm/rpmdb.sqlite");
599
600 let pkg = RpmSqliteDatabaseParser::extract_first_package(&test_file);
601
602 assert_eq!(pkg.package_type, Some(PackageType::Rpm));
603 assert_eq!(
604 pkg.datasource_id,
605 Some(DatasourceId::RpmInstalledDatabaseSqlite)
606 );
607 assert!(pkg.name.is_some());
608 }
609
610 #[cfg(feature = "rpm-sqlite")]
611 #[test]
612 fn test_parse_rpm_database_sqlite_preserves_release_in_version() {
613 let test_file = PathBuf::from("testdata/rpm/rpmdb.sqlite");
614
615 let pkg = RpmSqliteDatabaseParser::extract_first_package(&test_file);
616
617 assert!(
618 pkg.version
619 .as_ref()
620 .is_some_and(|version| version.contains('-'))
621 );
622 }
623
624 #[test]
625 fn test_build_file_references_skips_invalid_entries() {
626 let file_refs = build_file_references(
627 &[
628 Some("valid".to_string()),
629 Some("".to_string()),
630 Some("ignored".to_string()),
631 ],
632 &[0, 0, u32::MAX],
633 &["/usr/bin/".to_string()],
634 );
635
636 assert_eq!(file_refs.len(), 2);
637 assert_eq!(file_refs[0].path, "/usr/bin/valid");
638 assert_eq!(file_refs[1].path, "/usr/bin/");
639 }
640
641 #[test]
642 fn test_build_package_data_falls_back_to_file_names() {
643 let package = build_package_data(
644 RpmQueryPackage {
645 name: Some("libgcc".to_string()),
646 epoch: None,
647 version: Some("13.1.1".to_string()),
648 release: Some("2.fc38".to_string()),
649 vendor: Some("Fedora Project".to_string()),
650 distribution: None,
651 arch: Some("x86_64".to_string()),
652 platform: None,
653 size: Some(235748),
654 license: Some("GPLv3+".to_string()),
655 source_rpm: Some("gcc-13.1.1-2.fc38.src.rpm".to_string()),
656 requires: Vec::new(),
657 file_names: vec![
658 Some("/usr/share/licenses/libgcc/COPYING".to_string()),
659 Some("/usr/share/licenses/libgcc/COPYING.RUNTIME".to_string()),
660 ],
661 dir_indexes: Vec::new(),
662 base_names: Vec::new(),
663 dir_names: Vec::new(),
664 },
665 DatasourceId::RpmInstalledDatabaseSqlite,
666 );
667
668 assert_eq!(package.file_references.len(), 2);
669 assert_eq!(
670 package.file_references[0].path,
671 "/usr/share/licenses/libgcc/COPYING"
672 );
673 assert_eq!(
674 package.file_references[1].path,
675 "/usr/share/licenses/libgcc/COPYING.RUNTIME"
676 );
677 }
678
679 #[test]
680 fn test_build_package_data_uses_distribution_for_namespace() {
681 let package = build_package_data(
682 RpmQueryPackage {
683 name: Some("libgcc".to_string()),
684 epoch: None,
685 version: Some("13.1.1".to_string()),
686 release: Some("2.fc38".to_string()),
687 vendor: None,
688 distribution: Some("Fedora Project".to_string()),
689 arch: Some("x86_64".to_string()),
690 platform: None,
691 size: Some(235748),
692 license: Some("GPLv3+".to_string()),
693 source_rpm: Some("gcc-13.1.1-2.fc38.src.rpm".to_string()),
694 requires: Vec::new(),
695 file_names: vec![Some("/usr/share/licenses/libgcc/COPYING".to_string())],
696 dir_indexes: Vec::new(),
697 base_names: Vec::new(),
698 dir_names: Vec::new(),
699 },
700 DatasourceId::RpmInstalledDatabaseSqlite,
701 );
702
703 assert_eq!(package.namespace.as_deref(), Some("fedora"));
704 }
705
706 #[test]
707 fn test_build_package_data_uses_source_rpm_for_namespace() {
708 let package = build_package_data(
709 RpmQueryPackage {
710 name: Some("libgcc".to_string()),
711 epoch: None,
712 version: Some("13.1.1".to_string()),
713 release: None,
714 vendor: None,
715 distribution: None,
716 arch: Some("x86_64".to_string()),
717 platform: None,
718 size: Some(235748),
719 license: Some("GPLv3+".to_string()),
720 source_rpm: Some("gcc-13.1.1-2.fc38.src.rpm".to_string()),
721 requires: Vec::new(),
722 file_names: vec![Some("/usr/share/licenses/libgcc/COPYING".to_string())],
723 dir_indexes: Vec::new(),
724 base_names: Vec::new(),
725 dir_names: Vec::new(),
726 },
727 DatasourceId::RpmInstalledDatabaseSqlite,
728 );
729
730 assert_eq!(package.namespace.as_deref(), Some("fedora"));
731 }
732
733 #[test]
734 fn test_build_package_data_uses_platform_for_architecture() {
735 let package = build_package_data(
736 RpmQueryPackage {
737 name: Some("libgcc".to_string()),
738 epoch: None,
739 version: Some("13.1.1".to_string()),
740 release: None,
741 vendor: None,
742 distribution: None,
743 arch: None,
744 platform: Some("x86_64-redhat-linux".to_string()),
745 size: Some(235748),
746 license: Some("GPLv3+".to_string()),
747 source_rpm: Some("gcc-13.1.1-2.fc38.src.rpm".to_string()),
748 requires: Vec::new(),
749 file_names: vec![Some("/usr/share/licenses/libgcc/COPYING".to_string())],
750 dir_indexes: Vec::new(),
751 base_names: Vec::new(),
752 dir_names: Vec::new(),
753 },
754 DatasourceId::RpmInstalledDatabaseSqlite,
755 );
756
757 assert_eq!(
758 package.qualifiers.as_ref().and_then(|q| q.get("arch")),
759 Some(&"x86_64".to_string())
760 );
761 }
762}
763
764#[cfg(feature = "rpm-sqlite")]
765crate::register_parser!(
766 "RPM installed package database",
767 &[
768 "**/var/lib/rpm/Packages",
769 "**/usr/lib/sysimage/rpm/Packages",
770 "**/var/lib/rpm/Packages.db",
771 "**/usr/lib/sysimage/rpm/Packages.db",
772 "**/var/lib/rpm/rpmdb.sqlite",
773 "**/usr/lib/sysimage/rpm/rpmdb.sqlite"
774 ],
775 "rpm",
776 "",
777 Some("https://rpm.org/"),
778);
779
780#[cfg(not(feature = "rpm-sqlite"))]
781crate::register_parser!(
782 "RPM installed package database",
783 &[
784 "**/var/lib/rpm/Packages",
785 "**/usr/lib/sysimage/rpm/Packages",
786 "**/var/lib/rpm/Packages.db",
787 "**/usr/lib/sysimage/rpm/Packages.db"
788 ],
789 "rpm",
790 "",
791 Some("https://rpm.org/"),
792);