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