1 //
2 // Sysinfo
3 //
4 // Copyright (c) 2015 Guillaume Gomez
5 //
6 
7 use crate::sys::component::Component;
8 use crate::sys::disk::*;
9 use crate::sys::ffi;
10 use crate::sys::network::Networks;
11 use crate::sys::process::*;
12 use crate::sys::processor::*;
13 #[cfg(target_os = "macos")]
14 use core_foundation_sys::base::{kCFAllocatorDefault, CFRelease};
15 
16 use crate::{LoadAvg, Pid, ProcessorExt, RefreshKind, SystemExt, User};
17 
18 #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
19 use crate::ProcessExt;
20 
21 use std::cell::UnsafeCell;
22 use std::collections::HashMap;
23 use std::mem;
24 use std::sync::Arc;
25 
26 #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
27 use libc::size_t;
28 
29 use libc::{
30     c_char, c_int, c_void, host_statistics64, mach_port_t, mach_task_self, sysconf, sysctl,
31     sysctlbyname, timeval, vm_statistics64, _SC_PAGESIZE,
32 };
33 
34 /// Structs containing system's information.
35 pub struct System {
36     process_list: HashMap<Pid, Process>,
37     mem_total: u64,
38     mem_free: u64,
39     mem_available: u64,
40     swap_total: u64,
41     swap_free: u64,
42     global_processor: Processor,
43     processors: Vec<Processor>,
44     page_size_kb: u64,
45     components: Vec<Component>,
46     // Used to get CPU information, not supported on iOS, or inside the default macOS sandbox.
47     #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
48     connection: Option<ffi::io_connect_t>,
49     disks: Vec<Disk>,
50     networks: Networks,
51     port: mach_port_t,
52     users: Vec<User>,
53     boot_time: u64,
54     // Used to get disk information, to be more specific, it's needed by the
55     // DADiskCreateFromVolumePath function. Not supported on iOS.
56     #[cfg(target_os = "macos")]
57     session: ffi::SessionWrap,
58     #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
59     clock_info: Option<crate::sys::macos::system::SystemTimeInfo>,
60 }
61 
62 impl Drop for System {
drop(&mut self)63     fn drop(&mut self) {
64         #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
65         if let Some(conn) = self.connection {
66             unsafe {
67                 ffi::IOServiceClose(conn);
68             }
69         }
70 
71         #[cfg(target_os = "macos")]
72         if !self.session.0.is_null() {
73             unsafe {
74                 CFRelease(self.session.0 as _);
75             }
76         }
77     }
78 }
79 
80 pub(crate) struct Wrap<'a>(pub UnsafeCell<&'a mut HashMap<Pid, Process>>);
81 
82 unsafe impl<'a> Send for Wrap<'a> {}
83 unsafe impl<'a> Sync for Wrap<'a> {}
84 
85 #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
86 impl System {
clear_procs(&mut self)87     fn clear_procs(&mut self) {
88         use crate::sys::macos::process;
89 
90         let mut to_delete = Vec::new();
91 
92         for (pid, proc_) in &mut self.process_list {
93             if !process::has_been_updated(proc_) {
94                 to_delete.push(*pid);
95             }
96         }
97         for pid in to_delete {
98             self.process_list.remove(&pid);
99         }
100     }
101 }
102 
boot_time() -> u64103 fn boot_time() -> u64 {
104     let mut boot_time = timeval {
105         tv_sec: 0,
106         tv_usec: 0,
107     };
108     let mut len = std::mem::size_of::<timeval>();
109     let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME];
110     if unsafe {
111         sysctl(
112             mib.as_mut_ptr(),
113             2,
114             &mut boot_time as *mut timeval as *mut _,
115             &mut len,
116             std::ptr::null_mut(),
117             0,
118         )
119     } < 0
120     {
121         0
122     } else {
123         boot_time.tv_sec as _
124     }
125 }
126 
127 impl SystemExt for System {
128     const IS_SUPPORTED: bool = true;
129 
new_with_specifics(refreshes: RefreshKind) -> System130     fn new_with_specifics(refreshes: RefreshKind) -> System {
131         let port = unsafe { libc::mach_host_self() };
132         let (global_processor, processors) = init_processors(port);
133 
134         let mut s = System {
135             process_list: HashMap::with_capacity(200),
136             mem_total: 0,
137             mem_free: 0,
138             mem_available: 0,
139             swap_total: 0,
140             swap_free: 0,
141             global_processor,
142             processors,
143             page_size_kb: unsafe { sysconf(_SC_PAGESIZE) as u64 / 1_000 },
144             components: Vec::with_capacity(2),
145             #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
146             connection: get_io_service_connection(),
147             disks: Vec::with_capacity(1),
148             networks: Networks::new(),
149             port,
150             users: Vec::new(),
151             boot_time: boot_time(),
152             #[cfg(target_os = "macos")]
153             session: ffi::SessionWrap(::std::ptr::null_mut()),
154             #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
155             clock_info: crate::sys::macos::system::SystemTimeInfo::new(port),
156         };
157         s.refresh_specifics(refreshes);
158         s
159     }
160 
refresh_memory(&mut self)161     fn refresh_memory(&mut self) {
162         let mut mib = [0, 0];
163 
164         unsafe {
165             // get system values
166             // get swap info
167             let mut xs: libc::xsw_usage = mem::zeroed::<libc::xsw_usage>();
168             if get_sys_value(
169                 libc::CTL_VM as _,
170                 libc::VM_SWAPUSAGE as _,
171                 mem::size_of::<libc::xsw_usage>(),
172                 &mut xs as *mut _ as *mut c_void,
173                 &mut mib,
174             ) {
175                 self.swap_total = xs.xsu_total / 1_000;
176                 self.swap_free = xs.xsu_avail / 1_000;
177             }
178             // get ram info
179             if self.mem_total < 1 {
180                 get_sys_value(
181                     libc::CTL_HW as _,
182                     libc::HW_MEMSIZE as _,
183                     mem::size_of::<u64>(),
184                     &mut self.mem_total as *mut u64 as *mut c_void,
185                     &mut mib,
186                 );
187                 self.mem_total /= 1_000;
188             }
189             let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _;
190             let mut stat = mem::zeroed::<vm_statistics64>();
191             if host_statistics64(
192                 self.port,
193                 libc::HOST_VM_INFO64,
194                 &mut stat as *mut vm_statistics64 as *mut _,
195                 &mut count,
196             ) == libc::KERN_SUCCESS
197             {
198                 // From the apple documentation:
199                 //
200                 // /*
201                 //  * NB: speculative pages are already accounted for in "free_count",
202                 //  * so "speculative_count" is the number of "free" pages that are
203                 //  * used to hold data that was read speculatively from disk but
204                 //  * haven't actually been used by anyone so far.
205                 //  */
206                 self.mem_available = self.mem_total
207                     - (u64::from(stat.active_count)
208                         + u64::from(stat.inactive_count)
209                         + u64::from(stat.wire_count)
210                         + u64::from(stat.speculative_count)
211                         - u64::from(stat.purgeable_count))
212                         * self.page_size_kb;
213                 self.mem_free = u64::from(stat.free_count) * self.page_size_kb;
214             }
215         }
216     }
217 
218     #[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
refresh_components_list(&mut self)219     fn refresh_components_list(&mut self) {}
220 
221     #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
refresh_components_list(&mut self)222     fn refresh_components_list(&mut self) {
223         if let Some(con) = self.connection {
224             self.components.clear();
225             // getting CPU critical temperature
226             let critical_temp = crate::apple::component::get_temperature(
227                 con,
228                 &['T' as i8, 'C' as i8, '0' as i8, 'D' as i8, 0],
229             );
230 
231             for (id, v) in crate::apple::component::COMPONENTS_TEMPERATURE_IDS.iter() {
232                 if let Some(c) = Component::new((*id).to_owned(), None, critical_temp, v, con) {
233                     self.components.push(c);
234                 }
235             }
236         }
237     }
238 
refresh_cpu(&mut self)239     fn refresh_cpu(&mut self) {
240         let processors = &mut self.processors;
241         update_processor_usage(
242             self.port,
243             &mut self.global_processor,
244             |proc_data, cpu_info| {
245                 let mut percentage = 0f32;
246                 let mut offset = 0;
247                 for proc_ in processors.iter_mut() {
248                     let cpu_usage = compute_processor_usage(proc_, cpu_info, offset);
249                     proc_.update(cpu_usage, Arc::clone(&proc_data));
250                     percentage += proc_.cpu_usage();
251 
252                     offset += libc::CPU_STATE_MAX as isize;
253                 }
254                 (percentage, processors.len())
255             },
256         );
257     }
258 
259     #[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
refresh_processes(&mut self)260     fn refresh_processes(&mut self) {}
261 
262     #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
refresh_processes(&mut self)263     fn refresh_processes(&mut self) {
264         use crate::utils::into_iter;
265 
266         let count = unsafe { libc::proc_listallpids(::std::ptr::null_mut(), 0) };
267         if count < 1 {
268             return;
269         }
270         if let Some(pids) = get_proc_list() {
271             let arg_max = get_arg_max();
272             let port = self.port;
273             let time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port));
274             let entries: Vec<Process> = {
275                 let wrap = &Wrap(UnsafeCell::new(&mut self.process_list));
276 
277                 #[cfg(feature = "multithread")]
278                 use rayon::iter::ParallelIterator;
279 
280                 into_iter(pids)
281                     .flat_map(|pid| {
282                         match update_process(wrap, pid, arg_max as size_t, time_interval) {
283                             Ok(x) => x,
284                             _ => None,
285                         }
286                     })
287                     .collect()
288             };
289             entries.into_iter().for_each(|entry| {
290                 self.process_list.insert(entry.pid(), entry);
291             });
292             self.clear_procs();
293         }
294     }
295 
296     #[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
refresh_process(&mut self, _: Pid) -> bool297     fn refresh_process(&mut self, _: Pid) -> bool {
298         false
299     }
300 
301     #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
refresh_process(&mut self, pid: Pid) -> bool302     fn refresh_process(&mut self, pid: Pid) -> bool {
303         let arg_max = get_arg_max();
304         let port = self.port;
305         let time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port));
306         match {
307             let wrap = Wrap(UnsafeCell::new(&mut self.process_list));
308             update_process(&wrap, pid, arg_max as size_t, time_interval)
309         } {
310             Ok(Some(p)) => {
311                 self.process_list.insert(p.pid(), p);
312                 true
313             }
314             Ok(_) => true,
315             Err(_) => false,
316         }
317     }
318 
319     #[cfg(target_os = "ios")]
refresh_disks_list(&mut self)320     fn refresh_disks_list(&mut self) {}
321 
322     #[cfg(target_os = "macos")]
refresh_disks_list(&mut self)323     fn refresh_disks_list(&mut self) {
324         if self.session.0.is_null() {
325             self.session.0 = unsafe { ffi::DASessionCreate(kCFAllocatorDefault as _) };
326         }
327         self.disks = get_disks(self.session.0);
328     }
329 
refresh_users_list(&mut self)330     fn refresh_users_list(&mut self) {
331         self.users = crate::apple::users::get_users_list();
332     }
333 
334     // COMMON PART
335     //
336     // Need to be moved into a "common" file to avoid duplication.
337 
processes(&self) -> &HashMap<Pid, Process>338     fn processes(&self) -> &HashMap<Pid, Process> {
339         &self.process_list
340     }
341 
process(&self, pid: Pid) -> Option<&Process>342     fn process(&self, pid: Pid) -> Option<&Process> {
343         self.process_list.get(&pid)
344     }
345 
global_processor_info(&self) -> &Processor346     fn global_processor_info(&self) -> &Processor {
347         &self.global_processor
348     }
349 
processors(&self) -> &[Processor]350     fn processors(&self) -> &[Processor] {
351         &self.processors
352     }
353 
physical_core_count(&self) -> Option<usize>354     fn physical_core_count(&self) -> Option<usize> {
355         let mut physical_core_count = 0;
356 
357         if unsafe {
358             get_sys_value_by_name(
359                 b"hw.physicalcpu\0",
360                 &mut mem::size_of::<u32>(),
361                 &mut physical_core_count as *mut usize as *mut c_void,
362             )
363         } {
364             Some(physical_core_count)
365         } else {
366             None
367         }
368     }
369 
networks(&self) -> &Networks370     fn networks(&self) -> &Networks {
371         &self.networks
372     }
373 
networks_mut(&mut self) -> &mut Networks374     fn networks_mut(&mut self) -> &mut Networks {
375         &mut self.networks
376     }
377 
total_memory(&self) -> u64378     fn total_memory(&self) -> u64 {
379         self.mem_total
380     }
381 
free_memory(&self) -> u64382     fn free_memory(&self) -> u64 {
383         self.mem_free
384     }
385 
available_memory(&self) -> u64386     fn available_memory(&self) -> u64 {
387         self.mem_available
388     }
389 
used_memory(&self) -> u64390     fn used_memory(&self) -> u64 {
391         self.mem_total - self.mem_free
392     }
393 
total_swap(&self) -> u64394     fn total_swap(&self) -> u64 {
395         self.swap_total
396     }
397 
free_swap(&self) -> u64398     fn free_swap(&self) -> u64 {
399         self.swap_free
400     }
401 
402     // need to be checked
used_swap(&self) -> u64403     fn used_swap(&self) -> u64 {
404         self.swap_total - self.swap_free
405     }
406 
components(&self) -> &[Component]407     fn components(&self) -> &[Component] {
408         &self.components
409     }
410 
components_mut(&mut self) -> &mut [Component]411     fn components_mut(&mut self) -> &mut [Component] {
412         &mut self.components
413     }
414 
disks(&self) -> &[Disk]415     fn disks(&self) -> &[Disk] {
416         &self.disks
417     }
418 
disks_mut(&mut self) -> &mut [Disk]419     fn disks_mut(&mut self) -> &mut [Disk] {
420         &mut self.disks
421     }
422 
uptime(&self) -> u64423     fn uptime(&self) -> u64 {
424         let csec = unsafe { libc::time(::std::ptr::null_mut()) };
425 
426         unsafe { libc::difftime(csec, self.boot_time as _) as u64 }
427     }
428 
load_average(&self) -> LoadAvg429     fn load_average(&self) -> LoadAvg {
430         let mut loads = vec![0f64; 3];
431         unsafe {
432             libc::getloadavg(loads.as_mut_ptr(), 3);
433         }
434         LoadAvg {
435             one: loads[0],
436             five: loads[1],
437             fifteen: loads[2],
438         }
439     }
440 
users(&self) -> &[User]441     fn users(&self) -> &[User] {
442         &self.users
443     }
444 
boot_time(&self) -> u64445     fn boot_time(&self) -> u64 {
446         self.boot_time
447     }
448 
name(&self) -> Option<String>449     fn name(&self) -> Option<String> {
450         get_system_info(libc::KERN_OSTYPE, Some("Darwin"))
451     }
452 
long_os_version(&self) -> Option<String>453     fn long_os_version(&self) -> Option<String> {
454         #[cfg(target_os = "macos")]
455         let friendly_name = match self.os_version().unwrap_or_default() {
456             f_n if f_n.starts_with("10.16")
457                 | f_n.starts_with("11.0")
458                 | f_n.starts_with("11.1")
459                 | f_n.starts_with("11.2") =>
460             {
461                 "Big Sur"
462             }
463             f_n if f_n.starts_with("10.15") => "Catalina",
464             f_n if f_n.starts_with("10.14") => "Mojave",
465             f_n if f_n.starts_with("10.13") => "High Sierra",
466             f_n if f_n.starts_with("10.12") => "Sierra",
467             f_n if f_n.starts_with("10.11") => "El Capitan",
468             f_n if f_n.starts_with("10.10") => "Yosemite",
469             f_n if f_n.starts_with("10.9") => "Mavericks",
470             f_n if f_n.starts_with("10.8") => "Mountain Lion",
471             f_n if f_n.starts_with("10.7") => "Lion",
472             f_n if f_n.starts_with("10.6") => "Snow Leopard",
473             f_n if f_n.starts_with("10.5") => "Leopard",
474             f_n if f_n.starts_with("10.4") => "Tiger",
475             f_n if f_n.starts_with("10.3") => "Panther",
476             f_n if f_n.starts_with("10.2") => "Jaguar",
477             f_n if f_n.starts_with("10.1") => "Puma",
478             f_n if f_n.starts_with("10.0") => "Cheetah",
479             _ => "",
480         };
481 
482         #[cfg(target_os = "macos")]
483         let long_name = Some(format!(
484             "MacOS {} {}",
485             self.os_version().unwrap_or_default(),
486             friendly_name
487         ));
488 
489         #[cfg(target_os = "ios")]
490         let long_name = Some(format!("iOS {}", self.os_version().unwrap_or_default()));
491 
492         long_name
493     }
494 
host_name(&self) -> Option<String>495     fn host_name(&self) -> Option<String> {
496         get_system_info(libc::KERN_HOSTNAME, None)
497     }
498 
kernel_version(&self) -> Option<String>499     fn kernel_version(&self) -> Option<String> {
500         get_system_info(libc::KERN_OSRELEASE, None)
501     }
502 
os_version(&self) -> Option<String>503     fn os_version(&self) -> Option<String> {
504         unsafe {
505             // get the size for the buffer first
506             let mut size = 0;
507             if get_sys_value_by_name(b"kern.osproductversion\0", &mut size, std::ptr::null_mut())
508                 && size > 0
509             {
510                 // now create a buffer with the size and get the real value
511                 let mut buf = vec![0_u8; size as usize];
512 
513                 if get_sys_value_by_name(
514                     b"kern.osproductversion\0",
515                     &mut size,
516                     buf.as_mut_ptr() as *mut c_void,
517                 ) {
518                     if let Some(pos) = buf.iter().position(|x| *x == 0) {
519                         // Shrink buffer to terminate the null bytes
520                         buf.resize(pos, 0);
521                     }
522 
523                     String::from_utf8(buf).ok()
524                 } else {
525                     // getting the system value failed
526                     None
527                 }
528             } else {
529                 // getting the system value failed, or did not return a buffer size
530                 None
531             }
532         }
533     }
534 }
535 
536 impl Default for System {
default() -> System537     fn default() -> System {
538         System::new()
539     }
540 }
541 
542 // code from https://github.com/Chris911/iStats
543 // Not supported on iOS, or in the default macOS
544 #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
get_io_service_connection() -> Option<ffi::io_connect_t>545 fn get_io_service_connection() -> Option<ffi::io_connect_t> {
546     let mut master_port: mach_port_t = 0;
547     let mut iterator: ffi::io_iterator_t = 0;
548 
549     unsafe {
550         ffi::IOMasterPort(libc::MACH_PORT_NULL, &mut master_port);
551 
552         let matching_dictionary = ffi::IOServiceMatching(b"AppleSMC\0".as_ptr() as *const i8);
553         let result =
554             ffi::IOServiceGetMatchingServices(master_port, matching_dictionary, &mut iterator);
555         if result != ffi::KIO_RETURN_SUCCESS {
556             sysinfo_debug!("Error: IOServiceGetMatchingServices() = {}", result);
557             return None;
558         }
559 
560         let device = ffi::IOIteratorNext(iterator);
561         ffi::IOObjectRelease(iterator);
562         if device == 0 {
563             sysinfo_debug!("Error: no SMC found");
564             return None;
565         }
566 
567         let mut conn = 0;
568         let result = ffi::IOServiceOpen(device, mach_task_self(), 0, &mut conn);
569         ffi::IOObjectRelease(device);
570         if result != ffi::KIO_RETURN_SUCCESS {
571             sysinfo_debug!("Error: IOServiceOpen() = {}", result);
572             return None;
573         }
574 
575         Some(conn)
576     }
577 }
578 
579 #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
get_arg_max() -> usize580 fn get_arg_max() -> usize {
581     let mut mib: [c_int; 3] = [libc::CTL_KERN, libc::KERN_ARGMAX, 0];
582     let mut arg_max = 0i32;
583     let mut size = mem::size_of::<c_int>();
584     unsafe {
585         if sysctl(
586             mib.as_mut_ptr(),
587             2,
588             (&mut arg_max) as *mut i32 as *mut c_void,
589             &mut size,
590             std::ptr::null_mut(),
591             0,
592         ) == -1
593         {
594             4096 // We default to this value
595         } else {
596             arg_max as usize
597         }
598     }
599 }
600 
get_sys_value( high: u32, low: u32, mut len: usize, value: *mut c_void, mib: &mut [i32; 2], ) -> bool601 pub(crate) unsafe fn get_sys_value(
602     high: u32,
603     low: u32,
604     mut len: usize,
605     value: *mut c_void,
606     mib: &mut [i32; 2],
607 ) -> bool {
608     mib[0] = high as i32;
609     mib[1] = low as i32;
610     sysctl(
611         mib.as_mut_ptr(),
612         2,
613         value,
614         &mut len as *mut usize,
615         std::ptr::null_mut(),
616         0,
617     ) == 0
618 }
619 
get_sys_value_by_name(name: &[u8], len: &mut usize, value: *mut c_void) -> bool620 unsafe fn get_sys_value_by_name(name: &[u8], len: &mut usize, value: *mut c_void) -> bool {
621     sysctlbyname(
622         name.as_ptr() as *const c_char,
623         value,
624         len,
625         std::ptr::null_mut(),
626         0,
627     ) == 0
628 }
629 
get_system_info(value: c_int, default: Option<&str>) -> Option<String>630 fn get_system_info(value: c_int, default: Option<&str>) -> Option<String> {
631     let mut mib: [c_int; 2] = [libc::CTL_KERN, value];
632     let mut size = 0;
633 
634     // Call first to get size
635     unsafe {
636         sysctl(
637             mib.as_mut_ptr(),
638             2,
639             std::ptr::null_mut(),
640             &mut size,
641             std::ptr::null_mut(),
642             0,
643         )
644     };
645 
646     // exit early if we did not update the size
647     if size == 0 {
648         default.map(|s| s.to_owned())
649     } else {
650         // set the buffer to the correct size
651         let mut buf = vec![0_u8; size as usize];
652 
653         if unsafe {
654             sysctl(
655                 mib.as_mut_ptr(),
656                 2,
657                 buf.as_mut_ptr() as _,
658                 &mut size,
659                 std::ptr::null_mut(),
660                 0,
661             )
662         } == -1
663         {
664             // If command fails return default
665             default.map(|s| s.to_owned())
666         } else {
667             if let Some(pos) = buf.iter().position(|x| *x == 0) {
668                 // Shrink buffer to terminate the null bytes
669                 buf.resize(pos, 0);
670             }
671 
672             String::from_utf8(buf).ok()
673         }
674     }
675 }
676