1use 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 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 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}