1use crate::sys::cpu::{get_physical_core_count, CpusWrapper};
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, c_char, sysconf, _SC_CLK_TCK, _SC_HOST_NAME_MAX, _SC_PAGESIZE};
12
13use std::cmp::min;
14use std::collections::HashMap;
15use std::ffi::CStr;
16use std::fs::File;
17use std::io::Read;
18use std::path::Path;
19use std::str::FromStr;
20use std::sync::{atomic::AtomicIsize, OnceLock};
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 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::CGroupLimits::new(self)
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 let content = get_all_utf8_data("/proc/uptime", 50).unwrap_or_default();
351 content
352 .split('.')
353 .next()
354 .and_then(|t| t.parse().ok())
355 .unwrap_or_default()
356 }
357
358 pub(crate) fn boot_time() -> u64 {
359 boot_time()
360 }
361
362 pub(crate) fn load_average() -> LoadAvg {
363 let mut s = String::new();
364 if File::open("/proc/loadavg")
365 .and_then(|mut f| f.read_to_string(&mut s))
366 .is_err()
367 {
368 return LoadAvg::default();
369 }
370 let loads = s
371 .trim()
372 .split(' ')
373 .take(3)
374 .map(|val| val.parse::<f64>().unwrap())
375 .collect::<Vec<f64>>();
376 LoadAvg {
377 one: loads[0],
378 five: loads[1],
379 fifteen: loads[2],
380 }
381 }
382
383 #[cfg(not(target_os = "android"))]
384 pub(crate) fn name() -> Option<String> {
385 get_system_info_linux(
386 InfoType::Name,
387 Path::new("/etc/os-release"),
388 Path::new("/etc/lsb-release"),
389 )
390 }
391
392 #[cfg(target_os = "android")]
393 pub(crate) fn name() -> Option<String> {
394 get_system_info_android(InfoType::Name)
395 }
396
397 #[cfg(not(target_os = "android"))]
398 pub(crate) fn long_os_version() -> Option<String> {
399 let mut long_name = "Linux".to_owned();
400
401 let distro_name = Self::name();
402 let distro_version = Self::os_version();
403 if let Some(distro_version) = &distro_version {
404 long_name.push_str(" (");
406 long_name.push_str(distro_name.as_deref().unwrap_or("unknown"));
407 long_name.push(' ');
408 long_name.push_str(distro_version);
409 long_name.push(')');
410 } else if let Some(distro_name) = &distro_name {
411 long_name.push_str(" (");
413 long_name.push_str(distro_name);
414 long_name.push(')');
415 }
416
417 Some(long_name)
418 }
419
420 #[cfg(target_os = "android")]
421 pub(crate) fn long_os_version() -> Option<String> {
422 let mut long_name = "Android".to_owned();
423
424 if let Some(os_version) = Self::os_version() {
425 long_name.push(' ');
426 long_name.push_str(&os_version);
427 }
428
429 if let Some(product_name) = Self::name() {
433 long_name.push_str(" on ");
434 long_name.push_str(&product_name);
435 }
436
437 Some(long_name)
438 }
439
440 pub(crate) fn host_name() -> Option<String> {
441 unsafe {
442 let hostname_max = sysconf(_SC_HOST_NAME_MAX);
443 let mut buffer = vec![0_u8; hostname_max as usize];
444 if libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len()) == 0 {
445 if let Some(pos) = buffer.iter().position(|x| *x == 0) {
446 buffer.resize(pos, 0);
448 }
449 String::from_utf8(buffer).ok()
450 } else {
451 sysinfo_debug!("gethostname failed: hostname cannot be retrieved...");
452 None
453 }
454 }
455 }
456
457 pub(crate) fn kernel_version() -> Option<String> {
458 let mut raw = std::mem::MaybeUninit::<libc::utsname>::zeroed();
459
460 unsafe {
461 if libc::uname(raw.as_mut_ptr()) == 0 {
462 let info = raw.assume_init();
463
464 let release = info
465 .release
466 .iter()
467 .filter(|c| **c != 0)
468 .map(|c| *c as u8 as char)
469 .collect::<String>();
470
471 Some(release)
472 } else {
473 None
474 }
475 }
476 }
477
478 #[cfg(not(target_os = "android"))]
479 pub(crate) fn os_version() -> Option<String> {
480 get_system_info_linux(
481 InfoType::OsVersion,
482 Path::new("/etc/os-release"),
483 Path::new("/etc/lsb-release"),
484 )
485 }
486
487 #[cfg(target_os = "android")]
488 pub(crate) fn os_version() -> Option<String> {
489 get_system_info_android(InfoType::OsVersion)
490 }
491
492 #[cfg(not(target_os = "android"))]
493 pub(crate) fn distribution_id() -> String {
494 get_system_info_linux(
495 InfoType::DistributionID,
496 Path::new("/etc/os-release"),
497 Path::new(""),
498 )
499 .unwrap_or_else(|| std::env::consts::OS.to_owned())
500 }
501
502 #[cfg(target_os = "android")]
503 pub(crate) fn distribution_id() -> String {
504 get_system_info_android(InfoType::DistributionID)
508 .unwrap_or_else(|| std::env::consts::OS.to_owned())
509 }
510
511 #[cfg(not(target_os = "android"))]
512 pub(crate) fn distribution_id_like() -> Vec<String> {
513 system_info_as_list(get_system_info_linux(
514 InfoType::DistributionIDLike,
515 Path::new("/etc/os-release"),
516 Path::new(""),
517 ))
518 }
519
520 #[cfg(target_os = "android")]
521 pub(crate) fn distribution_id_like() -> Vec<String> {
522 system_info_as_list(get_system_info_android(InfoType::DistributionIDLike))
526 }
527
528 #[cfg(not(target_os = "android"))]
529 pub(crate) fn kernel_name() -> Option<&'static str> {
530 Some("Linux")
531 }
532
533 #[cfg(target_os = "android")]
534 pub(crate) fn kernel_name() -> Option<&'static str> {
535 Some("Android kernel")
536 }
537
538 pub(crate) fn cpu_arch() -> Option<String> {
539 let mut raw = std::mem::MaybeUninit::<libc::utsname>::uninit();
540
541 unsafe {
542 if libc::uname(raw.as_mut_ptr()) != 0 {
543 return None;
544 }
545 let info = raw.assume_init();
546 let machine: &[u8] =
548 std::slice::from_raw_parts(info.machine.as_ptr() as *const _, info.machine.len());
549
550 CStr::from_bytes_until_nul(machine)
551 .ok()
552 .and_then(|res| match res.to_str() {
553 Ok(arch) => Some(arch.to_string()),
554 Err(_) => None,
555 })
556 }
557 }
558
559 pub(crate) fn physical_core_count() -> Option<usize> {
560 get_physical_core_count()
561 }
562
563 pub(crate) fn refresh_cpu_list(&mut self, refresh_kind: CpuRefreshKind) {
564 self.cpus = CpusWrapper::new();
565 self.refresh_cpu_specifics(refresh_kind);
566 }
567
568 pub(crate) fn open_files_limit() -> Option<usize> {
569 unsafe {
570 match getrlimit() {
571 Some(limits) => Some(limits.rlim_cur as _),
572 None => {
573 sysinfo_debug!("getrlimit failed");
574 None
575 }
576 }
577 }
578 }
579}
580
581fn read_u64(filename: &str) -> Option<u64> {
582 let result = get_all_utf8_data(filename, 16_635)
583 .ok()
584 .and_then(|d| u64::from_str(d.trim()).ok());
585
586 if result.is_none() {
587 sysinfo_debug!("Failed to read u64 in filename {}", filename);
588 }
589
590 result
591}
592
593fn read_table<F>(filename: &str, colsep: char, mut f: F)
594where
595 F: FnMut(&str, u64),
596{
597 if let Ok(content) = get_all_utf8_data(filename, 16_635) {
598 content
599 .split('\n')
600 .flat_map(|line| {
601 let mut split = line.split(colsep);
602 let key = split.next()?;
603 let value = split.next()?;
604 let value0 = value.trim_start().split(' ').next()?;
605 let value0_u64 = u64::from_str(value0).ok()?;
606 Some((key, value0_u64))
607 })
608 .for_each(|(k, v)| f(k, v));
609 }
610}
611
612fn read_table_key(filename: &str, target_key: &str, colsep: char) -> Option<u64> {
613 if let Ok(content) = get_all_utf8_data(filename, 16_635) {
614 return content.split('\n').find_map(|line| {
615 let mut split = line.split(colsep);
616 let key = split.next()?;
617 if key != target_key {
618 return None;
619 }
620
621 let value = split.next()?;
622 let value0 = value.trim_start().split(' ').next()?;
623 u64::from_str(value0).ok()
624 });
625 }
626
627 None
628}
629
630impl crate::CGroupLimits {
631 fn new(sys: &SystemInner) -> Option<Self> {
632 assert!(
633 sys.mem_total != 0,
634 "You need to call System::refresh_memory before trying to get cgroup limits!",
635 );
636 if let (Some(mem_cur), Some(mem_max), Some(mem_rss)) = (
637 read_u64("/sys/fs/cgroup/memory.current"),
639 read_u64("/sys/fs/cgroup/memory.max").or(Some(u64::MAX)),
641 read_table_key("/sys/fs/cgroup/memory.stat", "anon", ' '),
642 ) {
643 let mut limits = Self {
644 total_memory: sys.mem_total,
645 free_memory: sys.mem_free,
646 free_swap: sys.swap_free,
647 rss: mem_rss,
648 };
649
650 limits.total_memory = min(mem_max, sys.mem_total);
651 limits.free_memory = limits.total_memory.saturating_sub(mem_cur);
652
653 if let Some(swap_cur) = read_u64("/sys/fs/cgroup/memory.swap.current") {
654 limits.free_swap = sys.swap_total.saturating_sub(swap_cur);
655 }
656
657 Some(limits)
658 } else if let (Some(mem_cur), Some(mem_max), Some(mem_rss)) = (
659 read_u64("/sys/fs/cgroup/memory/memory.usage_in_bytes"),
661 read_u64("/sys/fs/cgroup/memory/memory.limit_in_bytes"),
662 read_table_key("/sys/fs/cgroup/memory/memory.stat", "total_rss", ' '),
663 ) {
664 let mut limits = Self {
665 total_memory: sys.mem_total,
666 free_memory: sys.mem_free,
667 free_swap: sys.swap_free,
668 rss: mem_rss,
669 };
670
671 limits.total_memory = min(mem_max, sys.mem_total);
672 limits.free_memory = limits.total_memory.saturating_sub(mem_cur);
673
674 Some(limits)
675 } else {
676 None
677 }
678 }
679}
680
681#[derive(PartialEq, Eq)]
682enum InfoType {
683 Name,
687 OsVersion,
688 DistributionID,
691 DistributionIDLike,
694}
695
696#[cfg(not(target_os = "android"))]
697fn get_system_info_linux(info: InfoType, path: &Path, fallback_path: &Path) -> Option<String> {
698 if let Ok(buf) = File::open(path).and_then(|mut f| {
699 let mut buf = String::new();
700 f.read_to_string(&mut buf)?;
701 Ok(buf)
702 }) {
703 let info_str = match info {
704 InfoType::Name => "NAME=",
705 InfoType::OsVersion => "VERSION_ID=",
706 InfoType::DistributionID => "ID=",
707 InfoType::DistributionIDLike => "ID_LIKE=",
708 };
709
710 for line in buf.lines() {
711 if let Some(stripped) = line.strip_prefix(info_str) {
712 return Some(stripped.replace('"', ""));
713 }
714 }
715 }
716
717 let buf = File::open(fallback_path)
722 .and_then(|mut f| {
723 let mut buf = String::new();
724 f.read_to_string(&mut buf)?;
725 Ok(buf)
726 })
727 .ok()?;
728
729 let info_str = match info {
730 InfoType::OsVersion => "DISTRIB_RELEASE=",
731 InfoType::Name => "DISTRIB_ID=",
732 InfoType::DistributionID => {
733 return None;
735 }
736 InfoType::DistributionIDLike => {
737 return None;
739 }
740 };
741 for line in buf.lines() {
742 if let Some(stripped) = line.strip_prefix(info_str) {
743 return Some(stripped.replace('"', ""));
744 }
745 }
746 None
747}
748
749fn system_info_as_list(sysinfo: Option<String>) -> Vec<String> {
752 match sysinfo {
753 Some(value) => value.split_ascii_whitespace().map(String::from).collect(),
754 None => Vec::new(),
756 }
757}
758
759#[cfg(target_os = "android")]
760fn get_system_info_android(info: InfoType) -> Option<String> {
761 let name: &'static [u8] = match info {
763 InfoType::Name => b"ro.product.model\0",
764 InfoType::OsVersion => b"ro.build.version.release\0",
765 InfoType::DistributionID => {
766 return None;
768 }
769 InfoType::DistributionIDLike => {
770 return None;
772 }
773 };
774
775 let mut value_buffer = vec![0u8; libc::PROP_VALUE_MAX as usize];
776 unsafe {
777 let len = libc::__system_property_get(
778 name.as_ptr() as *const c_char,
779 value_buffer.as_mut_ptr() as *mut c_char,
780 );
781
782 if len != 0 {
783 if let Some(pos) = value_buffer.iter().position(|c| *c == 0) {
784 value_buffer.resize(pos, 0);
785 }
786 String::from_utf8(value_buffer).ok()
787 } else {
788 None
789 }
790 }
791}
792
793#[cfg(test)]
794mod test {
795 #[cfg(target_os = "android")]
796 use super::get_system_info_android;
797 #[cfg(not(target_os = "android"))]
798 use super::get_system_info_linux;
799 use super::read_table;
800 use super::read_table_key;
801 use super::system_info_as_list;
802 use super::InfoType;
803 use std::collections::HashMap;
804 use std::io::Write;
805 use tempfile::NamedTempFile;
806
807 #[test]
808 fn test_read_table() {
809 let mut file = NamedTempFile::new().unwrap();
811 writeln!(file, "KEY1:100 kB").unwrap();
812 writeln!(file, "KEY2:200 kB").unwrap();
813 writeln!(file, "KEY3:300 kB").unwrap();
814 writeln!(file, "KEY4:invalid").unwrap();
815
816 let file_path = file.path().to_str().unwrap();
817
818 let mut result = HashMap::new();
820 read_table(file_path, ':', |key, value| {
821 result.insert(key.to_string(), value);
822 });
823
824 assert_eq!(result.get("KEY1"), Some(&100));
825 assert_eq!(result.get("KEY2"), Some(&200));
826 assert_eq!(result.get("KEY3"), Some(&300));
827 assert_eq!(result.get("KEY4"), None);
828
829 let mut file = NamedTempFile::new().unwrap();
831 writeln!(file, "KEY1 400 MB").unwrap();
832 writeln!(file, "KEY2 500 GB").unwrap();
833 writeln!(file, "KEY3 600").unwrap();
834
835 let file_path = file.path().to_str().unwrap();
836
837 let mut result = HashMap::new();
838 read_table(file_path, ' ', |key, value| {
839 result.insert(key.to_string(), value);
840 });
841
842 assert_eq!(result.get("KEY1"), Some(&400));
843 assert_eq!(result.get("KEY2"), Some(&500));
844 assert_eq!(result.get("KEY3"), Some(&600));
845
846 let file = NamedTempFile::new().unwrap();
848 let file_path = file.path().to_str().unwrap();
849
850 let mut result = HashMap::new();
851 read_table(file_path, ':', |key, value| {
852 result.insert(key.to_string(), value);
853 });
854
855 assert!(result.is_empty());
856
857 let mut result = HashMap::new();
859 read_table("/nonexistent/file", ':', |key, value| {
860 result.insert(key.to_string(), value);
861 });
862
863 assert!(result.is_empty());
864 }
865
866 #[test]
867 fn test_read_table_key() {
868 let mut file = NamedTempFile::new().unwrap();
870 writeln!(file, "KEY1:100 kB").unwrap();
871 writeln!(file, "KEY2:200 kB").unwrap();
872 writeln!(file, "KEY3:300 kB").unwrap();
873
874 let file_path = file.path().to_str().unwrap();
875
876 assert_eq!(read_table_key(file_path, "KEY1", ':'), Some(100));
878 assert_eq!(read_table_key(file_path, "KEY2", ':'), Some(200));
879 assert_eq!(read_table_key(file_path, "KEY3", ':'), Some(300));
880
881 assert_eq!(read_table_key(file_path, "KEY4", ':'), None);
883
884 let mut file = NamedTempFile::new().unwrap();
886 writeln!(file, "KEY1 400 kB").unwrap();
887 writeln!(file, "KEY2 500 kB").unwrap();
888
889 let file_path = file.path().to_str().unwrap();
890
891 assert_eq!(read_table_key(file_path, "KEY1", ' '), Some(400));
892 assert_eq!(read_table_key(file_path, "KEY2", ' '), Some(500));
893
894 assert_eq!(read_table_key("/nonexistent/file", "KEY1", ':'), None);
896 }
897
898 #[test]
899 #[cfg(target_os = "android")]
900 fn lsb_release_fallback_android() {
901 assert!(get_system_info_android(InfoType::OsVersion).is_some());
902 assert!(get_system_info_android(InfoType::Name).is_some());
903 assert!(get_system_info_android(InfoType::DistributionID).is_none());
904 assert!(get_system_info_android(InfoType::DistributionIDLike).is_none());
905 }
906
907 #[test]
908 #[cfg(not(target_os = "android"))]
909 fn lsb_release_fallback_not_android() {
910 use std::path::Path;
911
912 let dir = tempfile::tempdir().expect("failed to create temporary directory");
913 let tmp1 = dir.path().join("tmp1");
914 let tmp2 = dir.path().join("tmp2");
915
916 std::fs::write(
918 &tmp1,
919 r#"NAME="Ubuntu"
920VERSION="20.10 (Groovy Gorilla)"
921ID=ubuntu
922ID_LIKE=debian
923PRETTY_NAME="Ubuntu 20.10"
924VERSION_ID="20.10"
925VERSION_CODENAME=groovy
926UBUNTU_CODENAME=groovy
927"#,
928 )
929 .expect("Failed to create tmp1");
930
931 std::fs::write(
933 &tmp2,
934 r#"DISTRIB_ID=Ubuntu
935DISTRIB_RELEASE=20.10
936DISTRIB_CODENAME=groovy
937DISTRIB_DESCRIPTION="Ubuntu 20.10"
938"#,
939 )
940 .expect("Failed to create tmp2");
941
942 assert_eq!(
944 get_system_info_linux(InfoType::OsVersion, &tmp1, Path::new("")),
945 Some("20.10".to_owned())
946 );
947 assert_eq!(
948 get_system_info_linux(InfoType::Name, &tmp1, Path::new("")),
949 Some("Ubuntu".to_owned())
950 );
951 assert_eq!(
952 get_system_info_linux(InfoType::DistributionID, &tmp1, Path::new("")),
953 Some("ubuntu".to_owned())
954 );
955 assert_eq!(
956 get_system_info_linux(InfoType::DistributionIDLike, &tmp1, Path::new("")),
957 Some("debian".to_owned())
958 );
959
960 assert_eq!(
962 get_system_info_linux(InfoType::OsVersion, Path::new(""), &tmp2),
963 Some("20.10".to_owned())
964 );
965 assert_eq!(
966 get_system_info_linux(InfoType::Name, Path::new(""), &tmp2),
967 Some("Ubuntu".to_owned())
968 );
969 assert_eq!(
970 get_system_info_linux(InfoType::DistributionID, Path::new(""), &tmp2),
971 None
972 );
973 assert_eq!(
974 get_system_info_linux(InfoType::DistributionIDLike, Path::new(""), &tmp2),
975 None
976 );
977 }
978
979 #[test]
980 fn test_system_info_as_list() {
981 assert_eq!(system_info_as_list(None), Vec::<String>::new());
983 assert_eq!(
985 system_info_as_list(Some("".to_string())),
986 Vec::<String>::new(),
987 );
988 assert_eq!(
990 system_info_as_list(Some(" ".to_string())),
991 Vec::<String>::new(),
992 );
993 assert_eq!(
995 system_info_as_list(Some("debian".to_string())),
996 vec!["debian".to_string()],
997 );
998 assert_eq!(
1000 system_info_as_list(Some("rhel fedora".to_string())),
1001 vec!["rhel".to_string(), "fedora".to_string()],
1002 );
1003 assert_eq!(
1005 system_info_as_list(Some("rhel fedora".to_string())),
1006 vec!["rhel".to_string(), "fedora".to_string()],
1007 );
1008 }
1009}