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