1use std::{fmt, fs::File, io::Read, path::Path};
4
5use log::{trace, warn};
6
7use crate::{matcher::Matcher, Bitness, Info, Type, Version};
8
9pub fn get() -> Option<Info> {
10 retrieve(&DISTRIBUTIONS, "/")
11}
12
13fn retrieve(distributions: &[ReleaseInfo], root: &str) -> Option<Info> {
14 for release_info in distributions {
15 let path = Path::new(root).join(release_info.path);
16
17 if !path.exists() {
18 trace!("Path '{}' doesn't exist", release_info.path);
19 continue;
20 }
21
22 let mut file = match File::open(&path) {
23 Ok(val) => val,
24 Err(e) => {
25 warn!("Unable to open {:?} file: {:?}", &path, e);
26 continue;
27 }
28 };
29
30 let mut file_content = String::new();
31 if let Err(e) = file.read_to_string(&mut file_content) {
32 warn!("Unable to read {:?} file: {:?}", &path, e);
33 continue;
34 }
35
36 let os_type = (release_info.os_type)(&file_content);
37
38 if os_type.is_none() {
40 continue;
41 }
42
43 let version = (release_info.version)(&file_content);
44
45 return Some(Info {
46 os_type: os_type.unwrap(),
48 version: version.unwrap_or(Version::Unknown),
49 bitness: Bitness::Unknown,
50 ..Default::default()
51 });
52 }
53
54 None
56}
57
58#[derive(Clone)]
60struct ReleaseInfo<'a> {
61 path: &'a str,
63
64 os_type: for<'b> fn(&'b str) -> Option<Type>,
66
67 version: for<'b> fn(&'b str) -> Option<Version>,
69}
70
71impl fmt::Debug for ReleaseInfo<'_> {
72 fn fmt<'a>(&'a self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
73 f.debug_struct("ReleaseInfo")
74 .field("path", &self.path)
75 .field("os_type", &(self.os_type as fn(&'a str) -> Option<Type>))
76 .field("version", &(self.version as fn(&'a str) -> Option<Version>))
77 .finish()
78 }
79}
80
81static DISTRIBUTIONS: [ReleaseInfo; 6] = [
84 ReleaseInfo {
86 path: "etc/os-release",
87 os_type: |release| {
88 Matcher::KeyValue { key: "ID" }
89 .find(release)
90 .and_then(|id| match id.as_str() {
91 "almalinux" => Some(Type::AlmaLinux),
94 "alpaquita" => Some(Type::Alpaquita),
95 "alpine" => Some(Type::Alpine),
96 "amzn" => Some(Type::Amazon),
97 "arch" => Some(Type::Arch),
100 "archarm" => Some(Type::Arch),
101 "artix" => Some(Type::Artix),
102 "bluefin" => Some(Type::Bluefin),
103 "cachyos" => Some(Type::CachyOS),
104 "centos" => Some(Type::CentOS),
105 "debian" => Some(Type::Debian),
110 "fedora" => Some(Type::Fedora),
113 "kali" => Some(Type::Kali),
116 "linuxmint" => Some(Type::Mint),
119 "mariner" => Some(Type::Mariner),
120 "nixos" => Some(Type::NixOS),
122 "nobara" => Some(Type::Nobara),
123 "Uos" => Some(Type::Uos),
124 "opencloudos" => Some(Type::OpenCloudOS),
125 "openEuler" => Some(Type::openEuler),
126 "ol" => Some(Type::OracleLinux),
127 "opensuse" => Some(Type::openSUSE),
128 "opensuse-leap" => Some(Type::openSUSE),
129 "opensuse-microos" => Some(Type::openSUSE),
130 "opensuse-tumbleweed" => Some(Type::openSUSE),
131 "rhel" => Some(Type::RedHatEnterprise),
135 "rocky" => Some(Type::RockyLinux),
136 "sled" => Some(Type::SUSE), "sles" => Some(Type::SUSE),
141 "sles_sap" => Some(Type::SUSE), "ubuntu" => Some(Type::Ubuntu),
143 "ultramarine" => Some(Type::Ultramarine),
144 "void" => Some(Type::Void),
146 _ => None,
150 })
151 },
152 version: |release| {
153 Matcher::KeyValue { key: "VERSION_ID" }
154 .find(release)
155 .map(Version::from_string)
156 },
157 },
158 ReleaseInfo {
160 path: "etc/mariner-release",
161 os_type: |_| Some(Type::Mariner),
162 version: |release| {
163 Matcher::PrefixedVersion {
164 prefix: "CBL-Mariner",
165 }
166 .find(release)
167 .map(Version::from_string)
168 },
169 },
170 ReleaseInfo {
171 path: "etc/centos-release",
172 os_type: |_| Some(Type::CentOS),
173 version: |release| {
174 Matcher::PrefixedVersion { prefix: "release" }
175 .find(release)
176 .map(Version::from_string)
177 },
178 },
179 ReleaseInfo {
180 path: "etc/fedora-release",
181 os_type: |_| Some(Type::Fedora),
182 version: |release| {
183 Matcher::PrefixedVersion { prefix: "release" }
184 .find(release)
185 .map(Version::from_string)
186 },
187 },
188 ReleaseInfo {
189 path: "etc/alpine-release",
190 os_type: |_| Some(Type::Alpine),
191 version: |release| Matcher::AllTrimmed.find(release).map(Version::from_string),
192 },
193 ReleaseInfo {
194 path: "etc/redhat-release",
195 os_type: |_| Some(Type::RedHatEnterprise),
196 version: |release| {
197 Matcher::PrefixedVersion { prefix: "release" }
198 .find(release)
199 .map(Version::from_string)
200 },
201 },
202];
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use pretty_assertions::assert_eq;
208
209 #[test]
210 fn almalinux_9_0_release() {
211 let root = "src/linux/tests/AlmaLinux-9.0";
212
213 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
214 assert_eq!(info.os_type(), Type::AlmaLinux);
215 assert_eq!(info.version, Version::Semantic(9, 0, 0));
216 assert_eq!(info.edition, None);
217 assert_eq!(info.codename, None);
218 }
219
220 #[test]
221 fn alpaquita_os_release() {
222 let root = "src/linux/tests/Alpaquita";
223
224 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
225 assert_eq!(info.os_type(), Type::Alpaquita);
226 assert_eq!(info.version, Version::Semantic(23, 0, 0));
227 assert_eq!(info.edition, None);
228 assert_eq!(info.codename, None);
229 }
230
231 #[test]
232 fn alpine_3_12_os_release() {
233 let root = "src/linux/tests/Alpine_3_12";
234
235 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
236 assert_eq!(info.os_type(), Type::Alpine);
237 assert_eq!(info.version, Version::Semantic(3, 12, 0));
238 assert_eq!(info.edition, None);
239 assert_eq!(info.codename, None);
240 }
241
242 #[test]
243 fn alpine_release() {
244 let root = "src/linux/tests/Alpine";
245
246 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
247 assert_eq!(info.os_type(), Type::Alpine);
248 assert_eq!(info.version, Version::Custom("A.B.C".to_owned()));
249 assert_eq!(info.edition, None);
250 assert_eq!(info.codename, None);
251 }
252
253 #[test]
254 fn amazon_1_os_release() {
255 let root = "src/linux/tests/Amazon_1";
256
257 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
258 assert_eq!(info.os_type(), Type::Amazon);
259 assert_eq!(info.version, Version::Semantic(2018, 3, 0));
260 assert_eq!(info.edition, None);
261 assert_eq!(info.codename, None);
262 }
263
264 #[test]
265 fn amazon_2_os_release() {
266 let root = "src/linux/tests/Amazon_2";
267
268 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
269 assert_eq!(info.os_type(), Type::Amazon);
270 assert_eq!(info.version, Version::Semantic(2, 0, 0));
271 assert_eq!(info.edition, None);
272 assert_eq!(info.codename, None);
273 }
274
275 #[test]
276 fn arch_os_release() {
277 let root = "src/linux/tests/Arch";
278
279 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
280 assert_eq!(info.os_type(), Type::Arch);
281 assert_eq!(info.version, Version::Unknown);
282 assert_eq!(info.edition, None);
283 assert_eq!(info.codename, None);
284 }
285
286 #[test]
287 fn archarm_os_release() {
288 let root = "src/linux/tests/ArchARM";
289
290 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
291 assert_eq!(info.os_type(), Type::Arch);
292 assert_eq!(info.version, Version::Unknown);
293 assert_eq!(info.edition, None);
294 assert_eq!(info.codename, None);
295 }
296
297 #[test]
298 fn artix_os_release() {
299 let root = "src/linux/tests/Artix";
300
301 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
302 assert_eq!(info.os_type(), Type::Artix);
303 assert_eq!(info.version, Version::Unknown);
304 assert_eq!(info.edition, None);
305 assert_eq!(info.codename, None);
306 }
307
308 #[test]
309 fn bluefin_os_release() {
310 let root = "src/linux/tests/Bluefin";
311
312 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
313 assert_eq!(info.os_type(), Type::Bluefin);
314 assert_eq!(info.version, Version::Semantic(41, 0, 0));
315 assert_eq!(info.edition, None);
316 assert_eq!(info.codename, None);
317 }
318
319 #[test]
320 fn centos_7_os_release() {
321 let root = "src/linux/tests/CentOS_7";
322
323 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
324 assert_eq!(info.os_type(), Type::CentOS);
325 assert_eq!(info.version, Version::Semantic(7, 0, 0));
326 assert_eq!(info.edition, None);
327 assert_eq!(info.codename, None);
328 }
329
330 #[test]
331 fn centos_stream_os_release() {
332 let root = "src/linux/tests/CentOS_Stream";
333
334 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
335 assert_eq!(info.os_type(), Type::CentOS);
336 assert_eq!(info.version, Version::Semantic(8, 0, 0));
337 assert_eq!(info.edition, None);
338 assert_eq!(info.codename, None);
339 }
340
341 #[test]
342 fn centos_release() {
343 let root = "src/linux/tests/CentOS";
344
345 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
346 assert_eq!(info.os_type(), Type::CentOS);
347 assert_eq!(info.version, Version::Custom("XX".to_owned()));
348 assert_eq!(info.edition, None);
349 assert_eq!(info.codename, None);
350 }
351
352 #[test]
353 fn centos_release_unknown() {
354 let root = "src/linux/tests/CentOS_Unknown";
355
356 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
357 assert_eq!(info.os_type(), Type::CentOS);
358 assert_eq!(info.version, Version::Unknown);
359 assert_eq!(info.edition, None);
360 assert_eq!(info.codename, None);
361 }
362
363 #[test]
364 fn debian_11_os_release() {
365 let root = "src/linux/tests/Debian_11";
366
367 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
368 assert_eq!(info.os_type(), Type::Debian);
369 assert_eq!(info.version, Version::Semantic(11, 0, 0));
370 assert_eq!(info.edition, None);
371 assert_eq!(info.codename, None);
372 }
373
374 #[test]
375 fn fedora_32_os_release() {
376 let root = "src/linux/tests/Fedora_32";
377
378 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
379 assert_eq!(info.os_type(), Type::Fedora);
380 assert_eq!(info.version, Version::Semantic(32, 0, 0));
381 assert_eq!(info.edition, None);
382 assert_eq!(info.codename, None);
383 }
384
385 #[test]
386 fn fedora_35_os_release() {
387 let root = "src/linux/tests/Fedora_35";
388
389 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
390 assert_eq!(info.os_type(), Type::Fedora);
391 assert_eq!(info.version, Version::Semantic(35, 0, 0));
392 assert_eq!(info.edition, None);
393 assert_eq!(info.codename, None);
394 }
395
396 #[test]
397 fn fedora_release() {
398 let root = "src/linux/tests/Fedora";
399
400 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
401 assert_eq!(info.os_type(), Type::Fedora);
402 assert_eq!(info.version, Version::Semantic(26, 0, 0));
403 assert_eq!(info.edition, None);
404 assert_eq!(info.codename, None);
405 }
406
407 #[test]
408 fn fedora_release_unknown() {
409 let root = "src/linux/tests/Fedora_Unknown";
410
411 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
412 assert_eq!(info.os_type(), Type::Fedora);
413 assert_eq!(info.version, Version::Unknown);
414 assert_eq!(info.edition, None);
415 assert_eq!(info.codename, None);
416 }
417
418 #[test]
419 fn kali_2023_2_os_release() {
420 let root = "src/linux/tests/Kali_2023_2";
421
422 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
423 assert_eq!(info.os_type(), Type::Kali);
424 assert_eq!(info.version, Version::Semantic(2023, 2, 0));
425 assert_eq!(info.edition, None);
426 assert_eq!(info.codename, None);
427 }
428
429 #[test]
430 fn mariner_release() {
431 let root = "src/linux/tests/Mariner";
432
433 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
434 assert_eq!(info.os_type(), Type::Mariner);
435 assert_eq!(info.version, Version::Semantic(2, 0, 20220210));
436 assert_eq!(info.edition, None);
437 assert_eq!(info.codename, None);
438 }
439
440 #[test]
441 fn mariner_release_unknown() {
442 let root = "src/linux/tests/Mariner_Unknown";
443
444 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
445 assert_eq!(info.os_type(), Type::Mariner);
446 assert_eq!(info.version, Version::Unknown);
447 assert_eq!(info.edition, None);
448 assert_eq!(info.codename, None);
449 }
450
451 #[test]
452 fn mint_os_release() {
453 let root = "src/linux/tests/Mint";
454
455 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
456 assert_eq!(info.os_type(), Type::Mint);
457 assert_eq!(info.version, Version::Semantic(20, 0, 0));
458 assert_eq!(info.edition, None);
459 assert_eq!(info.codename, None);
460 }
461
462 #[test]
463 fn nixos_os_release() {
464 let root = "src/linux/tests/NixOS";
465
466 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
467 assert_eq!(info.os_type(), Type::NixOS);
468 assert_eq!(
469 info.version,
470 Version::Custom("21.05pre275822.916ee862e87".to_string())
471 );
472 assert_eq!(info.edition, None);
473 assert_eq!(info.codename, None);
474 }
475
476 #[test]
477 fn nobara_os_release() {
478 let root = "src/linux/tests/Nobara";
479
480 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
481 assert_eq!(info.os_type(), Type::Nobara);
482 assert_eq!(info.version, Version::Semantic(39, 0, 0));
483 assert_eq!(info.edition, None);
484 assert_eq!(info.codename, None);
485 }
486
487 #[test]
488 fn uos_os_release() {
489 let root = "src/linux/tests/Uos";
490
491 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
492 assert_eq!(info.os_type(), Type::Uos);
493 assert_eq!(info.version, Version::Semantic(20, 0, 0));
494 assert_eq!(info.edition, None);
495 assert_eq!(info.codename, None);
496 }
497
498 #[test]
499 fn none_invalid_os_release() {
500 let root = "src/linux/tests/none_invalid_os_release";
501
502 let info = retrieve(&DISTRIBUTIONS, root);
503 assert_eq!(info, None);
504 }
505
506 #[test]
507 fn none_no_release() {
508 let root = "src/linux/tests/none_no_release";
509
510 let info = retrieve(&DISTRIBUTIONS, root);
511 assert_eq!(info, None);
512 }
513
514 #[test]
515 fn none_no_path() {
516 let root = "src/linux/tests/none_no_path";
517
518 let info = retrieve(&DISTRIBUTIONS, root);
519 assert_eq!(info, None);
520 }
521
522 #[test]
523 fn opencloudos_os_release() {
524 let root = "src/linux/tests/OpenCloudOS";
525
526 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
527 assert_eq!(info.os_type(), Type::OpenCloudOS);
528 assert_eq!(info.version, Version::Semantic(8, 6, 0));
529 assert_eq!(info.edition, None);
530 assert_eq!(info.codename, None);
531 }
532
533 #[test]
534 fn openeuler_os_release() {
535 let root = "src/linux/tests/openEuler";
536
537 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
538 assert_eq!(info.os_type(), Type::openEuler);
539 assert_eq!(info.version, Version::Semantic(22, 3, 0));
540 assert_eq!(info.edition, None);
541 assert_eq!(info.codename, None);
542 }
543
544 #[test]
545 fn opensuse_tumbleweed_os_release() {
546 let root = "src/linux/tests/openSUSE_Tumbleweed";
547
548 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
549 assert_eq!(info.os_type(), Type::openSUSE);
550 assert_eq!(info.version, Version::Semantic(20230816, 0, 0));
551 assert_eq!(info.edition, None);
552 assert_eq!(info.codename, None);
553 }
554
555 #[test]
556 fn oracle_linux_os_release() {
557 let root = "src/linux/tests/OracleLinux";
558
559 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
560 assert_eq!(info.os_type(), Type::OracleLinux);
561 assert_eq!(info.version, Version::Semantic(8, 1, 0));
562 assert_eq!(info.edition, None);
563 assert_eq!(info.codename, None);
564 }
565
566 #[test]
567 fn rhel_8_os_release() {
568 let root = "src/linux/tests/RedHatEnterprise_8";
569
570 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
571 assert_eq!(info.os_type(), Type::RedHatEnterprise);
572 assert_eq!(info.version, Version::Semantic(8, 2, 0));
573 assert_eq!(info.edition, None);
574 assert_eq!(info.codename, None);
575 }
576
577 #[test]
578 fn rhel_7_os_release() {
579 let root = "src/linux/tests/RedHatEnterprise_7";
580
581 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
582 assert_eq!(info.os_type(), Type::RedHatEnterprise);
583 assert_eq!(info.version, Version::Semantic(7, 9, 0));
584 assert_eq!(info.edition, None);
585 assert_eq!(info.codename, None);
586 }
587
588 #[test]
589 fn redhat_release() {
590 let root = "src/linux/tests/RedHatEnterprise";
591
592 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
593 assert_eq!(info.os_type(), Type::RedHatEnterprise);
594 assert_eq!(info.version, Version::Custom("XX".to_owned()));
595 assert_eq!(info.edition, None);
596 assert_eq!(info.codename, None);
597 }
598
599 #[test]
600 fn redhat_release_unknown() {
601 let root = "src/linux/tests/RedHatEnterprise_Unknown";
602
603 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
604 assert_eq!(info.os_type(), Type::RedHatEnterprise);
605 assert_eq!(info.version, Version::Unknown);
606 assert_eq!(info.edition, None);
607 assert_eq!(info.codename, None);
608 }
609
610 #[test]
611 fn rocky_9_2_release() {
612 let root = "src/linux/tests/RockyLinux-9.2";
613
614 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
615 assert_eq!(info.os_type(), Type::RockyLinux);
616 assert_eq!(info.version, Version::Semantic(9, 2, 0));
617 assert_eq!(info.edition, None);
618 assert_eq!(info.codename, None);
619 }
620
621 #[test]
622 fn suse_12_os_release() {
623 let root = "src/linux/tests/SUSE_12";
624
625 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
626 assert_eq!(info.os_type(), Type::SUSE);
627 assert_eq!(info.version, Version::Semantic(12, 5, 0));
628 assert_eq!(info.edition, None);
629 assert_eq!(info.codename, None);
630 }
631
632 #[test]
633 fn suse_15_os_release() {
634 let root = "src/linux/tests/SUSE_15";
635
636 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
637 assert_eq!(info.os_type(), Type::SUSE);
638 assert_eq!(info.version, Version::Semantic(15, 2, 0));
639 assert_eq!(info.edition, None);
640 assert_eq!(info.codename, None);
641 }
642
643 #[test]
644 fn ubuntu_os_release() {
645 let root = "src/linux/tests/Ubuntu";
646
647 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
648 assert_eq!(info.os_type(), Type::Ubuntu);
649 assert_eq!(info.version, Version::Semantic(18, 10, 0));
650 assert_eq!(info.edition, None);
651 assert_eq!(info.codename, None);
652 }
653
654 #[test]
655 fn ultramarine_os_release() {
656 let root = "src/linux/tests/Ultramarine";
657
658 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
659 assert_eq!(info.os_type(), Type::Ultramarine);
660 assert_eq!(info.version, Version::Semantic(39, 0, 0));
661 assert_eq!(info.edition, None);
662 assert_eq!(info.codename, None);
663 }
664
665 #[test]
666 fn void_os_release() {
667 let root = "src/linux/tests/Void";
668
669 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
670 assert_eq!(info.os_type(), Type::Void);
671 assert_eq!(info.version, Version::Unknown);
672 assert_eq!(info.edition, None);
673 assert_eq!(info.codename, None);
674 }
675
676 #[test]
677 fn cachy_os_release() {
678 let root = "src/linux/tests/CachyOS";
679
680 let info = retrieve(&DISTRIBUTIONS, root).unwrap();
681 assert_eq!(info.os_type(), Type::CachyOS);
682 assert_eq!(info.version, Version::Unknown);
683 assert_eq!(info.edition, None);
684 assert_eq!(info.codename, None);
685 }
686
687 #[test]
688 fn release_info_debug() {
689 dbg!("{:?}", &DISTRIBUTIONS[0]);
690 }
691}