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