os_info/linux/
lsb_release.rs

1// spell-checker:ignore codename, noarch, rhel, ootpa, maipo
2
3use std::process::Command;
4
5use log::{debug, trace};
6
7use crate::{matcher::Matcher, Info, Type, Version};
8
9pub fn get() -> Option<Info> {
10    let release = retrieve()?;
11
12    let version = match release.version.as_deref() {
13        Some("rolling") => Version::Rolling(None),
14        Some(v) => Version::from_string(v.to_owned()),
15        None => Version::Unknown,
16    };
17
18    let os_type = match release.distribution.as_ref().map(String::as_ref) {
19        Some("Alpaquita") => Type::Alpaquita,
20        Some("Amazon") | Some("AmazonAMI") => Type::Amazon,
21        Some("Arch") => Type::Arch,
22        Some("Artix") => Type::Artix,
23        Some("Bluefin") => Type::Bluefin,
24        Some("cachyos") => Type::CachyOS,
25        Some("CentOS") => Type::CentOS,
26        Some("Debian") => Type::Debian,
27        Some("EndeavourOS") => Type::EndeavourOS,
28        Some("Fedora") | Some("Fedora Linux") => Type::Fedora,
29        Some("Garuda") => Type::Garuda,
30        Some("Gentoo") => Type::Gentoo,
31        Some("Kali") => Type::Kali,
32        Some("Linuxmint") => Type::Mint,
33        Some("MaboxLinux") => Type::Mabox,
34        Some("ManjaroLinux") => Type::Manjaro,
35        Some("Mariner") => Type::Mariner,
36        Some("NixOS") => Type::NixOS,
37        Some("NobaraLinux") => Type::Nobara,
38        Some("Uos") => Type::Uos,
39        Some("OpenCloudOS") => Type::OpenCloudOS,
40        Some("openEuler") => Type::openEuler,
41        Some("openSUSE") => Type::openSUSE,
42        Some("OracleServer") => Type::OracleLinux,
43        Some("Pop") => Type::Pop,
44        Some("Raspbian") => Type::Raspbian,
45        Some("RedHatEnterprise") | Some("RedHatEnterpriseServer") => Type::RedHatEnterprise,
46        Some("Solus") => Type::Solus,
47        Some("SUSE") => Type::SUSE,
48        Some("Ubuntu") => Type::Ubuntu,
49        Some("UltramarineLinux") => Type::Ultramarine,
50        Some("VoidLinux") => Type::Void,
51        _ => Type::Linux,
52    };
53
54    Some(Info {
55        os_type,
56        version,
57        codename: release.codename,
58        ..Default::default()
59    })
60}
61
62struct LsbRelease {
63    pub distribution: Option<String>,
64    pub version: Option<String>,
65    pub codename: Option<String>,
66}
67
68fn retrieve() -> Option<LsbRelease> {
69    match Command::new("lsb_release").arg("-a").output() {
70        Ok(output) => {
71            trace!("lsb_release command returned {:?}", output);
72            Some(parse(&String::from_utf8_lossy(&output.stdout)))
73        }
74        Err(e) => {
75            debug!("lsb_release command failed with {:?}", e);
76            None
77        }
78    }
79}
80
81fn parse(output: &str) -> LsbRelease {
82    trace!("Trying to parse {:?}", output);
83
84    let distribution = Matcher::PrefixedWord {
85        prefix: "Distributor ID:",
86    }
87    .find(output);
88
89    let codename = Matcher::PrefixedWord {
90        prefix: "Codename:",
91    }
92    .find(output)
93    .filter(|c| c != "n/a");
94
95    let version = Matcher::PrefixedVersion { prefix: "Release:" }.find(output);
96
97    trace!(
98        "Parsed as '{:?}' distribution and '{:?}' version",
99        distribution,
100        version
101    );
102
103    LsbRelease {
104        distribution,
105        version,
106        codename,
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use pretty_assertions::assert_eq;
114
115    #[test]
116    fn debian() {
117        let parse_results = parse(file());
118        assert_eq!(parse_results.distribution, Some("Debian".to_string()));
119        assert_eq!(parse_results.version, Some("7.8".to_string()));
120        assert_eq!(parse_results.codename, Some("wheezy".to_string()));
121    }
122
123    #[test]
124    fn alpaquita() {
125        let parse_results = parse(alpaquita_file());
126        assert_eq!(parse_results.distribution, Some("Alpaquita".to_string()));
127        assert_eq!(parse_results.version, Some("23".to_string()));
128        assert_eq!(parse_results.codename, None);
129    }
130
131    #[test]
132    fn arch() {
133        let parse_results = parse(arch_file());
134        assert_eq!(parse_results.distribution, Some("Arch".to_string()));
135        assert_eq!(parse_results.version, Some("rolling".to_string()));
136        assert_eq!(parse_results.codename, None);
137    }
138
139    #[test]
140    fn artix() {
141        let parse_results = parse(artix_file());
142        assert_eq!(parse_results.distribution, Some("Artix".to_string()));
143        assert_eq!(parse_results.version, Some("rolling".to_string()));
144        assert_eq!(parse_results.codename, None);
145    }
146
147    #[test]
148    fn fedora() {
149        let parse_results = parse(fedora_file());
150        assert_eq!(parse_results.distribution, Some("Fedora".to_string()));
151        assert_eq!(parse_results.version, Some("26".to_string()));
152        assert_eq!(parse_results.codename, Some("TwentySix".to_string()));
153    }
154
155    #[test]
156    fn kali_2023_2() {
157        let parse_results = parse(kali_2023_2_file());
158        assert_eq!(parse_results.distribution, Some("Kali".to_string()));
159        assert_eq!(parse_results.version, Some("2023.2".to_string()));
160        assert_eq!(parse_results.codename, Some("kali-rolling".to_string()));
161    }
162
163    #[test]
164    fn ubuntu() {
165        let parse_results = parse(ubuntu_file());
166        assert_eq!(parse_results.distribution, Some("Ubuntu".to_string()));
167        assert_eq!(parse_results.version, Some("16.04".to_string()));
168        assert_eq!(parse_results.codename, Some("xenial".to_string()));
169    }
170
171    #[test]
172    fn mint() {
173        let parse_results = parse(mint_file());
174        assert_eq!(parse_results.distribution, Some("Linuxmint".to_string()));
175        assert_eq!(parse_results.version, Some("20".to_string()));
176        assert_eq!(parse_results.codename, Some("ulyana".to_string()));
177    }
178
179    #[test]
180    fn nixos() {
181        let parse_results = parse(nixos_file());
182        assert_eq!(parse_results.distribution, Some("NixOS".to_string()));
183        assert_eq!(
184            parse_results.version,
185            Some("21.05pre275822.916ee862e87".to_string())
186        );
187        assert_eq!(parse_results.codename, Some("okapi".to_string()));
188    }
189
190    #[test]
191    fn nobara() {
192        let parse_results = parse(nobara_file());
193        assert_eq!(parse_results.distribution, Some("NobaraLinux".to_string()));
194        assert_eq!(parse_results.version, Some("39".to_string()));
195        assert_eq!(parse_results.codename, None);
196    }
197
198    #[test]
199    fn uos() {
200        let parse_results = parse(uos_file());
201        assert_eq!(parse_results.distribution, Some("uos".to_string()));
202        assert_eq!(parse_results.version, Some("20".to_string()));
203        assert_eq!(parse_results.codename, Some("eagle".to_string()));
204    }
205
206    #[test]
207    fn amazon1() {
208        let parse_results = parse(amazon1_file());
209        assert_eq!(parse_results.distribution, Some("AmazonAMI".to_string()));
210        assert_eq!(parse_results.version, Some("2018.03".to_string()));
211        assert_eq!(parse_results.codename, None);
212    }
213
214    #[test]
215    fn amazon2() {
216        let parse_results = parse(amazon2_file());
217        assert_eq!(parse_results.distribution, Some("Amazon".to_string()));
218        assert_eq!(parse_results.version, Some("2".to_string()));
219        assert_eq!(parse_results.codename, Some("Karoo".to_string()));
220    }
221
222    #[test]
223    fn redhat_enterprise_8() {
224        let parse_results = parse(rhel8_file());
225        assert_eq!(
226            parse_results.distribution,
227            Some("RedHatEnterprise".to_string())
228        );
229        assert_eq!(parse_results.version, Some("8.1".to_string()));
230        assert_eq!(parse_results.codename, Some("Ootpa".to_string()));
231    }
232
233    #[test]
234    fn redhat_enterprise_7() {
235        let parse_results = parse(rhel7_file());
236        assert_eq!(
237            parse_results.distribution,
238            Some("RedHatEnterpriseServer".to_string())
239        );
240        assert_eq!(parse_results.version, Some("7.7".to_string()));
241        assert_eq!(parse_results.codename, Some("Maipo".to_string()));
242    }
243
244    #[test]
245    fn redhat_enterprise_6() {
246        let parse_results = parse(rhel6_file());
247        assert_eq!(
248            parse_results.distribution,
249            Some("RedHatEnterpriseServer".to_string())
250        );
251        assert_eq!(parse_results.version, Some("6.10".to_string()));
252        assert_eq!(parse_results.codename, Some("Santiago".to_string()));
253    }
254
255    #[test]
256    fn suse_enterprise_15_1() {
257        let parse_results = parse(suse_enterprise15_1_file());
258        assert_eq!(parse_results.distribution, Some("SUSE".to_string()));
259        assert_eq!(parse_results.version, Some("15.1".to_string()));
260        assert_eq!(parse_results.codename, None);
261    }
262
263    #[test]
264    fn suse_enterprise_12_5() {
265        let parse_results = parse(suse_enterprise12_5_file());
266        assert_eq!(parse_results.distribution, Some("SUSE".to_string()));
267        assert_eq!(parse_results.version, Some("12.5".to_string()));
268        assert_eq!(parse_results.codename, None);
269    }
270
271    #[test]
272    fn open_suse_15_1() {
273        let parse_results = parse(open_suse_15_1_file());
274        assert_eq!(parse_results.distribution, Some("openSUSE".to_string()));
275        assert_eq!(parse_results.version, Some("15.1".to_string()));
276        assert_eq!(parse_results.codename, None);
277    }
278
279    #[test]
280    fn oracle_linux_7_5() {
281        let parse_results = parse(oracle_server_linux_7_5_file());
282        assert_eq!(parse_results.distribution, Some("OracleServer".to_string()));
283        assert_eq!(parse_results.version, Some("7.5".to_string()));
284        assert_eq!(parse_results.codename, None);
285    }
286
287    #[test]
288    fn oracle_linux_8_1() {
289        let parse_results = parse(oracle_server_linux_8_1_file());
290        assert_eq!(parse_results.distribution, Some("OracleServer".to_string()));
291        assert_eq!(parse_results.version, Some("8.1".to_string()));
292        assert_eq!(parse_results.codename, None);
293    }
294
295    #[test]
296    fn pop_os_20_04_lts() {
297        let parse_results = parse(pop_os_20_04_lts_file());
298        assert_eq!(parse_results.distribution, Some("Pop".to_string()));
299        assert_eq!(parse_results.version, Some("20.04".to_string()));
300        assert_eq!(parse_results.codename, Some("focal".to_string()));
301    }
302
303    #[test]
304    fn solus_4_1() {
305        let parse_results = parse(solus_4_1_file());
306        assert_eq!(parse_results.distribution, Some("Solus".to_string()));
307        assert_eq!(parse_results.version, Some("4.1".to_string()));
308        assert_eq!(parse_results.codename, Some("fortitude".to_string()));
309    }
310
311    #[test]
312    fn manjaro() {
313        let parse_results = parse(manjaro_19_0_2_file());
314        assert_eq!(parse_results.distribution, Some("ManjaroLinux".to_string()));
315        assert_eq!(parse_results.version, Some("19.0.2".to_string()));
316        assert_eq!(parse_results.codename, None);
317    }
318
319    #[test]
320    fn mariner() {
321        let parse_results = parse(mariner_file());
322        assert_eq!(parse_results.distribution, Some("Mariner".to_string()));
323        assert_eq!(parse_results.version, Some("2.0.20220210".to_string()));
324        assert_eq!(parse_results.codename, Some("Mariner".to_string()));
325    }
326
327    #[test]
328    fn endeavouros() {
329        let parse_results = parse(endeavouros_file());
330        assert_eq!(parse_results.distribution, Some("EndeavourOS".to_string()));
331        assert_eq!(parse_results.version, Some("rolling".to_string()));
332        assert_eq!(parse_results.codename, None);
333    }
334
335    #[test]
336    fn ultramarine() {
337        let parse_results = parse(ultramarine_file());
338        assert_eq!(
339            parse_results.distribution,
340            Some("UltramarineLinux".to_string())
341        );
342        assert_eq!(parse_results.version, Some("39".to_string()));
343        assert_eq!(parse_results.codename, Some("kuma".to_string()));
344    }
345
346    #[test]
347    fn void_linux() {
348        let parse_results = parse(void_file());
349        assert_eq!(parse_results.distribution, Some("Void".to_string()));
350        assert_eq!(parse_results.version, Some("rolling".to_string()));
351    }
352
353    #[test]
354    fn raspbian() {
355        let parse_results = parse(raspberry_os_file());
356        assert_eq!(parse_results.distribution, Some("Raspbian".to_string()));
357        assert_eq!(parse_results.version, Some("10".to_string()));
358        assert_eq!(parse_results.codename, None);
359    }
360
361    #[test]
362    fn cachyos() {
363        let parse_results = parse(cachyos_file());
364        assert_eq!(parse_results.distribution, Some("cachyos".to_string()));
365        assert_eq!(parse_results.version, Some("rolling".to_string()));
366    }
367
368    fn file() -> &'static str {
369        "\nDistributor ID:	Debian\n\
370         Description:	Debian GNU/Linux 7.8 (wheezy)\n\
371         Release:	7.8\n\
372         Codename:	wheezy\n\
373         "
374    }
375
376    fn alpaquita_file() -> &'static str {
377        "\nDistributor ID: Alpaquita\n\
378        Description:    BellSoft Alpaquita Linux Stream 23 (musl)\n\
379        Release:        23\n\
380        Codename:       n/a"
381    }
382
383    fn arch_file() -> &'static str {
384        "\nLSB Version:	1.4\n\
385         Distributor ID:	Arch\n\
386         Description:	Arch Linux\n\
387         Release:	rolling\n\
388         Codename:	n/a"
389    }
390
391    fn artix_file() -> &'static str {
392        "\nLSB Version:	n/a\n\
393         Distributor ID:	Artix\n\
394         Description:	Artix Linux\n\
395         Release:	rolling\n\
396         Codename:	n/a"
397    }
398
399    fn fedora_file() -> &'static str {
400        "\nLSB Version:    :core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch\n\
401         Distributor ID: Fedora\n\
402         Description:    Fedora release 26 (Twenty Six)\n\
403         Release:    26\n\
404         Codename:   TwentySix\n\
405         "
406    }
407
408    fn kali_2023_2_file() -> &'static str {
409        "\nDistributor ID: Kali\n\
410         Description:    Kali GNU/Linux Rolling\n\
411         Release:        2023.2\n\
412         Codename:       kali-rolling\n\
413         "
414    }
415
416    fn ubuntu_file() -> &'static str {
417        "Distributor ID: Ubuntu\n\
418         Description:    Ubuntu 16.04.5 LTS\n\
419         Release:        16.04\n\
420         Codename:       xenial"
421    }
422
423    fn mint_file() -> &'static str {
424        "Distributor ID:	Linuxmint\n\
425         Description:	    Linux Mint 20\n\
426         Release:	        20\n\
427         Codename:	        ulyana"
428    }
429
430    fn nixos_file() -> &'static str {
431        "Distributor ID: NixOS\n\
432         Description:    NixOS 21.05 (Okapi)\n\
433         Release:        21.05pre275822.916ee862e87\n\
434         Codename:       okapi"
435    }
436
437    fn nobara_file() -> &'static str {
438        "LSB Version:	n/a\n\
439        Distributor ID:	NobaraLinux\n\
440        Description:	Nobara Linux 39 (KDE Plasma)\n\
441        Release:	39\n\
442        Codename:	n/a\n\
443        "
444    }
445
446    fn uos_file() -> &'static str {
447        "Distributor ID: uos\n\
448         Description:	 UnionTech OS 20\n\
449         Release:	     20\n\
450         Codename:	     eagle\n\
451         "
452    }
453
454    // Amazon Linux 1 uses a separate Distributor ID and Release format from Amazon Linux 2
455    fn amazon1_file() -> &'static str {
456        "LSB Version:	:base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch\n\
457         Distributor ID:	AmazonAMI\n\
458         Description:	Amazon Linux AMI release 2018.03\n\
459         Release:	2018.03\n\
460         Codename:	n/a\n\
461         "
462    }
463
464    // Amazon Linux 2 uses a separate Distributor ID and Release format from Amazon Linux 1
465    fn amazon2_file() -> &'static str {
466        "LSB Version:	:core-4.1-amd64:core-4.1-noarch\n\
467         Distributor ID:	Amazon\n\
468         Description:	Amazon Linux release 2 (Karoo)\n\
469         Release:	2\n\
470         Codename:	Karoo\n\
471         "
472    }
473
474    fn rhel8_file() -> &'static str {
475        "LSB Version:	:core-4.1-amd64:core-4.1-noarch\n\
476         Distributor ID:	RedHatEnterprise\n\
477         Description:	Red Hat Enterprise Linux release 8.1 (Ootpa)\n\
478         Release:	8.1\n\
479         Codename:	Ootpa\n\
480         "
481    }
482
483    fn rhel7_file() -> &'static str {
484        "LSB Version:	:core-4.1-amd64:core-4.1-noarch\n\
485         Distributor ID:	RedHatEnterpriseServer\n\
486         Description:	Red Hat Enterprise Linux Server release 7.7 (Maipo)\n\
487         Release:	7.7\n\
488         Codename:	Maipo\n\
489         "
490    }
491
492    fn rhel6_file() -> &'static str {
493        "LSB Version:	:base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch\n\
494        Distributor ID:	RedHatEnterpriseServer\n\
495        Description:	Red Hat Enterprise Linux Server release 6.10 (Santiago)\n\
496        Release:	6.10\n\
497        Codename:	Santiago\n\
498        "
499    }
500
501    fn suse_enterprise15_1_file() -> &'static str {
502        "LSB Version:	n/a\n\
503        Distributor ID:	SUSE\n\
504        Description:	SUSE Linux Enterprise Server 15 SP1\n\
505        Release:	15.1\n\
506        Codename:	n/a\n\
507        "
508    }
509
510    fn suse_enterprise12_5_file() -> &'static str {
511        "LSB Version:	n/a\n\
512        Distributor ID:	SUSE\n\
513        Description:	SUSE Linux Enterprise Server 12 SP5\n\
514        Release:	12.5\n\
515        Codename:	n/a\n\
516        "
517    }
518
519    fn raspberry_os_file() -> &'static str {
520        "LSB Version:   n/a\n\
521         Distributor ID: Raspbian\n\
522         Description:    Raspbian GNU/Linux 10 (buster)\n\
523         Release:        10\n\
524         Codename:       n/a\n\
525         "
526    }
527
528    fn open_suse_15_1_file() -> &'static str {
529        "LSB Version:	n/a\n\
530        Distributor ID:	openSUSE\n\
531        Description:	openSUSE Leap 15.1\n\
532        Release:	15.1\n\
533        Codename:	n/a\n\
534        "
535    }
536
537    fn oracle_server_linux_7_5_file() -> &'static str {
538        "LSB Version:	:core-4.1-amd64:core-4.1-noarch\n\
539        Distributor ID:	OracleServer\n\
540        Description:	Oracle Linux Server release 7.5\n\
541        Release:	7.5\n\
542        Codename:	n/a\n\
543        "
544    }
545
546    fn oracle_server_linux_8_1_file() -> &'static str {
547        "LSB Version:	:core-4.1-amd64:core-4.1-noarch\n\
548        Distributor ID:	OracleServer\n\
549        Description:	Oracle Linux Server release 8.1\n\
550        Release:	8.1\n\
551        Codename:	n/a\n\
552        "
553    }
554
555    fn pop_os_20_04_lts_file() -> &'static str {
556        "No LSB modules are available.\n\
557        Distributor ID: Pop\n\
558        Description: Pop!_OS 20.04 LTS\n\
559        Release: 20.04\n\
560        Codename: focal\n\
561        "
562    }
563
564    fn solus_4_1_file() -> &'static str {
565        "LSB Version:	1.4\n\
566        Distributor ID:	Solus\n\
567        Description:	Solus\n\
568        Release:	4.1\n\
569        Codename:	fortitude\n\
570        "
571    }
572
573    fn manjaro_19_0_2_file() -> &'static str {
574        "LSB Version:    n/a\n\
575        Distributor ID: ManjaroLinux\n\
576        Description:    Manjaro Linux\n\
577        Release:        19.0.2\n\
578        Codename:       n/a\n\
579        "
580    }
581
582    fn mariner_file() -> &'static str {
583        "LSB Version:    n/a\n\
584        Distributor ID: Mariner\n\
585        Description:    CBL-Mariner 2.0.20220210\n\
586        Release:        2.0.20220210\n\
587        Codename:       Mariner\n\
588        "
589    }
590
591    fn endeavouros_file() -> &'static str {
592        "LSB Version:	1.4\n\
593        Distributor ID:	EndeavourOS\n\
594        Description:	EndeavourOS Linux\n\
595        Release:	rolling\n\
596        Codename:	n/a\n\
597        "
598    }
599
600    fn ultramarine_file() -> &'static str {
601        "LSB Version:    n/a\n\
602        Distributor ID: UltramarineLinux\n\
603        Description:    Ultramarine Linux 39 (Kuma)\n\
604        Release:        39\n\
605        Codename:       kuma\n\
606        "
607    }
608
609    fn void_file() -> &'static str {
610        "LSB Version:        n/a\n\
611          Distributor ID:        Void\n\
612          Description:        Void Linux\n\
613          Release:        rolling\n\
614          Codename:        n/a\n\
615          "
616    }
617
618    fn cachyos_file() -> &'static str {
619        "Distributor ID: cachyos\n\
620         Description:	 CachyOS\n\
621         Release:	     rolling\n\
622         Codename:	     n/a\n\
623         "
624    }
625}