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