1 //
2 // Sysinfo
3 //
4 // Copyright (c) 2015 Guillaume Gomez
5 //
6 
7 use crate::sys::component::{self, Component};
8 use crate::sys::disk;
9 use crate::sys::process::*;
10 use crate::sys::processor::*;
11 use crate::sys::utils::get_all_data;
12 use crate::{Disk, LoadAvg, Networks, Pid, ProcessExt, RefreshKind, SystemExt, User};
13 
14 use libc::{self, c_char, sysconf, _SC_HOST_NAME_MAX, _SC_PAGESIZE};
15 use std::collections::HashMap;
16 use std::fs::File;
17 use std::io::{BufRead, BufReader, Read};
18 use std::path::Path;
19 use std::str::FromStr;
20 use std::sync::{Arc, Mutex};
21 use std::time::SystemTime;
22 
23 // This whole thing is to prevent having too many files open at once. It could be problematic
24 // for processes using a lot of files and using sysinfo at the same time.
25 #[allow(clippy::mutex_atomic)]
26 pub(crate) static mut REMAINING_FILES: once_cell::sync::Lazy<Arc<Mutex<isize>>> =
27     once_cell::sync::Lazy::new(|| {
28         unsafe {
29             let mut limits = libc::rlimit {
30                 rlim_cur: 0,
31                 rlim_max: 0,
32             };
33             if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 {
34                 // Most linux system now defaults to 1024.
35                 return Arc::new(Mutex::new(1024 / 2));
36             }
37             // We save the value in case the update fails.
38             let current = limits.rlim_cur;
39 
40             // The set the soft limit to the hard one.
41             limits.rlim_cur = limits.rlim_max;
42             // In this part, we leave minimum 50% of the available file descriptors to the process
43             // using sysinfo.
44             Arc::new(Mutex::new(
45                 if libc::setrlimit(libc::RLIMIT_NOFILE, &limits) == 0 {
46                     limits.rlim_cur / 2
47                 } else {
strip_trailoptnull48                     current / 2
49                 } as _,
50             ))
51         }
52     });
53 
54 pub(crate) fn get_max_nb_fds() -> isize {
55     unsafe {
56         let mut limits = libc::rlimit {
57             rlim_cur: 0,
58             rlim_max: 0,
59         };
60         if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 {
61             // Most linux system now defaults to 1024.
62             1024 / 2
63         } else {
64             limits.rlim_max as isize / 2
65         }
66     }
67 }
68 
69 macro_rules! to_str {
70     ($e:expr) => {
71         unsafe { std::str::from_utf8_unchecked($e) }
72     };
73 }
74 
75 fn boot_time() -> u64 {
76     if let Ok(f) = File::open("/proc/stat") {
77         let buf = BufReader::new(f);
78         let line = buf
79             .split(b'\n')
80             .filter_map(|r| r.ok())
81             .find(|l| l.starts_with(b"btime"));
82 
83         if let Some(line) = line {
84             return line
85                 .split(|x| *x == b' ')
86                 .filter(|s| !s.is_empty())
87                 .nth(1)
88                 .map(to_u64)
89                 .unwrap_or(0);
90         }
91     }
92     // Either we didn't find "btime" or "/proc/stat" wasn't available for some reason...
93     let mut up = libc::timespec {
94         tv_sec: 0,
95         tv_nsec: 0,
96     };
97     if unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut up) } == 0 {
98         up.tv_sec as u64
99     } else {
100         sysinfo_debug!("clock_gettime failed: boot time cannot be retrieve...");
101         0
102     }
103 }
104 
105 /// Structs containing system's information.
106 pub struct System {
107     process_list: Process,
108     mem_total: u64,
109     mem_free: u64,
110     mem_available: u64,
111     mem_buffers: u64,
112     mem_page_cache: u64,
113     mem_slab_reclaimable: u64,
114     swap_total: u64,
115     swap_free: u64,
116     global_processor: Processor,
117     processors: Vec<Processor>,
118     page_size_kb: u64,
119     components: Vec<Component>,
120     disks: Vec<Disk>,
121     networks: Networks,
122     users: Vec<User>,
123     boot_time: u64,
124 }
125 
126 impl System {
127     fn clear_procs(&mut self) {
128         if !self.processors.is_empty() {
129             let (new, old) = get_raw_times(&self.global_processor);
130             let total_time = (if old > new { 1 } else { new - old }) as f32;
131             let mut to_delete = Vec::with_capacity(20);
132 
133             for (pid, proc_) in &mut self.process_list.tasks {
134                 if !has_been_updated(proc_) {
135                     to_delete.push(*pid);
136                 } else {
137                     compute_cpu_usage(proc_, self.processors.len() as u64, total_time);
138                 }
139             }
140             for pid in to_delete {
141                 self.process_list.tasks.remove(&pid);
142             }
143         }
144     }
145 
146     fn refresh_processors(&mut self, limit: Option<u32>) {
147         if let Ok(f) = File::open("/proc/stat") {
148             let buf = BufReader::new(f);
149             let mut i: usize = 0;
150             let first = self.processors.is_empty();
151             let mut it = buf.split(b'\n');
152             let mut count = 0;
153             let (vendor_id, brand) = if first {
154                 get_vendor_id_and_brand()
155             } else {
156                 (String::new(), String::new())
157             };
158 
159             if let Some(Ok(line)) = it.next() {
160                 if &line[..4] != b"cpu " {
161                     return;
162                 }
163                 let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty());
164                 if first {
165                     self.global_processor.name = to_str!(parts.next().unwrap_or(&[])).to_owned();
166                 } else {
167                     parts.next();
168                 }
169                 self.global_processor.set(
170                     parts.next().map(to_u64).unwrap_or(0),
171                     parts.next().map(to_u64).unwrap_or(0),
172                     parts.next().map(to_u64).unwrap_or(0),
173                     parts.next().map(to_u64).unwrap_or(0),
174                     parts.next().map(to_u64).unwrap_or(0),
175                     parts.next().map(to_u64).unwrap_or(0),
176                     parts.next().map(to_u64).unwrap_or(0),
177                     parts.next().map(to_u64).unwrap_or(0),
178                     parts.next().map(to_u64).unwrap_or(0),
179                     parts.next().map(to_u64).unwrap_or(0),
180                 );
181                 count += 1;
182                 if let Some(limit) = limit {
183                     if count >= limit {
184                         return;
185                     }
186                 }
187             }
188             while let Some(Ok(line)) = it.next() {
189                 if &line[..3] != b"cpu" {
190                     break;
191                 }
192 
193                 let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty());
194                 if first {
195                     self.processors.push(Processor::new_with_values(
196                         to_str!(parts.next().unwrap_or(&[])),
197                         parts.next().map(to_u64).unwrap_or(0),
198                         parts.next().map(to_u64).unwrap_or(0),
199                         parts.next().map(to_u64).unwrap_or(0),
200                         parts.next().map(to_u64).unwrap_or(0),
201                         parts.next().map(to_u64).unwrap_or(0),
202                         parts.next().map(to_u64).unwrap_or(0),
203                         parts.next().map(to_u64).unwrap_or(0),
204                         parts.next().map(to_u64).unwrap_or(0),
205                         parts.next().map(to_u64).unwrap_or(0),
206                         parts.next().map(to_u64).unwrap_or(0),
207                         get_cpu_frequency(i),
208                         vendor_id.clone(),
209                         brand.clone(),
210                     ));
211                 } else {
212                     parts.next(); // we don't want the name again
213                     self.processors[i].set(
214                         parts.next().map(to_u64).unwrap_or(0),
215                         parts.next().map(to_u64).unwrap_or(0),
216                         parts.next().map(to_u64).unwrap_or(0),
217                         parts.next().map(to_u64).unwrap_or(0),
218                         parts.next().map(to_u64).unwrap_or(0),
219                         parts.next().map(to_u64).unwrap_or(0),
220                         parts.next().map(to_u64).unwrap_or(0),
221                         parts.next().map(to_u64).unwrap_or(0),
222                         parts.next().map(to_u64).unwrap_or(0),
223                         parts.next().map(to_u64).unwrap_or(0),
224                     );
225                     self.processors[i].frequency = get_cpu_frequency(i);
226                 }
227 
228                 self.global_processor.frequency = self
229                     .processors
230                     .iter()
231                     .map(|p| p.frequency)
232                     .max()
233                     .unwrap_or(0);
234 
235                 i += 1;
236                 count += 1;
237                 if let Some(limit) = limit {
238                     if count >= limit {
239                         break;
240                     }
241                 }
242             }
243             if first {
244                 self.global_processor.vendor_id = vendor_id;
245                 self.global_processor.brand = brand;
246             }
247         }
248     }
249 }
250 
251 impl SystemExt for System {
252     const IS_SUPPORTED: bool = true;
253 
254     fn new_with_specifics(refreshes: RefreshKind) -> System {
255         let mut s = System {
256             process_list: Process::new(0, None, 0),
257             mem_total: 0,
258             mem_free: 0,
259             mem_available: 0,
260             mem_buffers: 0,
261             mem_page_cache: 0,
262             mem_slab_reclaimable: 0,
263             swap_total: 0,
264             swap_free: 0,
265             global_processor: Processor::new_with_values(
266                 "",
267                 0,
268                 0,
269                 0,
270                 0,
271                 0,
272                 0,
273                 0,
274                 0,
275                 0,
276                 0,
277                 0,
278                 String::new(),
279                 String::new(),
280             ),
281             processors: Vec::with_capacity(4),
282             page_size_kb: unsafe { sysconf(_SC_PAGESIZE) as u64 / 1024 },
283             components: Vec::new(),
284             disks: Vec::with_capacity(2),
285             networks: Networks::new(),
286             users: Vec::new(),
287             boot_time: boot_time(),
288         };
289         if !refreshes.cpu() {
290             s.refresh_processors(None); // We need the processors to be filled.
291         }
292         s.refresh_specifics(refreshes);
293         s
294     }
295 
296     fn refresh_components_list(&mut self) {
297         self.components = component::get_components();
298     }
299 
300     fn refresh_memory(&mut self) {
301         if let Ok(data) = get_all_data("/proc/meminfo", 16_385) {
302             for line in data.split('\n') {
303                 let mut iter = line.split(':');
304                 let field = match iter.next() {
305                     Some("MemTotal") => &mut self.mem_total,
306                     Some("MemFree") => &mut self.mem_free,
307                     Some("MemAvailable") => &mut self.mem_available,
308                     Some("Buffers") => &mut self.mem_buffers,
309                     Some("Cached") => &mut self.mem_page_cache,
310                     Some("SReclaimable") => &mut self.mem_slab_reclaimable,
311                     Some("SwapTotal") => &mut self.swap_total,
312                     Some("SwapFree") => &mut self.swap_free,
313                     _ => continue,
314                 };
315                 if let Some(val_str) = iter.next().and_then(|s| s.trim_start().split(' ').next()) {
316                     if let Ok(value) = u64::from_str(val_str) {
317                         // /proc/meminfo reports KiB, though it says "kB". Convert it.
318                         *field = value * 128 / 125;
319                     }
320                 }
321             }
322         }
323     }
324 
325     fn refresh_cpu(&mut self) {
326         self.refresh_processors(None);
327     }
328 
329     fn refresh_processes(&mut self) {
330         let uptime = self.uptime();
331         if refresh_procs(
332             &mut self.process_list,
333             Path::new("/proc"),
334             self.page_size_kb,
335             0,
336             uptime,
337             get_secs_since_epoch(),
338         ) {
339             self.clear_procs();
340         }
341     }
342 
343     fn refresh_process(&mut self, pid: Pid) -> bool {
344         let uptime = self.uptime();
345         let found = match _get_process_data(
346             &Path::new("/proc/").join(pid.to_string()),
347             &mut self.process_list,
348             self.page_size_kb,
349             0,
350             uptime,
351             get_secs_since_epoch(),
352         ) {
353             Ok((Some(p), pid)) => {
354                 self.process_list.tasks.insert(pid, p);
355                 true
356             }
357             Ok(_) => true,
358             Err(_) => false,
359         };
360         if found && !self.processors.is_empty() {
361             self.refresh_processors(Some(1));
362             let (new, old) = get_raw_times(&self.global_processor);
363             let total_time = (if old >= new { 1 } else { new - old }) as f32;
364 
365             if let Some(p) = self.process_list.tasks.get_mut(&pid) {
366                 compute_cpu_usage(p, self.processors.len() as u64, total_time);
367             }
368         }
369         found
370     }
371 
372     fn refresh_disks_list(&mut self) {
373         self.disks = disk::get_all_disks();
374     }
375 
376     fn refresh_users_list(&mut self) {
377         self.users = crate::linux::users::get_users_list();
378     }
379 
380     // COMMON PART
381     //
382     // Need to be moved into a "common" file to avoid duplication.
383 
384     fn processes(&self) -> &HashMap<Pid, Process> {
385         &self.process_list.tasks
386     }
387 
388     fn process(&self, pid: Pid) -> Option<&Process> {
389         self.process_list.tasks.get(&pid)
390     }
391 
392     fn networks(&self) -> &Networks {
393         &self.networks
394     }
395 
396     fn networks_mut(&mut self) -> &mut Networks {
397         &mut self.networks
398     }
399 
400     fn global_processor_info(&self) -> &Processor {
401         &self.global_processor
402     }
403 
404     fn processors(&self) -> &[Processor] {
405         &self.processors
406     }
407 
408     fn physical_core_count(&self) -> Option<usize> {
409         get_physical_core_count()
410     }
411 
412     fn total_memory(&self) -> u64 {
413         self.mem_total
414     }
415 
416     fn free_memory(&self) -> u64 {
417         self.mem_free
418     }
419 
420     fn available_memory(&self) -> u64 {
421         self.mem_available
422     }
423 
424     fn used_memory(&self) -> u64 {
425         self.mem_total
426             - self.mem_free
427             - self.mem_buffers
428             - self.mem_page_cache
429             - self.mem_slab_reclaimable
430     }
431 
432     fn total_swap(&self) -> u64 {
433         self.swap_total
434     }
435 
436     fn free_swap(&self) -> u64 {
437         self.swap_free
438     }
439 
440     // need to be checked
441     fn used_swap(&self) -> u64 {
442         self.swap_total - self.swap_free
443     }
444 
445     fn components(&self) -> &[Component] {
446         &self.components
447     }
448 
449     fn components_mut(&mut self) -> &mut [Component] {
450         &mut self.components
451     }
452 
453     fn disks(&self) -> &[Disk] {
454         &self.disks
455     }
456 
457     fn disks_mut(&mut self) -> &mut [Disk] {
458         &mut self.disks
459     }
460 
461     fn uptime(&self) -> u64 {
462         let content = get_all_data("/proc/uptime", 50).unwrap_or_default();
463         content
464             .split('.')
465             .next()
466             .and_then(|t| t.parse().ok())
467             .unwrap_or_default()
468     }
469 
470     fn boot_time(&self) -> u64 {
471         self.boot_time
472     }
473 
474     fn load_average(&self) -> LoadAvg {
475         let mut s = String::new();
476         if File::open("/proc/loadavg")
477             .and_then(|mut f| f.read_to_string(&mut s))
478             .is_err()
479         {
480             return LoadAvg::default();
481         }
482         let loads = s
483             .trim()
484             .split(' ')
485             .take(3)
486             .map(|val| val.parse::<f64>().unwrap())
487             .collect::<Vec<f64>>();
488         LoadAvg {
489             one: loads[0],
490             five: loads[1],
491             fifteen: loads[2],
492         }
493     }
494 
495     fn users(&self) -> &[User] {
496         &self.users
497     }
498 
499     #[cfg(not(target_os = "android"))]
500     fn name(&self) -> Option<String> {
501         get_system_info_linux(
502             InfoType::Name,
503             Path::new("/etc/os-release"),
504             Path::new("/etc/lsb-release"),
505         )
506     }
507 
508     #[cfg(target_os = "android")]
509     fn name(&self) -> Option<String> {
510         get_system_info_android(InfoType::Name)
511     }
512 
513     fn long_os_version(&self) -> Option<String> {
514         #[cfg(target_os = "android")]
515         let system_name = "Android";
516 
517         #[cfg(not(target_os = "android"))]
518         let system_name = "Linux";
519 
520         Some(format!(
521             "{} {} {}",
522             system_name,
523             self.os_version().unwrap_or_default(),
524             self.name().unwrap_or_default()
525         ))
526     }
527 
528     fn host_name(&self) -> Option<String> {
529         let hostname_max = unsafe { sysconf(_SC_HOST_NAME_MAX) };
530         let mut buffer = vec![0_u8; hostname_max as usize];
531         if unsafe { libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len()) } == 0 {
532             if let Some(pos) = buffer.iter().position(|x| *x == 0) {
533                 // Shrink buffer to terminate the null bytes
534                 buffer.resize(pos, 0);
535             }
536             String::from_utf8(buffer).ok()
537         } else {
538             sysinfo_debug!("gethostname failed: hostname cannot be retrieved...");
539             None
540         }
541     }
542 
543     fn kernel_version(&self) -> Option<String> {
544         let mut raw = std::mem::MaybeUninit::<libc::utsname>::zeroed();
545 
546         if unsafe { libc::uname(raw.as_mut_ptr()) } == 0 {
547             let info = unsafe { raw.assume_init() };
548 
549             let release = info
550                 .release
551                 .iter()
552                 .filter(|c| **c != 0)
553                 .map(|c| *c as u8 as char)
554                 .collect::<String>();
555 
556             Some(release)
557         } else {
558             None
559         }
560     }
561 
562     #[cfg(not(target_os = "android"))]
563     fn os_version(&self) -> Option<String> {
564         get_system_info_linux(
565             InfoType::OsVersion,
566             Path::new("/etc/os-release"),
567             Path::new("/etc/lsb-release"),
568         )
569     }
570 
571     #[cfg(target_os = "android")]
572     fn os_version(&self) -> Option<String> {
573         get_system_info_android(InfoType::OsVersion)
574     }
575 }
576 
577 impl Default for System {
578     fn default() -> System {
579         System::new()
580     }
581 }
582 
583 fn to_u64(v: &[u8]) -> u64 {
584     let mut x = 0;
585 
586     for c in v {
587         x *= 10;
588         x += u64::from(c - b'0');
589     }
590     x
591 }
592 
593 #[derive(PartialEq)]
594 enum InfoType {
595     /// The end-user friendly name of:
596     /// - Android: The device model
597     /// - Linux: The distributions name
598     Name,
599     OsVersion,
600 }
601 
602 #[cfg(not(target_os = "android"))]
603 fn get_system_info_linux(info: InfoType, path: &Path, fallback_path: &Path) -> Option<String> {
604     if let Ok(f) = File::open(path) {
605         let reader = BufReader::new(f);
606 
607         let info_str = match info {
608             InfoType::Name => "NAME=",
609             InfoType::OsVersion => "VERSION_ID=",
610         };
611 
612         for line in reader.lines().flatten() {
613             if let Some(stripped) = line.strip_prefix(info_str) {
614                 return Some(stripped.replace("\"", ""));
615             }
616         }
617     }
618 
619     // Fallback to `/etc/lsb-release` file for systems where VERSION_ID is not included.
620     // VERSION_ID is not required in the `/etc/os-release` file
621     // per https://www.linux.org/docs/man5/os-release.html
622     // If this fails for some reason, fallback to None
623     let reader = BufReader::new(File::open(fallback_path).ok()?);
624 
625     let info_str = match info {
626         InfoType::OsVersion => "DISTRIB_RELEASE=",
627         InfoType::Name => "DISTRIB_ID=",
628     };
629     for line in reader.lines().flatten() {
630         if let Some(stripped) = line.strip_prefix(info_str) {
631             return Some(stripped.replace("\"", ""));
632         }
633     }
634     None
635 }
636 
637 #[cfg(target_os = "android")]
638 fn get_system_info_android(info: InfoType) -> Option<String> {
639     use libc::c_int;
640 
641     // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/Build.java#58
642     let name: &'static [u8] = match info {
643         InfoType::Name => b"ro.product.model\0",
644         InfoType::OsVersion => b"ro.build.version.release\0",
645     };
646 
647     let mut value_buffer = vec![0u8; libc::PROP_VALUE_MAX as usize];
648     let len = unsafe {
649         libc::__system_property_get(
650             name.as_ptr() as *const c_char,
651             value_buffer.as_mut_ptr() as *mut c_char,
652         )
653     };
654 
655     if len != 0 {
656         if let Some(pos) = value_buffer.iter().position(|c| *c == 0) {
657             value_buffer.resize(pos, 0);
658         }
659         String::from_utf8(value_buffer).ok()
660     } else {
661         None
662     }
663 }
664 
665 fn get_secs_since_epoch() -> u64 {
666     match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
667         Ok(n) => n.as_secs(),
668         _ => panic!("SystemTime before UNIX EPOCH!"),
669     }
670 }
671 
672 #[cfg(test)]
673 mod test {
674     #[cfg(target_os = "android")]
675     use super::get_system_info_android;
676     #[cfg(not(target_os = "android"))]
677     use super::get_system_info_linux;
678     use super::InfoType;
679 
680     #[test]
681     #[cfg(target_os = "android")]
682     fn lsb_release_fallback_android() {
683         assert!(get_system_info_android(InfoType::OsVersion).is_some());
684         assert!(get_system_info_android(InfoType::Name).is_some());
685     }
686 
687     #[test]
688     #[cfg(not(target_os = "android"))]
689     fn lsb_release_fallback_not_android() {
690         use std::path::Path;
691 
692         let dir = tempfile::tempdir().expect("failed to create temporary directory");
693         let tmp1 = dir.path().join("tmp1");
694         let tmp2 = dir.path().join("tmp2");
695 
696         // /etc/os-release
697         std::fs::write(
698             &tmp1,
699             r#"NAME="Ubuntu"
700 VERSION="20.10 (Groovy Gorilla)"
701 ID=ubuntu
702 ID_LIKE=debian
703 PRETTY_NAME="Ubuntu 20.10"
704 VERSION_ID="20.10"
705 VERSION_CODENAME=groovy
706 UBUNTU_CODENAME=groovy
707 "#,
708         )
709         .expect("Failed to create tmp1");
710 
711         // /etc/lsb-release
712         std::fs::write(
713             &tmp2,
714             r#"DISTRIB_ID=Ubuntu
715 DISTRIB_RELEASE=20.10
716 DISTRIB_CODENAME=groovy
717 DISTRIB_DESCRIPTION="Ubuntu 20.10"
718 "#,
719         )
720         .expect("Failed to create tmp2");
721 
722         // Check for the "normal" path: "/etc/os-release"
723         assert_eq!(
724             get_system_info_linux(InfoType::OsVersion, &tmp1, Path::new("")),
725             Some("20.10".to_owned())
726         );
727         assert_eq!(
728             get_system_info_linux(InfoType::Name, &tmp1, Path::new("")),
729             Some("Ubuntu".to_owned())
730         );
731 
732         // Check for the "fallback" path: "/etc/lsb-release"
733         assert_eq!(
734             get_system_info_linux(InfoType::OsVersion, Path::new(""), &tmp2),
735             Some("20.10".to_owned())
736         );
737         assert_eq!(
738             get_system_info_linux(InfoType::Name, Path::new(""), &tmp2),
739             Some("Ubuntu".to_owned())
740         );
741     }
742 }
743