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) -> InstalledRpmDbKind {
185 match datasource_id {
186 DatasourceId::RpmInstalledDatabaseBdb => InstalledRpmDbKind::Bdb,
187 DatasourceId::RpmInstalledDatabaseNdb => InstalledRpmDbKind::Ndb,
188 DatasourceId::RpmInstalledDatabaseSqlite => InstalledRpmDbKind::Sqlite,
189 other => panic!("unexpected datasource for installed RPM DB: {other:?}"),
190 }
191}
192
193fn native_package_to_query_package(package: InstalledRpmPackage) -> RpmQueryPackage {
194 RpmQueryPackage {
195 name: truncate_optional_string(Some(package.name)),
196 epoch: Some(package.epoch.to_string()),
197 version: truncate_optional_string(Some(package.version)),
198 release: truncate_optional_string(Some(package.release)),
199 vendor: truncate_optional_string(Some(package.vendor)),
200 distribution: truncate_optional_string(Some(package.distribution)),
201 arch: truncate_optional_string(Some(package.arch)),
202 platform: truncate_optional_string(Some(package.platform)),
203 size: (package.size > 0).then_some(u64::from(package.size)),
204 license: truncate_optional_string(Some(package.license)),
205 source_rpm: truncate_optional_string(Some(package.source_rpm)),
206 requires: package
207 .requires
208 .into_iter()
209 .take(MAX_ITERATION_COUNT)
210 .map(truncate_field)
211 .collect(),
212 file_names: package
213 .file_names
214 .into_iter()
215 .take(MAX_ITERATION_COUNT)
216 .map(|s| Some(truncate_field(s)))
217 .collect(),
218 dir_indexes: package.dir_indexes,
219 base_names: package
220 .base_names
221 .into_iter()
222 .take(MAX_ITERATION_COUNT)
223 .map(|s| Some(truncate_field(s)))
224 .collect(),
225 dir_names: package
226 .dir_names
227 .into_iter()
228 .take(MAX_ITERATION_COUNT)
229 .map(truncate_field)
230 .collect(),
231 }
232}
233
234fn truncate_optional_string(value: Option<String>) -> Option<String> {
235 value
236 .map(truncate_field)
237 .and_then(|v| normalize_optional_string(Some(v)))
238}
239
240fn build_evr_version(epoch: u32, version: &str, release: &str) -> Option<String> {
241 if version.is_empty() {
242 return None;
243 }
244
245 let mut evr = String::new();
246
247 if epoch > 0 {
248 evr.push_str(&format!("{}:", epoch));
249 }
250
251 evr.push_str(version);
252
253 if !release.is_empty() {
254 evr.push('-');
255 evr.push_str(release);
256 }
257
258 Some(evr)
259}
260
261fn build_file_references(
262 base_names: &[Option<String>],
263 dir_indexes: &[u32],
264 dir_names: &[String],
265) -> Vec<FileReference> {
266 if base_names.is_empty() || dir_names.is_empty() {
267 return Vec::new();
268 }
269
270 base_names
271 .iter()
272 .zip(dir_indexes.iter())
273 .take(MAX_ITERATION_COUNT)
274 .filter_map(|(basename, &dir_idx)| {
275 let dirname = dir_names.get(dir_idx as usize)?;
276 let basename = basename.as_deref().unwrap_or_default();
277 let path = format!("{}{}", dirname, basename);
278 if path.is_empty() || path == "/" {
279 return None;
280 }
281 Some(FileReference {
282 path,
283 size: None,
284 sha1: None,
285 md5: None,
286 sha256: None,
287 sha512: None,
288 extra_data: None,
289 })
290 })
291 .collect()
292}
293
294fn build_file_references_from_paths(paths: &[Option<String>]) -> Vec<FileReference> {
295 paths
296 .iter()
297 .take(MAX_ITERATION_COUNT)
298 .filter_map(|path| {
299 let path = path.as_deref()?.trim();
300 if path.is_empty() || path == "/" {
301 return None;
302 }
303
304 Some(FileReference {
305 path: path.to_string(),
306 size: None,
307 sha1: None,
308 md5: None,
309 sha256: None,
310 sha512: None,
311 extra_data: None,
312 })
313 })
314 .collect()
315}
316
317fn build_package_data(pkg: RpmQueryPackage, datasource_id: DatasourceId) -> PackageData {
318 let name = normalize_optional_string(pkg.name).map(truncate_field);
319 let version_raw = normalize_optional_string(pkg.version).map(truncate_field);
320 let release = normalize_optional_string(pkg.release).map(truncate_field);
321 let version = build_evr_version(
322 parse_epoch(pkg.epoch),
323 version_raw.as_deref().unwrap_or_default(),
324 release.as_deref().unwrap_or_default(),
325 );
326
327 let vendor = normalize_optional_string(pkg.vendor)
328 .map(truncate_field)
329 .or_else(|| normalize_optional_string(pkg.distribution).map(truncate_field));
330 let source_rpm = normalize_optional_string(pkg.source_rpm).map(truncate_field);
331 let namespace =
332 infer_rpm_namespace(None, vendor.as_deref(), release.as_deref(), None).or_else(|| {
333 source_rpm
334 .as_deref()
335 .and_then(|source_rpm| infer_rpm_namespace_from_filename(Path::new(source_rpm)))
336 });
337
338 let architecture = normalize_optional_string(pkg.arch)
339 .map(truncate_field)
340 .or_else(|| infer_platform_architecture(pkg.platform.as_deref()));
341 let dependencies = pkg
342 .requires
343 .into_iter()
344 .take(MAX_ITERATION_COUNT)
345 .filter_map(|require| build_dependency(&require))
346 .collect();
347 let extracted_license_statement = normalize_optional_string(pkg.license).map(truncate_field);
348 let source_packages = source_rpm.clone().into_iter().collect();
349 let file_references = {
350 let from_dir_components =
351 build_file_references(&pkg.base_names, &pkg.dir_indexes, &pkg.dir_names);
352 if from_dir_components.is_empty() {
353 build_file_references_from_paths(&pkg.file_names)
354 } else {
355 from_dir_components
356 }
357 };
358 let purl = build_package_purl(
359 name.as_deref(),
360 namespace.as_deref(),
361 version.as_deref(),
362 architecture.as_deref(),
363 );
364
365 PackageData {
366 datasource_id: Some(datasource_id),
367 package_type: Some(PACKAGE_TYPE),
368 namespace,
369 name,
370 version,
371 qualifiers: architecture.as_ref().map(|arch| {
372 let mut q = std::collections::HashMap::new();
373 q.insert("arch".to_string(), arch.clone());
374 q
375 }),
376 subpath: None,
377 primary_language: None,
378 description: None,
379 release_date: None,
380 parties: Vec::new(),
381 keywords: Vec::new(),
382 homepage_url: None,
383 download_url: None,
384 size: pkg.size.filter(|size| *size > 0),
385 sha1: None,
386 md5: None,
387 sha256: None,
388 sha512: None,
389 bug_tracking_url: None,
390 code_view_url: None,
391 vcs_url: None,
392 copyright: None,
393 holder: None,
394 declared_license_expression: None,
395 declared_license_expression_spdx: None,
396 license_detections: Vec::new(),
397 other_license_expression: None,
398 other_license_expression_spdx: None,
399 other_license_detections: Vec::new(),
400 extracted_license_statement,
401 notice_text: None,
402 source_packages,
403 file_references,
404 is_private: false,
405 is_virtual: false,
406 extra_data: None,
407 dependencies,
408 repository_homepage_url: None,
409 repository_download_url: None,
410 api_data_url: None,
411 purl,
412 }
413}
414
415fn build_dependency(require: &str) -> Option<Dependency> {
416 let require = require.trim();
417 if require.is_empty() || require.starts_with("rpmlib(") || require.starts_with("config(") {
418 return None;
419 }
420
421 let purl = packageurl::PackageUrl::new(PACKAGE_TYPE.as_str(), require)
422 .ok()
423 .map(|p| p.to_string());
424
425 Some(Dependency {
426 purl,
427 extracted_requirement: None,
428 scope: Some("requires".to_string()),
429 is_runtime: Some(true),
430 is_optional: Some(false),
431 is_pinned: Some(false),
432 is_direct: Some(true),
433 resolved_package: None,
434 extra_data: None,
435 })
436}
437
438fn build_package_purl(
439 name: Option<&str>,
440 namespace: Option<&str>,
441 version: Option<&str>,
442 arch: Option<&str>,
443) -> Option<String> {
444 let name = name?;
445 let mut purl = packageurl::PackageUrl::new(PACKAGE_TYPE.as_str(), name).ok()?;
446
447 if let Some(namespace) = namespace {
448 purl.with_namespace(namespace).ok()?;
449 }
450
451 if let Some(version) = version {
452 purl.with_version(version).ok()?;
453 }
454
455 if let Some(arch) = arch {
456 purl.add_qualifier("arch", arch).ok()?;
457 }
458
459 Some(purl.to_string())
460}
461
462fn normalize_optional_string(value: Option<String>) -> Option<String> {
463 value.and_then(|value| {
464 let trimmed = value.trim();
465 if trimmed.is_empty() || trimmed == "(none)" || trimmed == "[]" {
466 None
467 } else {
468 Some(trimmed.to_string())
469 }
470 })
471}
472
473fn parse_epoch(value: Option<String>) -> u32 {
474 normalize_optional_string(value)
475 .and_then(|value| value.parse::<u32>().ok())
476 .unwrap_or(0)
477}
478
479fn infer_platform_architecture(platform: Option<&str>) -> Option<String> {
480 let platform = platform?.trim();
481 if platform.is_empty() {
482 return None;
483 }
484
485 platform
486 .split_once('-')
487 .map(|(arch, _)| arch)
488 .filter(|arch| !arch.is_empty())
489 .map(|arch| arch.to_string())
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495
496 use crate::models::DatasourceId;
497 use std::path::PathBuf;
498
499 #[test]
500 fn test_bdb_parser_is_match() {
501 assert!(RpmBdbDatabaseParser::is_match(&PathBuf::from(
502 "/var/lib/rpm/Packages"
503 )));
504 assert!(RpmBdbDatabaseParser::is_match(&PathBuf::from(
505 "rootfs/var/lib/rpm/Packages"
506 )));
507 assert!(RpmBdbDatabaseParser::is_match(&PathBuf::from(
508 "/usr/lib/sysimage/rpm/Packages"
509 )));
510 assert!(!RpmBdbDatabaseParser::is_match(&PathBuf::from(
511 "/var/lib/rpm/Packages.db"
512 )));
513 assert!(!RpmBdbDatabaseParser::is_match(&PathBuf::from(
514 "lib/modules/datasource/deb/__fixtures__/Packages"
515 )));
516 assert!(!RpmBdbDatabaseParser::is_match(&PathBuf::from("Packages")));
517 assert!(!RpmBdbDatabaseParser::is_match(&PathBuf::from(
518 "testdata/rpm/var/lib/rpm/Packages.expected.json"
519 )));
520 }
521
522 #[test]
523 fn test_ndb_parser_is_match() {
524 assert!(RpmNdbDatabaseParser::is_match(&PathBuf::from(
525 "usr/lib/sysimage/rpm/Packages.db"
526 )));
527 assert!(RpmNdbDatabaseParser::is_match(&PathBuf::from(
528 "/rootfs/usr/lib/sysimage/rpm/Packages.db"
529 )));
530 assert!(!RpmNdbDatabaseParser::is_match(&PathBuf::from(
531 "usr/lib/rpm/Packages"
532 )));
533 assert!(RpmNdbDatabaseParser::is_match(&PathBuf::from(
534 "var/lib/rpm/Packages.db"
535 )));
536 assert!(!RpmNdbDatabaseParser::is_match(&PathBuf::from(
537 "testdata/rpm/usr/lib/sysimage/rpm/Packages.db.expected.json"
538 )));
539 }
540
541 #[cfg(feature = "rpm-sqlite")]
542 #[test]
543 fn test_sqlite_parser_is_match() {
544 assert!(RpmSqliteDatabaseParser::is_match(&PathBuf::from(
545 "var/lib/rpm/rpmdb.sqlite"
546 )));
547 assert!(RpmSqliteDatabaseParser::is_match(&PathBuf::from(
548 "/rootfs/var/lib/rpm/rpmdb.sqlite"
549 )));
550 assert!(RpmSqliteDatabaseParser::is_match(&PathBuf::from(
551 "/rootfs/usr/lib/sysimage/rpm/rpmdb.sqlite"
552 )));
553 assert!(!RpmSqliteDatabaseParser::is_match(&PathBuf::from(
554 "/var/lib/rpm/Packages"
555 )));
556 assert!(!RpmSqliteDatabaseParser::is_match(&PathBuf::from(
557 "testdata/rpm/rpmdb.sqlite.expected.json"
558 )));
559 assert!(!RpmSqliteDatabaseParser::is_match(&PathBuf::from(
560 "testdata/rpm/rpmdb.sqlite-shm"
561 )));
562 assert!(!RpmSqliteDatabaseParser::is_match(&PathBuf::from(
563 "testdata/rpm/rpmdb.sqlite-wal"
564 )));
565 }
566
567 #[test]
568 fn test_build_evr_version_full() {
569 assert_eq!(
570 build_evr_version(2, "1.0.0", "1.el7"),
571 Some("2:1.0.0-1.el7".to_string())
572 );
573 }
574
575 #[test]
576 fn test_build_evr_version_no_epoch() {
577 assert_eq!(
578 build_evr_version(0, "1.0.0", "1.el7"),
579 Some("1.0.0-1.el7".to_string())
580 );
581 }
582
583 #[test]
584 fn test_build_evr_version_no_release() {
585 assert_eq!(build_evr_version(0, "1.0.0", ""), Some("1.0.0".to_string()));
586 }
587
588 #[test]
589 fn test_build_evr_version_empty() {
590 assert_eq!(build_evr_version(0, "", ""), None);
591 }
592
593 #[cfg(feature = "rpm-sqlite")]
594 #[test]
595 fn test_parse_rpm_database_sqlite() {
596 let test_file = PathBuf::from("testdata/rpm/rpmdb.sqlite");
597
598 let pkg = RpmSqliteDatabaseParser::extract_first_package(&test_file);
599
600 assert_eq!(pkg.package_type, Some(PackageType::Rpm));
601 assert_eq!(
602 pkg.datasource_id,
603 Some(DatasourceId::RpmInstalledDatabaseSqlite)
604 );
605 assert!(pkg.name.is_some());
606 }
607
608 #[cfg(feature = "rpm-sqlite")]
609 #[test]
610 fn test_parse_rpm_database_sqlite_preserves_release_in_version() {
611 let test_file = PathBuf::from("testdata/rpm/rpmdb.sqlite");
612
613 let pkg = RpmSqliteDatabaseParser::extract_first_package(&test_file);
614
615 assert!(
616 pkg.version
617 .as_ref()
618 .is_some_and(|version| version.contains('-'))
619 );
620 }
621
622 #[test]
623 fn test_build_file_references_skips_invalid_entries() {
624 let file_refs = build_file_references(
625 &[
626 Some("valid".to_string()),
627 Some("".to_string()),
628 Some("ignored".to_string()),
629 ],
630 &[0, 0, u32::MAX],
631 &["/usr/bin/".to_string()],
632 );
633
634 assert_eq!(file_refs.len(), 2);
635 assert_eq!(file_refs[0].path, "/usr/bin/valid");
636 assert_eq!(file_refs[1].path, "/usr/bin/");
637 }
638
639 #[test]
640 fn test_build_package_data_falls_back_to_file_names() {
641 let package = build_package_data(
642 RpmQueryPackage {
643 name: Some("libgcc".to_string()),
644 epoch: None,
645 version: Some("13.1.1".to_string()),
646 release: Some("2.fc38".to_string()),
647 vendor: Some("Fedora Project".to_string()),
648 distribution: None,
649 arch: Some("x86_64".to_string()),
650 platform: None,
651 size: Some(235748),
652 license: Some("GPLv3+".to_string()),
653 source_rpm: Some("gcc-13.1.1-2.fc38.src.rpm".to_string()),
654 requires: Vec::new(),
655 file_names: vec![
656 Some("/usr/share/licenses/libgcc/COPYING".to_string()),
657 Some("/usr/share/licenses/libgcc/COPYING.RUNTIME".to_string()),
658 ],
659 dir_indexes: Vec::new(),
660 base_names: Vec::new(),
661 dir_names: Vec::new(),
662 },
663 DatasourceId::RpmInstalledDatabaseSqlite,
664 );
665
666 assert_eq!(package.file_references.len(), 2);
667 assert_eq!(
668 package.file_references[0].path,
669 "/usr/share/licenses/libgcc/COPYING"
670 );
671 assert_eq!(
672 package.file_references[1].path,
673 "/usr/share/licenses/libgcc/COPYING.RUNTIME"
674 );
675 }
676
677 #[test]
678 fn test_build_package_data_uses_distribution_for_namespace() {
679 let package = build_package_data(
680 RpmQueryPackage {
681 name: Some("libgcc".to_string()),
682 epoch: None,
683 version: Some("13.1.1".to_string()),
684 release: Some("2.fc38".to_string()),
685 vendor: None,
686 distribution: Some("Fedora Project".to_string()),
687 arch: Some("x86_64".to_string()),
688 platform: None,
689 size: Some(235748),
690 license: Some("GPLv3+".to_string()),
691 source_rpm: Some("gcc-13.1.1-2.fc38.src.rpm".to_string()),
692 requires: Vec::new(),
693 file_names: vec![Some("/usr/share/licenses/libgcc/COPYING".to_string())],
694 dir_indexes: Vec::new(),
695 base_names: Vec::new(),
696 dir_names: Vec::new(),
697 },
698 DatasourceId::RpmInstalledDatabaseSqlite,
699 );
700
701 assert_eq!(package.namespace.as_deref(), Some("fedora"));
702 }
703
704 #[test]
705 fn test_build_package_data_uses_source_rpm_for_namespace() {
706 let package = build_package_data(
707 RpmQueryPackage {
708 name: Some("libgcc".to_string()),
709 epoch: None,
710 version: Some("13.1.1".to_string()),
711 release: None,
712 vendor: None,
713 distribution: None,
714 arch: Some("x86_64".to_string()),
715 platform: None,
716 size: Some(235748),
717 license: Some("GPLv3+".to_string()),
718 source_rpm: Some("gcc-13.1.1-2.fc38.src.rpm".to_string()),
719 requires: Vec::new(),
720 file_names: vec![Some("/usr/share/licenses/libgcc/COPYING".to_string())],
721 dir_indexes: Vec::new(),
722 base_names: Vec::new(),
723 dir_names: Vec::new(),
724 },
725 DatasourceId::RpmInstalledDatabaseSqlite,
726 );
727
728 assert_eq!(package.namespace.as_deref(), Some("fedora"));
729 }
730
731 #[test]
732 fn test_build_package_data_uses_platform_for_architecture() {
733 let package = build_package_data(
734 RpmQueryPackage {
735 name: Some("libgcc".to_string()),
736 epoch: None,
737 version: Some("13.1.1".to_string()),
738 release: None,
739 vendor: None,
740 distribution: None,
741 arch: None,
742 platform: Some("x86_64-redhat-linux".to_string()),
743 size: Some(235748),
744 license: Some("GPLv3+".to_string()),
745 source_rpm: Some("gcc-13.1.1-2.fc38.src.rpm".to_string()),
746 requires: Vec::new(),
747 file_names: vec![Some("/usr/share/licenses/libgcc/COPYING".to_string())],
748 dir_indexes: Vec::new(),
749 base_names: Vec::new(),
750 dir_names: Vec::new(),
751 },
752 DatasourceId::RpmInstalledDatabaseSqlite,
753 );
754
755 assert_eq!(
756 package.qualifiers.as_ref().and_then(|q| q.get("arch")),
757 Some(&"x86_64".to_string())
758 );
759 }
760}
761
762#[cfg(feature = "rpm-sqlite")]
763crate::register_parser!(
764 "RPM installed package database",
765 &[
766 "**/var/lib/rpm/Packages",
767 "**/usr/lib/sysimage/rpm/Packages",
768 "**/var/lib/rpm/Packages.db",
769 "**/usr/lib/sysimage/rpm/Packages.db",
770 "**/var/lib/rpm/rpmdb.sqlite",
771 "**/usr/lib/sysimage/rpm/rpmdb.sqlite"
772 ],
773 "rpm",
774 "",
775 Some("https://rpm.org/"),
776);
777
778#[cfg(not(feature = "rpm-sqlite"))]
779crate::register_parser!(
780 "RPM installed package database",
781 &[
782 "**/var/lib/rpm/Packages",
783 "**/usr/lib/sysimage/rpm/Packages",
784 "**/var/lib/rpm/Packages.db",
785 "**/usr/lib/sysimage/rpm/Packages.db"
786 ],
787 "rpm",
788 "",
789 Some("https://rpm.org/"),
790);