1use crate::sys::cpu::{CpusWrapper, get_physical_core_count};
4use crate::sys::process::{compute_cpu_usage, refresh_procs};
5use crate::sys::utils::{get_all_utf8_data, to_u64};
6use crate::{
7 Cpu, CpuRefreshKind, Error, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind,
8 ProcessesToUpdate,
9};
10
11use libc::{self, _SC_CLK_TCK, _SC_HOST_NAME_MAX, _SC_PAGESIZE, c_char, sysconf};
12
13use std::collections::HashMap;
14use std::ffi::CStr;
15use std::fs::File;
16use std::io::Read;
17use std::mem::MaybeUninit;
18use std::path::Path;
19use std::str::FromStr;
20use std::time::Duration;
21
22unsafe fn getrlimit() -> Option<libc::rlimit> {
23 let mut limits = libc::rlimit {
24 rlim_cur: 0,
25 rlim_max: 0,
26 };
27
28 if unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) } != 0 {
29 None
30 } else {
31 Some(limits)
32 }
33}
34
35declare_signals! {
36 libc::c_int,
37 Signal::Hangup => libc::SIGHUP,
38 Signal::Interrupt => libc::SIGINT,
39 Signal::Quit => libc::SIGQUIT,
40 Signal::Illegal => libc::SIGILL,
41 Signal::Trap => libc::SIGTRAP,
42 Signal::Abort => libc::SIGABRT,
43 Signal::IOT => libc::SIGIOT,
44 Signal::Bus => libc::SIGBUS,
45 Signal::FloatingPointException => libc::SIGFPE,
46 Signal::Kill => libc::SIGKILL,
47 Signal::User1 => libc::SIGUSR1,
48 Signal::Segv => libc::SIGSEGV,
49 Signal::User2 => libc::SIGUSR2,
50 Signal::Pipe => libc::SIGPIPE,
51 Signal::Alarm => libc::SIGALRM,
52 Signal::Term => libc::SIGTERM,
53 Signal::Child => libc::SIGCHLD,
54 Signal::Continue => libc::SIGCONT,
55 Signal::Stop => libc::SIGSTOP,
56 Signal::TSTP => libc::SIGTSTP,
57 Signal::TTIN => libc::SIGTTIN,
58 Signal::TTOU => libc::SIGTTOU,
59 Signal::Urgent => libc::SIGURG,
60 Signal::XCPU => libc::SIGXCPU,
61 Signal::XFSZ => libc::SIGXFSZ,
62 Signal::VirtualAlarm => libc::SIGVTALRM,
63 Signal::Profiling => libc::SIGPROF,
64 Signal::Winch => libc::SIGWINCH,
65 Signal::IO => libc::SIGIO,
66 Signal::Poll => libc::SIGPOLL,
67 Signal::Power => libc::SIGPWR,
68 Signal::Sys => libc::SIGSYS,
69}
70
71#[doc = include_str!("../../../md_doc/supported_signals.md")]
72pub const SUPPORTED_SIGNALS: &[crate::Signal] = supported_signals();
73#[doc = include_str!("../../../md_doc/minimum_cpu_update_interval.md")]
74pub const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(200);
75
76fn boot_time() -> Result<u64, Error> {
77 if let Ok(buf) = File::open("/proc/stat").and_then(|mut f| {
78 let mut buf = Vec::new();
79 f.read_to_end(&mut buf)?;
80 Ok(buf)
81 }) {
82 let line = buf.split(|c| *c == b'\n').find(|l| l.starts_with(b"btime"));
83
84 if let Some(line) = line
85 && let Some(btime) = line
86 .split(|x| *x == b' ')
87 .filter(|s| !s.is_empty())
88 .nth(1)
89 .map(to_u64)
90 {
91 return Ok(btime);
92 }
93 }
94 unsafe {
96 let mut up: libc::timespec = std::mem::zeroed();
97 if libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut up) == 0 {
98 Ok(up.tv_sec as u64)
99 } else {
100 Err(Error::from(
101 "clock_gettime failed: boot time cannot be retrieved",
102 ))
103 }
104 }
105}
106
107pub(crate) struct SystemInfo {
108 pub(crate) page_size_b: u64,
109 pub(crate) clock_cycle: u64,
110 pub(crate) boot_time: u64,
111}
112
113impl SystemInfo {
114 fn new() -> Self {
115 unsafe {
116 Self {
117 page_size_b: sysconf(_SC_PAGESIZE) as _,
118 clock_cycle: sysconf(_SC_CLK_TCK) as _,
119 boot_time: boot_time().unwrap_or(0),
120 }
121 }
122 }
123}
124
125pub(crate) struct SystemInner {
126 process_list: HashMap<Pid, Process>,
127 mem_total: u64,
128 mem_free: u64,
129 mem_available: u64,
130 mem_buffers: u64,
131 mem_page_cache: u64,
132 mem_shmem: u64,
133 mem_slab_reclaimable: u64,
134 swap_total: u64,
135 swap_free: u64,
136 info: SystemInfo,
137 cpus: CpusWrapper,
138}
139
140impl SystemInner {
141 fn get_max_process_cpu_usage(&self) -> f32 {
147 self.cpus.len() as f32 * 100.
148 }
149
150 fn update_procs_cpu(&mut self, refresh_kind: ProcessRefreshKind) {
151 if !refresh_kind.cpu() {
152 return;
153 }
154 self.cpus
155 .refresh_if_needed(true, CpuRefreshKind::nothing().with_usage());
156
157 if self.cpus.is_empty() {
158 sysinfo_debug!("cannot compute processes CPU usage: no CPU found...");
159 return;
160 }
161 let (new, old) = self.cpus.get_global_raw_times();
162 let total_time = if old > new { 1 } else { new - old };
163 let total_time = total_time as f32 / self.cpus.len() as f32;
164 let max_value = self.get_max_process_cpu_usage();
165
166 for proc_ in self.process_list.values_mut() {
167 compute_cpu_usage(&mut proc_.inner, total_time, max_value);
168 }
169 }
170
171 fn refresh_cpus(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) {
172 self.cpus.refresh(only_update_global_cpu, refresh_kind);
173 }
174}
175
176impl SystemInner {
177 pub(crate) fn new() -> Result<Self, Error> {
178 Ok(Self {
179 process_list: HashMap::new(),
180 mem_total: 0,
181 mem_free: 0,
182 mem_available: 0,
183 mem_buffers: 0,
184 mem_page_cache: 0,
185 mem_shmem: 0,
186 mem_slab_reclaimable: 0,
187 swap_total: 0,
188 swap_free: 0,
189 cpus: CpusWrapper::new(),
190 info: SystemInfo::new(),
191 })
192 }
193
194 pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) {
195 if !refresh_kind.ram() && !refresh_kind.swap() {
196 return;
197 }
198 let mut mem_available_found = false;
199 read_table("/proc/meminfo", ':', |key, value_kib| {
200 let field = match key {
201 "MemTotal" => &mut self.mem_total,
202 "MemFree" => &mut self.mem_free,
203 "MemAvailable" => {
204 mem_available_found = true;
205 &mut self.mem_available
206 }
207 "Buffers" => &mut self.mem_buffers,
208 "Cached" => &mut self.mem_page_cache,
209 "Shmem" => &mut self.mem_shmem,
210 "SReclaimable" => &mut self.mem_slab_reclaimable,
211 "SwapTotal" => &mut self.swap_total,
212 "SwapFree" => &mut self.swap_free,
213 _ => return,
214 };
215 *field = value_kib.saturating_mul(1_024);
217 });
218
219 if !mem_available_found {
223 self.mem_available = self
224 .mem_free
225 .saturating_add(self.mem_buffers)
226 .saturating_add(self.mem_page_cache)
227 .saturating_add(self.mem_slab_reclaimable)
228 .saturating_sub(self.mem_shmem);
229 }
230 }
231
232 pub(crate) fn cgroup_limits(&self) -> Option<crate::CGroupLimits> {
233 crate::sys::cgroup::limits_for_system()
234 }
235
236 pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) {
237 self.refresh_cpus(false, refresh_kind);
238 }
239
240 pub(crate) fn refresh_processes_specifics(
241 &mut self,
242 processes_to_update: ProcessesToUpdate<'_>,
243 refresh_kind: ProcessRefreshKind,
244 ) -> usize {
245 let uptime = Self::uptime().unwrap_or(0);
246 let nb_updated = refresh_procs(
247 &mut self.process_list,
248 Path::new("/proc"),
249 uptime,
250 &self.info,
251 processes_to_update,
252 refresh_kind,
253 );
254 self.update_procs_cpu(refresh_kind);
255 nb_updated
256 }
257
258 pub(crate) fn processes(&self) -> &HashMap<Pid, Process> {
263 &self.process_list
264 }
265
266 pub(crate) fn processes_mut(&mut self) -> &mut HashMap<Pid, Process> {
267 &mut self.process_list
268 }
269
270 pub(crate) fn process(&self, pid: Pid) -> Option<&Process> {
271 self.process_list.get(&pid)
272 }
273
274 pub(crate) fn global_cpu_usage(&self) -> f32 {
275 self.cpus.global_cpu.usage()
276 }
277
278 pub(crate) fn cpus(&self) -> &[Cpu] {
279 &self.cpus.cpus
280 }
281
282 pub(crate) fn total_memory(&self) -> u64 {
283 self.mem_total
284 }
285
286 pub(crate) fn free_memory(&self) -> u64 {
287 self.mem_free
288 }
289
290 pub(crate) fn available_memory(&self) -> u64 {
291 self.mem_available
292 }
293
294 pub(crate) fn used_memory(&self) -> u64 {
295 self.mem_total - self.mem_available
296 }
297
298 pub(crate) fn total_swap(&self) -> u64 {
299 self.swap_total
300 }
301
302 pub(crate) fn free_swap(&self) -> u64 {
303 self.swap_free
304 }
305
306 pub(crate) fn used_swap(&self) -> u64 {
308 self.swap_total - self.swap_free
309 }
310
311 pub(crate) fn uptime() -> Result<u64, Error> {
312 if cfg!(not(target_os = "android"))
313 && let Ok(content) = get_all_utf8_data("/proc/uptime", 50)
314 && let Some(uptime) = content.split('.').next().and_then(|t| t.parse().ok())
315 {
316 Ok(uptime)
317 } else {
318 Self::uptime_with_sysinfo()
319 }
320 }
321
322 fn uptime_with_sysinfo() -> Result<u64, Error> {
323 unsafe {
324 let mut s = MaybeUninit::<libc::sysinfo>::uninit();
325 if libc::sysinfo(s.as_mut_ptr()) != 0 {
326 Err(Error::from("sysinfo call failed"))
327 } else {
328 let s = s.assume_init();
329 Ok(if s.uptime < 1 { 0 } else { s.uptime as u64 })
330 }
331 }
332 }
333
334 pub(crate) fn boot_time() -> Result<u64, Error> {
335 boot_time()
336 }
337
338 pub(crate) fn load_average() -> Result<LoadAvg, Error> {
339 let mut s = String::new();
340 File::open("/proc/loadavg").and_then(|mut f| f.read_to_string(&mut s))?;
341 let loads = s
342 .trim()
343 .split(' ')
344 .take(3)
345 .filter_map(|val| val.parse::<f64>().ok())
346 .collect::<Vec<f64>>();
347 Ok(match *loads.as_slice() {
348 [one, five, fifteen, ..] => LoadAvg { one, five, fifteen },
349 [one, five] => LoadAvg {
350 one,
351 five,
352 fifteen: 0.,
353 },
354 [one] => LoadAvg {
355 one,
356 five: 0.,
357 fifteen: 0.,
358 },
359 [] => LoadAvg {
360 one: 0.,
361 five: 0.,
362 fifteen: 0.,
363 },
364 })
365 }
366
367 #[cfg(not(target_os = "android"))]
368 pub(crate) fn name() -> Result<String, Error> {
369 match get_system_info_linux(
370 InfoType::Name,
371 Path::new("/etc/os-release"),
372 Path::new("/etc/lsb-release"),
373 ) {
374 Some(info) => Ok(info),
375 None => Err(Error::Other("failed to retrieve system name".into())),
376 }
377 }
378
379 #[cfg(target_os = "android")]
380 pub(crate) fn name() -> Result<String, Error> {
381 match get_system_info_android(InfoType::Name) {
382 Some(info) => Ok(info),
383 None => Err(Error::Other("failed to retrieve system name".into())),
384 }
385 }
386
387 #[cfg(not(target_os = "android"))]
388 pub(crate) fn long_os_version() -> Result<String, Error> {
389 let mut long_name = "Linux".to_owned();
390
391 let distro_name = Self::name();
392 let distro_version = Self::os_version();
393 if let Ok(distro_version) = &distro_version {
394 long_name.push_str(" (");
396 long_name.push_str(distro_name.as_deref().unwrap_or("unknown"));
397 long_name.push(' ');
398 long_name.push_str(distro_version);
399 long_name.push(')');
400 } else if let Ok(distro_name) = &distro_name {
401 long_name.push_str(" (");
403 long_name.push_str(distro_name);
404 long_name.push(')');
405 }
406
407 Ok(long_name)
408 }
409
410 #[cfg(target_os = "android")]
411 pub(crate) fn long_os_version() -> Result<String, Error> {
412 let mut long_name = "Android".to_owned();
413
414 if let Ok(os_version) = Self::os_version() {
415 long_name.push(' ');
416 long_name.push_str(&os_version);
417 }
418
419 if let Ok(product_name) = Self::name() {
423 long_name.push_str(" on ");
424 long_name.push_str(&product_name);
425 }
426
427 Ok(long_name)
428 }
429
430 pub(crate) fn host_name() -> Result<String, Error> {
431 unsafe {
432 let hostname_max = sysconf(_SC_HOST_NAME_MAX);
433 let mut buffer = vec![0_u8; hostname_max as usize];
434 if libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len()) == 0 {
435 if let Some(pos) = buffer.iter().position(|x| *x == 0) {
436 buffer.resize(pos, 0);
438 }
439 if let Ok(info) = String::from_utf8(buffer) {
440 return Ok(info);
441 }
442 } else {
443 sysinfo_debug!("gethostname failed: hostname cannot be retrieved...");
444 }
445 }
446 Err(Error::Other("failed to retrieve host name".into()))
447 }
448
449 pub(crate) fn kernel_version() -> Result<String, Error> {
450 let mut raw = MaybeUninit::<libc::utsname>::zeroed();
451
452 unsafe {
453 if libc::uname(raw.as_mut_ptr()) == 0 {
454 let info = raw.assume_init();
455
456 let release = info
457 .release
458 .iter()
459 .filter(|c| **c != 0)
460 .map(|c| *c as u8 as char)
461 .collect::<String>();
462
463 Ok(release)
464 } else {
465 Err(Error::Other("failed to retrieve kernel version".into()))
466 }
467 }
468 }
469
470 #[cfg(not(target_os = "android"))]
471 pub(crate) fn os_version() -> Result<String, Error> {
472 match get_system_info_linux(
473 InfoType::OsVersion,
474 Path::new("/etc/os-release"),
475 Path::new("/etc/lsb-release"),
476 ) {
477 Some(info) => Ok(info),
478 None => Err(Error::Other("failed to retrieve OS version".into())),
479 }
480 }
481
482 #[cfg(target_os = "android")]
483 pub(crate) fn os_version() -> Result<String, Error> {
484 match get_system_info_android(InfoType::OsVersion) {
485 Some(info) => Ok(info),
486 None => Err(Error::Other("failed to retrieve OS version".into())),
487 }
488 }
489
490 #[cfg(not(target_os = "android"))]
491 pub(crate) fn distribution_id() -> String {
492 get_system_info_linux(
493 InfoType::DistributionID,
494 Path::new("/etc/os-release"),
495 Path::new(""),
496 )
497 .unwrap_or_else(|| std::env::consts::OS.to_owned())
498 }
499
500 #[cfg(target_os = "android")]
501 pub(crate) fn distribution_id() -> String {
502 get_system_info_android(InfoType::DistributionID)
506 .unwrap_or_else(|| std::env::consts::OS.to_owned())
507 }
508
509 #[cfg(not(target_os = "android"))]
510 pub(crate) fn distribution_id_like() -> Vec<String> {
511 system_info_as_list(get_system_info_linux(
512 InfoType::DistributionIDLike,
513 Path::new("/etc/os-release"),
514 Path::new(""),
515 ))
516 }
517
518 #[cfg(target_os = "android")]
519 pub(crate) fn distribution_id_like() -> Vec<String> {
520 system_info_as_list(get_system_info_android(InfoType::DistributionIDLike))
524 }
525
526 #[cfg(not(target_os = "android"))]
527 pub(crate) fn kernel_name() -> Option<&'static str> {
528 Some("Linux")
529 }
530
531 #[cfg(target_os = "android")]
532 pub(crate) fn kernel_name() -> Option<&'static str> {
533 Some("Android kernel")
534 }
535
536 pub(crate) fn cpu_arch() -> Option<String> {
537 let mut raw = MaybeUninit::<libc::utsname>::uninit();
538
539 unsafe {
540 if libc::uname(raw.as_mut_ptr()) != 0 {
541 return None;
542 }
543 let info = raw.assume_init();
544 let machine: &[u8] =
546 std::slice::from_raw_parts(info.machine.as_ptr() as *const _, info.machine.len());
547
548 CStr::from_bytes_until_nul(machine)
549 .ok()
550 .and_then(|res| match res.to_str() {
551 Ok(arch) => Some(arch.to_string()),
552 Err(_) => None,
553 })
554 }
555 }
556
557 pub(crate) fn physical_core_count() -> Result<usize, Error> {
558 get_physical_core_count()
559 }
560
561 pub(crate) fn refresh_cpu_list(&mut self, refresh_kind: CpuRefreshKind) {
562 self.cpus = CpusWrapper::new();
563 self.refresh_cpu_specifics(refresh_kind);
564 }
565
566 pub(crate) fn open_files_limit() -> Result<usize, Error> {
567 unsafe {
568 match getrlimit() {
569 Some(limits) => Ok(limits.rlim_cur as _),
570 None => {
571 sysinfo_debug!("getrlimit failed");
572 Err(Error::Other("failed to retrieve open files limit".into()))
573 }
574 }
575 }
576 }
577}
578
579fn read_table<F>(filename: &str, colsep: char, mut f: F)
580where
581 F: FnMut(&str, u64),
582{
583 if let Ok(content) = get_all_utf8_data(filename, 16_635) {
584 content
585 .split('\n')
586 .flat_map(|line| {
587 let mut split = line.split(colsep);
588 let key = split.next()?;
589 let value = split.next()?;
590 let value0 = value.trim_start().split(' ').next()?;
591 let value0_u64 = u64::from_str(value0).ok()?;
592 Some((key, value0_u64))
593 })
594 .for_each(|(k, v)| f(k, v));
595 }
596}
597
598#[derive(PartialEq, Eq)]
599enum InfoType {
600 Name,
604 OsVersion,
605 DistributionID,
608 DistributionIDLike,
611}
612
613#[cfg(not(target_os = "android"))]
614fn get_system_info_linux(info: InfoType, path: &Path, fallback_path: &Path) -> Option<String> {
615 if let Ok(buf) = File::open(path).and_then(|mut f| {
616 let mut buf = String::new();
617 f.read_to_string(&mut buf)?;
618 Ok(buf)
619 }) {
620 let info_str = match info {
621 InfoType::Name => "NAME=",
622 InfoType::OsVersion => "VERSION_ID=",
623 InfoType::DistributionID => "ID=",
624 InfoType::DistributionIDLike => "ID_LIKE=",
625 };
626
627 for line in buf.lines() {
628 if let Some(stripped) = line.strip_prefix(info_str) {
629 return Some(stripped.replace('"', ""));
630 }
631 }
632 }
633
634 let buf = File::open(fallback_path)
639 .and_then(|mut f| {
640 let mut buf = String::new();
641 f.read_to_string(&mut buf)?;
642 Ok(buf)
643 })
644 .ok()?;
645
646 let info_str = match info {
647 InfoType::OsVersion => "DISTRIB_RELEASE=",
648 InfoType::Name => "DISTRIB_ID=",
649 InfoType::DistributionID => {
650 return None;
652 }
653 InfoType::DistributionIDLike => {
654 return None;
656 }
657 };
658 for line in buf.lines() {
659 if let Some(stripped) = line.strip_prefix(info_str) {
660 return Some(stripped.replace('"', ""));
661 }
662 }
663 None
664}
665
666fn system_info_as_list(sysinfo: Option<String>) -> Vec<String> {
669 match sysinfo {
670 Some(value) => value.split_ascii_whitespace().map(String::from).collect(),
671 None => Vec::new(),
673 }
674}
675
676#[cfg(target_os = "android")]
677fn get_system_info_android(info: InfoType) -> Option<String> {
678 let name: &'static [u8] = match info {
680 InfoType::Name => b"ro.product.model\0",
681 InfoType::OsVersion => b"ro.build.version.release\0",
682 InfoType::DistributionID => {
683 return None;
685 }
686 InfoType::DistributionIDLike => {
687 return None;
689 }
690 };
691
692 let mut value_buffer = vec![0u8; libc::PROP_VALUE_MAX as usize];
693 unsafe {
694 let len = libc::__system_property_get(
695 name.as_ptr() as *const c_char,
696 value_buffer.as_mut_ptr() as *mut c_char,
697 );
698
699 if len != 0 {
700 if let Some(pos) = value_buffer.iter().position(|c| *c == 0) {
701 value_buffer.resize(pos, 0);
702 }
703 String::from_utf8(value_buffer).ok()
704 } else {
705 None
706 }
707 }
708}
709
710#[cfg(test)]
711mod test {
712 use super::InfoType;
713 #[cfg(target_os = "android")]
714 use super::get_system_info_android;
715 #[cfg(not(target_os = "android"))]
716 use super::get_system_info_linux;
717 use super::read_table;
718 use super::system_info_as_list;
719 use std::collections::HashMap;
720 use std::io::Write;
721 use tempfile::NamedTempFile;
722
723 #[test]
724 fn test_read_table() {
725 let mut file = NamedTempFile::new().unwrap();
727 writeln!(file, "KEY1:100 kB").unwrap();
728 writeln!(file, "KEY2:200 kB").unwrap();
729 writeln!(file, "KEY3:300 kB").unwrap();
730 writeln!(file, "KEY4:invalid").unwrap();
731
732 let file_path = file.path().to_str().unwrap();
733
734 let mut result = HashMap::new();
736 read_table(file_path, ':', |key, value| {
737 result.insert(key.to_string(), value);
738 });
739
740 assert_eq!(result.get("KEY1"), Some(&100));
741 assert_eq!(result.get("KEY2"), Some(&200));
742 assert_eq!(result.get("KEY3"), Some(&300));
743 assert_eq!(result.get("KEY4"), None);
744
745 let mut file = NamedTempFile::new().unwrap();
747 writeln!(file, "KEY1 400 MB").unwrap();
748 writeln!(file, "KEY2 500 GB").unwrap();
749 writeln!(file, "KEY3 600").unwrap();
750
751 let file_path = file.path().to_str().unwrap();
752
753 let mut result = HashMap::new();
754 read_table(file_path, ' ', |key, value| {
755 result.insert(key.to_string(), value);
756 });
757
758 assert_eq!(result.get("KEY1"), Some(&400));
759 assert_eq!(result.get("KEY2"), Some(&500));
760 assert_eq!(result.get("KEY3"), Some(&600));
761
762 let file = NamedTempFile::new().unwrap();
764 let file_path = file.path().to_str().unwrap();
765
766 let mut result = HashMap::new();
767 read_table(file_path, ':', |key, value| {
768 result.insert(key.to_string(), value);
769 });
770
771 assert!(result.is_empty());
772
773 let mut result = HashMap::new();
775 read_table("/nonexistent/file", ':', |key, value| {
776 result.insert(key.to_string(), value);
777 });
778
779 assert!(result.is_empty());
780 }
781
782 #[test]
783 #[cfg(target_os = "android")]
784 fn lsb_release_fallback_android() {
785 assert!(get_system_info_android(InfoType::OsVersion).is_some());
786 assert!(get_system_info_android(InfoType::Name).is_some());
787 assert!(get_system_info_android(InfoType::DistributionID).is_none());
788 assert!(get_system_info_android(InfoType::DistributionIDLike).is_none());
789 }
790
791 #[test]
792 #[cfg(not(target_os = "android"))]
793 fn lsb_release_fallback_not_android() {
794 use std::path::Path;
795
796 let dir = tempfile::tempdir().expect("failed to create temporary directory");
797 let tmp1 = dir.path().join("tmp1");
798 let tmp2 = dir.path().join("tmp2");
799
800 std::fs::write(
802 &tmp1,
803 r#"NAME="Ubuntu"
804VERSION="20.10 (Groovy Gorilla)"
805ID=ubuntu
806ID_LIKE=debian
807PRETTY_NAME="Ubuntu 20.10"
808VERSION_ID="20.10"
809VERSION_CODENAME=groovy
810UBUNTU_CODENAME=groovy
811"#,
812 )
813 .expect("Failed to create tmp1");
814
815 std::fs::write(
817 &tmp2,
818 r#"DISTRIB_ID=Ubuntu
819DISTRIB_RELEASE=20.10
820DISTRIB_CODENAME=groovy
821DISTRIB_DESCRIPTION="Ubuntu 20.10"
822"#,
823 )
824 .expect("Failed to create tmp2");
825
826 assert_eq!(
828 get_system_info_linux(InfoType::OsVersion, &tmp1, Path::new("")),
829 Some("20.10".to_owned())
830 );
831 assert_eq!(
832 get_system_info_linux(InfoType::Name, &tmp1, Path::new("")),
833 Some("Ubuntu".to_owned())
834 );
835 assert_eq!(
836 get_system_info_linux(InfoType::DistributionID, &tmp1, Path::new("")),
837 Some("ubuntu".to_owned())
838 );
839 assert_eq!(
840 get_system_info_linux(InfoType::DistributionIDLike, &tmp1, Path::new("")),
841 Some("debian".to_owned())
842 );
843
844 assert_eq!(
846 get_system_info_linux(InfoType::OsVersion, Path::new(""), &tmp2),
847 Some("20.10".to_owned())
848 );
849 assert_eq!(
850 get_system_info_linux(InfoType::Name, Path::new(""), &tmp2),
851 Some("Ubuntu".to_owned())
852 );
853 assert_eq!(
854 get_system_info_linux(InfoType::DistributionID, Path::new(""), &tmp2),
855 None
856 );
857 assert_eq!(
858 get_system_info_linux(InfoType::DistributionIDLike, Path::new(""), &tmp2),
859 None
860 );
861 }
862
863 #[test]
864 fn test_system_info_as_list() {
865 assert_eq!(system_info_as_list(None), Vec::<String>::new());
867 assert_eq!(
869 system_info_as_list(Some("".to_string())),
870 Vec::<String>::new(),
871 );
872 assert_eq!(
874 system_info_as_list(Some(" ".to_string())),
875 Vec::<String>::new(),
876 );
877 assert_eq!(
879 system_info_as_list(Some("debian".to_string())),
880 vec!["debian".to_string()],
881 );
882 assert_eq!(
884 system_info_as_list(Some("rhel fedora".to_string())),
885 vec!["rhel".to_string(), "fedora".to_string()],
886 );
887 assert_eq!(
889 system_info_as_list(Some("rhel fedora".to_string())),
890 vec!["rhel".to_string(), "fedora".to_string()],
891 );
892 }
893}