diff options
Diffstat (limited to 'vendor/sysinfo/src/freebsd')
-rw-r--r-- | vendor/sysinfo/src/freebsd/component.rs | 71 | ||||
-rw-r--r-- | vendor/sysinfo/src/freebsd/cpu.rs | 44 | ||||
-rw-r--r-- | vendor/sysinfo/src/freebsd/disk.rs | 143 | ||||
-rw-r--r-- | vendor/sysinfo/src/freebsd/mod.rs | 16 | ||||
-rw-r--r-- | vendor/sysinfo/src/freebsd/network.rs | 199 | ||||
-rw-r--r-- | vendor/sysinfo/src/freebsd/process.rs | 254 | ||||
-rw-r--r-- | vendor/sysinfo/src/freebsd/system.rs | 803 | ||||
-rw-r--r-- | vendor/sysinfo/src/freebsd/utils.rs | 296 |
8 files changed, 1826 insertions, 0 deletions
diff --git a/vendor/sysinfo/src/freebsd/component.rs b/vendor/sysinfo/src/freebsd/component.rs new file mode 100644 index 000000000..6529be73c --- /dev/null +++ b/vendor/sysinfo/src/freebsd/component.rs @@ -0,0 +1,71 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use super::utils::get_sys_value_by_name; +use crate::ComponentExt; + +#[doc = include_str!("../../md_doc/component.md")] +pub struct Component { + id: Vec<u8>, + label: String, + temperature: f32, + max: f32, +} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + self.temperature + } + + fn max(&self) -> f32 { + self.max + } + + fn critical(&self) -> Option<f32> { + None + } + + fn label(&self) -> &str { + &self.label + } + + fn refresh(&mut self) { + unsafe { + if let Some(temperature) = refresh_component(&self.id) { + self.temperature = temperature; + if self.temperature > self.max { + self.max = self.temperature; + } + } + } + } +} + +unsafe fn refresh_component(id: &[u8]) -> Option<f32> { + let mut temperature: libc::c_int = 0; + if !get_sys_value_by_name(id, &mut temperature) { + None + } else { + // convert from Kelvin (x 10 -> 273.2 x 10) to Celsius + Some((temperature - 2732) as f32 / 10.) + } +} + +pub unsafe fn get_components(nb_cpus: usize) -> Vec<Component> { + // For now, we only have temperature for CPUs... + let mut components = Vec::with_capacity(nb_cpus); + + for core in 0..nb_cpus { + let id = format!("dev.cpu.{}.temperature\0", core) + .as_bytes() + .to_vec(); + if let Some(temperature) = refresh_component(&id) { + components.push(Component { + id, + label: format!("CPU {}", core + 1), + temperature, + max: temperature, + }); + } + } + components +} diff --git a/vendor/sysinfo/src/freebsd/cpu.rs b/vendor/sysinfo/src/freebsd/cpu.rs new file mode 100644 index 000000000..9d6c8e506 --- /dev/null +++ b/vendor/sysinfo/src/freebsd/cpu.rs @@ -0,0 +1,44 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::CpuExt; + +#[doc = include_str!("../../md_doc/cpu.md")] +pub struct Cpu { + pub(crate) cpu_usage: f32, + name: String, + pub(crate) vendor_id: String, + pub(crate) frequency: u64, +} + +impl Cpu { + pub(crate) fn new(name: String, vendor_id: String, frequency: u64) -> Cpu { + Cpu { + cpu_usage: 0., + name, + vendor_id, + frequency, + } + } +} + +impl CpuExt for Cpu { + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn name(&self) -> &str { + &self.name + } + + fn frequency(&self) -> u64 { + self.frequency + } + + fn vendor_id(&self) -> &str { + &self.vendor_id + } + + fn brand(&self) -> &str { + "" + } +} diff --git a/vendor/sysinfo/src/freebsd/disk.rs b/vendor/sysinfo/src/freebsd/disk.rs new file mode 100644 index 000000000..5156f1215 --- /dev/null +++ b/vendor/sysinfo/src/freebsd/disk.rs @@ -0,0 +1,143 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{DiskExt, DiskType}; + +use std::ffi::{OsStr, OsString}; +use std::path::{Path, PathBuf}; + +use super::utils::c_buf_to_str; + +#[doc = include_str!("../../md_doc/disk.md")] +pub struct Disk { + name: OsString, + c_mount_point: Vec<libc::c_char>, + mount_point: PathBuf, + total_space: u64, + available_space: u64, + file_system: Vec<u8>, + is_removable: bool, +} + +impl DiskExt for Disk { + fn type_(&self) -> DiskType { + DiskType::Unknown(-1) + } + + fn name(&self) -> &OsStr { + &self.name + } + + fn file_system(&self) -> &[u8] { + &self.file_system + } + + fn mount_point(&self) -> &Path { + &self.mount_point + } + + fn total_space(&self) -> u64 { + self.total_space + } + + fn available_space(&self) -> u64 { + self.available_space + } + + fn is_removable(&self) -> bool { + self.is_removable + } + + fn refresh(&mut self) -> bool { + unsafe { + let mut vfs: libc::statvfs = std::mem::zeroed(); + refresh_disk(self, &mut vfs) + } + } +} + +// FIXME: if you want to get disk I/O usage: +// statfs.[f_syncwrites, f_asyncwrites, f_syncreads, f_asyncreads] + +unsafe fn refresh_disk(disk: &mut Disk, vfs: &mut libc::statvfs) -> bool { + if libc::statvfs(disk.c_mount_point.as_ptr() as *const _, vfs) < 0 { + return false; + } + let f_frsize: u64 = vfs.f_frsize as _; + + disk.total_space = vfs.f_blocks.saturating_mul(f_frsize); + disk.available_space = vfs.f_favail.saturating_mul(f_frsize); + true +} + +pub unsafe fn get_all_disks() -> Vec<Disk> { + let mut fs_infos: *mut libc::statfs = std::ptr::null_mut(); + + let count = libc::getmntinfo(&mut fs_infos, libc::MNT_WAIT); + + if count < 1 { + return Vec::new(); + } + let mut vfs: libc::statvfs = std::mem::zeroed(); + let fs_infos: &[libc::statfs] = std::slice::from_raw_parts(fs_infos as _, count as _); + let mut disks = Vec::new(); + + for fs_info in fs_infos { + if fs_info.f_mntfromname[0] == 0 || fs_info.f_mntonname[0] == 0 { + // If we have missing information, no need to look any further... + continue; + } + let fs_type: &[libc::c_char] = + if let Some(pos) = fs_info.f_fstypename.iter().position(|x| *x == 0) { + &fs_info.f_fstypename[..pos] + } else { + &fs_info.f_fstypename + }; + let fs_type: &[u8] = std::slice::from_raw_parts(fs_type.as_ptr() as _, fs_type.len()); + match fs_type { + b"autofs" | b"devfs" | b"linprocfs" | b"procfs" | b"fdesckfs" | b"tmpfs" + | b"linsysfs" => { + sysinfo_debug!( + "Memory filesystem `{:?}`, ignoring it.", + c_buf_to_str(&fs_info.f_fstypename).unwrap(), + ); + continue; + } + _ => {} + } + + if libc::statvfs(fs_info.f_mntonname.as_ptr(), &mut vfs) != 0 { + continue; + } + + let mount_point = match c_buf_to_str(&fs_info.f_mntonname) { + Some(m) => m, + None => { + sysinfo_debug!("Cannot get disk mount point, ignoring it."); + continue; + } + }; + + let name = if mount_point == "/" { + OsString::from("root") + } else { + OsString::from(mount_point) + }; + + // USB keys and CDs are removable. + let is_removable = + [b"USB", b"usb"].iter().any(|b| b == &fs_type) || fs_type.starts_with(b"/dev/cd"); + + let f_frsize: u64 = vfs.f_frsize as _; + + disks.push(Disk { + name, + c_mount_point: fs_info.f_mntonname.to_vec(), + mount_point: PathBuf::from(mount_point), + total_space: vfs.f_blocks.saturating_mul(f_frsize), + available_space: vfs.f_favail.saturating_mul(f_frsize), + file_system: fs_type.to_vec(), + is_removable, + }); + } + disks +} diff --git a/vendor/sysinfo/src/freebsd/mod.rs b/vendor/sysinfo/src/freebsd/mod.rs new file mode 100644 index 000000000..8a8726812 --- /dev/null +++ b/vendor/sysinfo/src/freebsd/mod.rs @@ -0,0 +1,16 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +pub mod component; +pub mod cpu; +pub mod disk; +pub mod network; +pub mod process; +pub mod system; +mod utils; + +pub use self::component::Component; +pub use self::cpu::Cpu; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; +pub use self::process::Process; +pub use self::system::System; diff --git a/vendor/sysinfo/src/freebsd/network.rs b/vendor/sysinfo/src/freebsd/network.rs new file mode 100644 index 000000000..e58ad823a --- /dev/null +++ b/vendor/sysinfo/src/freebsd/network.rs @@ -0,0 +1,199 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::collections::{hash_map, HashMap}; +use std::mem::MaybeUninit; + +use super::utils; +use crate::{NetworkExt, NetworksExt, NetworksIter}; + +macro_rules! old_and_new { + ($ty_:expr, $name:ident, $old:ident, $data:expr) => {{ + $ty_.$old = $ty_.$name; + $ty_.$name = $data.$name; + }}; +} + +#[doc = include_str!("../../md_doc/networks.md")] +pub struct Networks { + interfaces: HashMap<String, NetworkData>, +} + +impl Networks { + pub(crate) fn new() -> Networks { + Networks { + interfaces: HashMap::new(), + } + } +} + +impl NetworksExt for Networks { + fn iter(&self) -> NetworksIter { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh_networks_list(&mut self) { + unsafe { + self.refresh_interfaces(true); + } + // Remove interfaces which are gone. + self.interfaces.retain(|_, n| n.updated); + } + + fn refresh(&mut self) { + unsafe { + self.refresh_interfaces(false); + } + } +} + +impl Networks { + unsafe fn refresh_interfaces(&mut self, refresh_all: bool) { + let mut nb_interfaces: libc::c_int = 0; + if !utils::get_sys_value( + &[ + libc::CTL_NET, + libc::PF_LINK, + libc::NETLINK_GENERIC, + libc::IFMIB_SYSTEM, + libc::IFMIB_IFCOUNT, + ], + &mut nb_interfaces, + ) { + return; + } + if refresh_all { + // We don't need to update this value if we're not updating all interfaces. + for interface in self.interfaces.values_mut() { + interface.updated = false; + } + } + let mut data: libc::ifmibdata = MaybeUninit::zeroed().assume_init(); + for row in 1..nb_interfaces { + let mib = [ + libc::CTL_NET, + libc::PF_LINK, + libc::NETLINK_GENERIC, + libc::IFMIB_IFDATA, + row, + libc::IFDATA_GENERAL, + ]; + + if !utils::get_sys_value(&mib, &mut data) { + continue; + } + if let Some(name) = utils::c_buf_to_string(&data.ifmd_name) { + let data = &data.ifmd_data; + match self.interfaces.entry(name) { + hash_map::Entry::Occupied(mut e) => { + let mut interface = e.get_mut(); + + old_and_new!(interface, ifi_ibytes, old_ifi_ibytes, data); + old_and_new!(interface, ifi_obytes, old_ifi_obytes, data); + old_and_new!(interface, ifi_ipackets, old_ifi_ipackets, data); + old_and_new!(interface, ifi_opackets, old_ifi_opackets, data); + old_and_new!(interface, ifi_ierrors, old_ifi_ierrors, data); + old_and_new!(interface, ifi_oerrors, old_ifi_oerrors, data); + interface.updated = true; + } + hash_map::Entry::Vacant(e) => { + if !refresh_all { + // This is simply a refresh, we don't want to add new interfaces! + continue; + } + e.insert(NetworkData { + ifi_ibytes: data.ifi_ibytes, + old_ifi_ibytes: 0, + ifi_obytes: data.ifi_obytes, + old_ifi_obytes: 0, + ifi_ipackets: data.ifi_ipackets, + old_ifi_ipackets: 0, + ifi_opackets: data.ifi_opackets, + old_ifi_opackets: 0, + ifi_ierrors: data.ifi_ierrors, + old_ifi_ierrors: 0, + ifi_oerrors: data.ifi_oerrors, + old_ifi_oerrors: 0, + updated: true, + }); + } + } + } + } + } +} + +#[doc = include_str!("../../md_doc/network_data.md")] +pub struct NetworkData { + /// Total number of bytes received over interface. + ifi_ibytes: u64, + old_ifi_ibytes: u64, + /// Total number of bytes transmitted over interface. + ifi_obytes: u64, + old_ifi_obytes: u64, + /// Total number of packets received. + ifi_ipackets: u64, + old_ifi_ipackets: u64, + /// Total number of packets transmitted. + ifi_opackets: u64, + old_ifi_opackets: u64, + /// Shows the total number of packets received with error. This includes + /// too-long-frames errors, ring-buffer overflow errors, CRC errors, + /// frame alignment errors, fifo overruns, and missed packets. + ifi_ierrors: u64, + old_ifi_ierrors: u64, + /// similar to `ifi_ierrors` + ifi_oerrors: u64, + old_ifi_oerrors: u64, + /// Whether or not the above data has been updated during refresh + updated: bool, +} + +impl NetworkExt for NetworkData { + fn received(&self) -> u64 { + self.ifi_ibytes.saturating_sub(self.old_ifi_ibytes) + } + + fn total_received(&self) -> u64 { + self.ifi_ibytes + } + + fn transmitted(&self) -> u64 { + self.ifi_obytes.saturating_sub(self.old_ifi_obytes) + } + + fn total_transmitted(&self) -> u64 { + self.ifi_obytes + } + + fn packets_received(&self) -> u64 { + self.ifi_ipackets.saturating_sub(self.old_ifi_ipackets) + } + + fn total_packets_received(&self) -> u64 { + self.ifi_ipackets + } + + fn packets_transmitted(&self) -> u64 { + self.ifi_opackets.saturating_sub(self.old_ifi_opackets) + } + + fn total_packets_transmitted(&self) -> u64 { + self.ifi_opackets + } + + fn errors_on_received(&self) -> u64 { + self.ifi_ierrors.saturating_sub(self.old_ifi_ierrors) + } + + fn total_errors_on_received(&self) -> u64 { + self.ifi_ierrors + } + + fn errors_on_transmitted(&self) -> u64 { + self.ifi_oerrors.saturating_sub(self.old_ifi_oerrors) + } + + fn total_errors_on_transmitted(&self) -> u64 { + self.ifi_oerrors + } +} diff --git a/vendor/sysinfo/src/freebsd/process.rs b/vendor/sysinfo/src/freebsd/process.rs new file mode 100644 index 000000000..b3302edbe --- /dev/null +++ b/vendor/sysinfo/src/freebsd/process.rs @@ -0,0 +1,254 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; + +use std::fmt; +use std::path::{Path, PathBuf}; + +use super::utils::{get_sys_value_str, WrapMap}; + +#[doc(hidden)] +impl From<libc::c_char> for ProcessStatus { + fn from(status: libc::c_char) -> ProcessStatus { + match status { + libc::SIDL => ProcessStatus::Idle, + libc::SRUN => ProcessStatus::Run, + libc::SSLEEP => ProcessStatus::Sleep, + libc::SSTOP => ProcessStatus::Stop, + libc::SZOMB => ProcessStatus::Zombie, + libc::SWAIT => ProcessStatus::Dead, + libc::SLOCK => ProcessStatus::LockBlocked, + x => ProcessStatus::Unknown(x as _), + } + } +} + +impl fmt::Display for ProcessStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + ProcessStatus::Idle => "Idle", + ProcessStatus::Run => "Runnable", + ProcessStatus::Sleep => "Sleeping", + ProcessStatus::Stop => "Stopped", + ProcessStatus::Zombie => "Zombie", + ProcessStatus::Dead => "Dead", + ProcessStatus::LockBlocked => "LockBlocked", + _ => "Unknown", + }) + } +} + +#[doc = include_str!("../../md_doc/process.md")] +pub struct Process { + pub(crate) name: String, + pub(crate) cmd: Vec<String>, + pub(crate) exe: PathBuf, + pub(crate) pid: Pid, + parent: Option<Pid>, + pub(crate) environ: Vec<String>, + pub(crate) cwd: PathBuf, + pub(crate) root: PathBuf, + pub(crate) memory: u64, + pub(crate) virtual_memory: u64, + pub(crate) updated: bool, + cpu_usage: f32, + start_time: u64, + run_time: u64, + pub(crate) status: ProcessStatus, + user_id: Uid, + group_id: Gid, + read_bytes: u64, + old_read_bytes: u64, + written_bytes: u64, + old_written_bytes: u64, +} + +impl ProcessExt for Process { + fn kill_with(&self, signal: Signal) -> Option<bool> { + let c_signal = super::system::convert_signal(signal)?; + unsafe { Some(libc::kill(self.pid.0, c_signal) == 0) } + } + + fn name(&self) -> &str { + &self.name + } + + fn cmd(&self) -> &[String] { + &self.cmd + } + + fn exe(&self) -> &Path { + self.exe.as_path() + } + + fn pid(&self) -> Pid { + self.pid + } + + fn environ(&self) -> &[String] { + &self.environ + } + + fn cwd(&self) -> &Path { + self.cwd.as_path() + } + + fn root(&self) -> &Path { + self.root.as_path() + } + + fn memory(&self) -> u64 { + self.memory + } + + fn virtual_memory(&self) -> u64 { + self.virtual_memory + } + + fn parent(&self) -> Option<Pid> { + self.parent + } + + fn status(&self) -> ProcessStatus { + self.status + } + + fn start_time(&self) -> u64 { + self.start_time + } + + fn run_time(&self) -> u64 { + self.run_time + } + + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn disk_usage(&self) -> DiskUsage { + DiskUsage { + written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes), + total_written_bytes: self.written_bytes, + read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes), + total_read_bytes: self.read_bytes, + } + } + + fn user_id(&self) -> Option<&Uid> { + Some(&self.user_id) + } + + fn group_id(&self) -> Option<Gid> { + Some(self.group_id) + } +} + +pub(crate) unsafe fn get_process_data( + kproc: &libc::kinfo_proc, + wrap: &WrapMap, + page_size: isize, + fscale: f32, + now: u64, + refresh_kind: ProcessRefreshKind, +) -> Result<Option<Process>, ()> { + if kproc.ki_pid != 1 && (kproc.ki_flag as libc::c_int & libc::P_SYSTEM) != 0 { + // We filter out the kernel threads. + return Err(()); + } + + // We now get the values needed for both new and existing process. + let cpu_usage = if refresh_kind.cpu() { + (100 * kproc.ki_pctcpu) as f32 / fscale + } else { + 0. + }; + // Processes can be reparented apparently? + let parent = if kproc.ki_ppid != 0 { + Some(Pid(kproc.ki_ppid)) + } else { + None + }; + let status = ProcessStatus::from(kproc.ki_stat); + + // from FreeBSD source /src/usr.bin/top/machine.c + let virtual_memory = (kproc.ki_size / 1_000) as u64; + let memory = (kproc.ki_rssize as u64).saturating_mul(page_size as _) / 1_000; + // FIXME: This is to get the "real" run time (in micro-seconds). + // let run_time = (kproc.ki_runtime + 5_000) / 10_000; + + if let Some(proc_) = (*wrap.0.get()).get_mut(&Pid(kproc.ki_pid)) { + proc_.cpu_usage = cpu_usage; + proc_.parent = parent; + proc_.status = status; + proc_.virtual_memory = virtual_memory; + proc_.memory = memory; + proc_.run_time = now.saturating_sub(proc_.start_time); + proc_.updated = true; + + if refresh_kind.disk_usage() { + proc_.old_read_bytes = proc_.read_bytes; + proc_.read_bytes = kproc.ki_rusage.ru_inblock as _; + proc_.old_written_bytes = proc_.written_bytes; + proc_.written_bytes = kproc.ki_rusage.ru_oublock as _; + } + + return Ok(None); + } + + // This is a new process, we need to get more information! + let mut buffer = [0; libc::PATH_MAX as usize + 1]; + + let exe = get_sys_value_str( + &[ + libc::CTL_KERN, + libc::KERN_PROC, + libc::KERN_PROC_PATHNAME, + kproc.ki_pid, + ], + &mut buffer, + ) + .unwrap_or_default(); + // For some reason, it can return completely invalid path like `p\u{5}`. So we need to use + // procstat to get around this problem. + // let cwd = get_sys_value_str( + // &[ + // libc::CTL_KERN, + // libc::KERN_PROC, + // libc::KERN_PROC_CWD, + // kproc.ki_pid, + // ], + // &mut buffer, + // ) + // .map(|s| s.into()) + // .unwrap_or_else(PathBuf::new); + + let start_time = kproc.ki_start.tv_sec as u64; + Ok(Some(Process { + pid: Pid(kproc.ki_pid), + parent, + user_id: Uid(kproc.ki_ruid), + group_id: Gid(kproc.ki_rgid), + start_time, + run_time: now.saturating_sub(start_time), + cpu_usage, + virtual_memory, + memory, + // procstat_getfiles + cwd: PathBuf::new(), + exe: exe.into(), + // kvm_getargv isn't thread-safe so we get it in the main thread. + name: String::new(), + // kvm_getargv isn't thread-safe so we get it in the main thread. + cmd: Vec::new(), + // kvm_getargv isn't thread-safe so we get it in the main thread. + root: PathBuf::new(), + // kvm_getenvv isn't thread-safe so we get it in the main thread. + environ: Vec::new(), + status, + read_bytes: kproc.ki_rusage.ru_inblock as _, + old_read_bytes: 0, + written_bytes: kproc.ki_rusage.ru_oublock as _, + old_written_bytes: 0, + updated: true, + })) +} diff --git a/vendor/sysinfo/src/freebsd/system.rs b/vendor/sysinfo/src/freebsd/system.rs new file mode 100644 index 000000000..16da2de50 --- /dev/null +++ b/vendor/sysinfo/src/freebsd/system.rs @@ -0,0 +1,803 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + sys::{component::Component, Cpu, Disk, Networks, Process}, + CpuRefreshKind, LoadAvg, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, +}; + +use std::cell::UnsafeCell; +use std::collections::HashMap; +use std::ffi::CStr; +use std::mem::MaybeUninit; +use std::path::{Path, PathBuf}; +use std::ptr::NonNull; + +use super::utils::{ + self, boot_time, c_buf_to_string, from_cstr_array, get_frequency_for_cpu, get_sys_value, + get_sys_value_array, get_sys_value_by_name, get_sys_value_str_by_name, get_system_info, + init_mib, +}; + +use libc::c_int; + +declare_signals! { + c_int, + Signal::Hangup => libc::SIGHUP, + Signal::Interrupt => libc::SIGINT, + Signal::Quit => libc::SIGQUIT, + Signal::Illegal => libc::SIGILL, + Signal::Trap => libc::SIGTRAP, + Signal::Abort => libc::SIGABRT, + Signal::IOT => libc::SIGIOT, + Signal::Bus => libc::SIGBUS, + Signal::FloatingPointException => libc::SIGFPE, + Signal::Kill => libc::SIGKILL, + Signal::User1 => libc::SIGUSR1, + Signal::Segv => libc::SIGSEGV, + Signal::User2 => libc::SIGUSR2, + Signal::Pipe => libc::SIGPIPE, + Signal::Alarm => libc::SIGALRM, + Signal::Term => libc::SIGTERM, + Signal::Child => libc::SIGCHLD, + Signal::Continue => libc::SIGCONT, + Signal::Stop => libc::SIGSTOP, + Signal::TSTP => libc::SIGTSTP, + Signal::TTIN => libc::SIGTTIN, + Signal::TTOU => libc::SIGTTOU, + Signal::Urgent => libc::SIGURG, + Signal::XCPU => libc::SIGXCPU, + Signal::XFSZ => libc::SIGXFSZ, + Signal::VirtualAlarm => libc::SIGVTALRM, + Signal::Profiling => libc::SIGPROF, + Signal::Winch => libc::SIGWINCH, + Signal::IO => libc::SIGIO, + Signal::Sys => libc::SIGSYS, + _ => None, +} + +#[doc = include_str!("../../md_doc/system.md")] +pub struct System { + process_list: HashMap<Pid, Process>, + mem_total: u64, + mem_free: u64, + mem_used: u64, + swap_total: u64, + swap_used: u64, + global_cpu: Cpu, + cpus: Vec<Cpu>, + components: Vec<Component>, + disks: Vec<Disk>, + networks: Networks, + users: Vec<User>, + boot_time: u64, + system_info: SystemInfo, + got_cpu_frequency: bool, +} + +impl SystemExt for System { + const IS_SUPPORTED: bool = true; + const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); + + fn new_with_specifics(refreshes: RefreshKind) -> System { + let system_info = SystemInfo::new(); + + let mut s = System { + process_list: HashMap::with_capacity(200), + mem_total: 0, + mem_free: 0, + mem_used: 0, + swap_total: 0, + swap_used: 0, + global_cpu: Cpu::new(String::new(), String::new(), 0), + cpus: Vec::with_capacity(system_info.nb_cpus as _), + components: Vec::with_capacity(2), + disks: Vec::with_capacity(1), + networks: Networks::new(), + users: Vec::new(), + boot_time: boot_time(), + system_info, + got_cpu_frequency: false, + }; + s.refresh_specifics(refreshes); + s + } + + fn refresh_memory(&mut self) { + if self.mem_total == 0 { + self.mem_total = self.system_info.get_total_memory(); + } + self.mem_used = self.system_info.get_used_memory(); + self.mem_free = self.system_info.get_free_memory(); + let (swap_used, swap_total) = self.system_info.get_swap_info(); + self.swap_total = swap_total; + self.swap_used = swap_used; + } + + fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { + if self.cpus.is_empty() { + let mut frequency = 0; + + // We get the CPU vendor ID in here. + let vendor_id = + get_sys_value_str_by_name(b"hw.model\0").unwrap_or_else(|| "<unknown>".to_owned()); + for pos in 0..self.system_info.nb_cpus { + if refresh_kind.frequency() { + unsafe { + frequency = get_frequency_for_cpu(pos); + } + } + self.cpus.push(Cpu::new( + format!("cpu {}", pos), + vendor_id.clone(), + frequency, + )); + } + self.global_cpu.vendor_id = vendor_id; + self.got_cpu_frequency = refresh_kind.frequency(); + } else if refresh_kind.frequency() && !self.got_cpu_frequency { + for (pos, proc_) in self.cpus.iter_mut().enumerate() { + unsafe { + proc_.frequency = get_frequency_for_cpu(pos as _); + } + } + self.got_cpu_frequency = true; + } + if refresh_kind.cpu_usage() { + self.system_info + .get_cpu_usage(&mut self.global_cpu, &mut self.cpus); + } + } + + fn refresh_components_list(&mut self) { + if self.cpus.is_empty() { + self.refresh_cpu(); + } + self.components = unsafe { super::component::get_components(self.cpus.len()) }; + } + + fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { + unsafe { self.refresh_procs(refresh_kind) } + } + + fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { + unsafe { + let kd = self.system_info.kd.as_ptr(); + let mut count = 0; + let procs = libc::kvm_getprocs(kd, libc::KERN_PROC_PROC, 0, &mut count); + if count < 1 { + sysinfo_debug!("kvm_getprocs returned nothing..."); + return false; + } + let now = super::utils::get_now(); + + let fscale = self.system_info.fscale; + let page_size = self.system_info.page_size as isize; + let proc_list = utils::WrapMap(UnsafeCell::new(&mut self.process_list)); + let procs: &mut [utils::KInfoProc] = + std::slice::from_raw_parts_mut(procs as _, count as _); + + #[cfg(feature = "multithread")] + use rayon::iter::ParallelIterator; + + macro_rules! multi_iter { + ($name:ident, $($iter:tt)+) => { + $name = crate::utils::into_iter(procs).$($iter)+; + } + } + + let ret; + #[cfg(not(feature = "multithread"))] + multi_iter!(ret, find(|kproc| kproc.ki_pid == pid.0)); + #[cfg(feature = "multithread")] + multi_iter!(ret, find_any(|kproc| kproc.ki_pid == pid.0)); + + let kproc = if let Some(kproc) = ret { + kproc + } else { + return false; + }; + match super::process::get_process_data( + kproc, + &proc_list, + page_size, + fscale, + now, + refresh_kind, + ) { + Ok(Some(proc_)) => { + self.add_missing_proc_info(self.system_info.kd.as_ptr(), kproc, proc_); + true + } + Ok(None) => true, + Err(_) => false, + } + } + } + + fn refresh_disks_list(&mut self) { + self.disks = unsafe { super::disk::get_all_disks() }; + } + + fn refresh_users_list(&mut self) { + self.users = crate::users::get_users_list(); + } + + // COMMON PART + // + // Need to be moved into a "common" file to avoid duplication. + + fn processes(&self) -> &HashMap<Pid, Process> { + &self.process_list + } + + fn process(&self, pid: Pid) -> Option<&Process> { + self.process_list.get(&pid) + } + + fn networks(&self) -> &Networks { + &self.networks + } + + fn networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn global_cpu_info(&self) -> &Cpu { + &self.global_cpu + } + + fn cpus(&self) -> &[Cpu] { + &self.cpus + } + + fn physical_core_count(&self) -> Option<usize> { + let mut physical_core_count: u32 = 0; + + unsafe { + if get_sys_value_by_name(b"hw.ncpu\0", &mut physical_core_count) { + Some(physical_core_count as _) + } else { + None + } + } + } + + fn total_memory(&self) -> u64 { + self.mem_total + } + + fn free_memory(&self) -> u64 { + self.mem_free + } + + fn available_memory(&self) -> u64 { + self.mem_free + } + + fn used_memory(&self) -> u64 { + self.mem_used + } + + fn total_swap(&self) -> u64 { + self.swap_total + } + + fn free_swap(&self) -> u64 { + self.swap_total - self.swap_used + } + + // TODO: need to be checked + fn used_swap(&self) -> u64 { + self.swap_used + } + + fn components(&self) -> &[Component] { + &self.components + } + + fn components_mut(&mut self) -> &mut [Component] { + &mut self.components + } + + fn disks(&self) -> &[Disk] { + &self.disks + } + + fn disks_mut(&mut self) -> &mut [Disk] { + &mut self.disks + } + + fn uptime(&self) -> u64 { + unsafe { + let csec = libc::time(::std::ptr::null_mut()); + + libc::difftime(csec, self.boot_time as _) as u64 + } + } + + fn boot_time(&self) -> u64 { + self.boot_time + } + + fn load_average(&self) -> LoadAvg { + let mut loads = vec![0f64; 3]; + unsafe { + libc::getloadavg(loads.as_mut_ptr(), 3); + LoadAvg { + one: loads[0], + five: loads[1], + fifteen: loads[2], + } + } + } + + fn users(&self) -> &[User] { + &self.users + } + + fn name(&self) -> Option<String> { + self.system_info.get_os_name() + } + + fn long_os_version(&self) -> Option<String> { + self.system_info.get_os_release_long() + } + + fn host_name(&self) -> Option<String> { + self.system_info.get_hostname() + } + + fn kernel_version(&self) -> Option<String> { + self.system_info.get_kernel_version() + } + + fn os_version(&self) -> Option<String> { + self.system_info.get_os_release() + } +} + +impl Default for System { + fn default() -> Self { + Self::new() + } +} + +impl System { + unsafe fn refresh_procs(&mut self, refresh_kind: ProcessRefreshKind) { + let kd = self.system_info.kd.as_ptr(); + let procs = { + let mut count = 0; + let procs = libc::kvm_getprocs(kd, libc::KERN_PROC_PROC, 0, &mut count); + if count < 1 { + sysinfo_debug!("kvm_getprocs returned nothing..."); + return; + } + #[cfg(feature = "multithread")] + use rayon::iter::{ParallelIterator, ParallelIterator as IterTrait}; + #[cfg(not(feature = "multithread"))] + use std::iter::Iterator as IterTrait; + + crate::utils::into_iter(&mut self.process_list).for_each(|(_, proc_)| { + proc_.updated = false; + }); + + let fscale = self.system_info.fscale; + let page_size = self.system_info.page_size as isize; + let now = super::utils::get_now(); + let proc_list = utils::WrapMap(UnsafeCell::new(&mut self.process_list)); + let procs: &mut [utils::KInfoProc] = + std::slice::from_raw_parts_mut(procs as _, count as _); + + IterTrait::filter_map(crate::utils::into_iter(procs), |kproc| { + super::process::get_process_data( + kproc, + &proc_list, + page_size, + fscale, + now, + refresh_kind, + ) + .ok() + .and_then(|p| p.map(|p| (kproc, p))) + }) + .collect::<Vec<_>>() + }; + + // We remove all processes that don't exist anymore. + self.process_list.retain(|_, v| v.updated); + + for (kproc, proc_) in procs { + self.add_missing_proc_info(kd, kproc, proc_); + } + } + + unsafe fn add_missing_proc_info( + &mut self, + kd: *mut libc::kvm_t, + kproc: &libc::kinfo_proc, + mut proc_: Process, + ) { + proc_.cmd = from_cstr_array(libc::kvm_getargv(kd, kproc, 0) as _); + self.system_info.get_proc_missing_info(kproc, &mut proc_); + if !proc_.cmd.is_empty() { + // First, we try to retrieve the name from the command line. + let p = Path::new(&proc_.cmd[0]); + if let Some(name) = p.file_name().and_then(|s| s.to_str()) { + proc_.name = name.to_owned(); + } + if proc_.root.as_os_str().is_empty() { + if let Some(parent) = p.parent() { + proc_.root = parent.to_path_buf(); + } + } + } + if proc_.name.is_empty() { + // The name can be cut short because the `ki_comm` field size is limited, + // which is why we prefer to get the name from the command line as much as + // possible. + proc_.name = c_buf_to_string(&kproc.ki_comm).unwrap_or_default(); + } + proc_.environ = from_cstr_array(libc::kvm_getenvv(kd, kproc, 0) as _); + self.process_list.insert(proc_.pid, proc_); + } +} + +#[derive(Debug)] +struct Zfs { + enabled: bool, + mib_arcstats_size: [c_int; 5], +} + +impl Zfs { + fn new() -> Self { + let mut zfs = Self { + enabled: false, + mib_arcstats_size: Default::default(), + }; + unsafe { + init_mib( + b"kstat.zfs.misc.arcstats.size\0", + &mut zfs.mib_arcstats_size, + ); + let mut arc_size: u64 = 0; + if get_sys_value(&zfs.mib_arcstats_size, &mut arc_size) { + zfs.enabled = arc_size != 0; + } + } + zfs + } + + fn arc_size(&self) -> Option<u64> { + if self.enabled { + let mut arc_size: u64 = 0; + unsafe { + get_sys_value(&self.mib_arcstats_size, &mut arc_size); + Some(arc_size) + } + } else { + None + } + } +} + +/// This struct is used to get system information more easily. +#[derive(Debug)] +struct SystemInfo { + hw_physical_memory: [c_int; 2], + page_size: c_int, + virtual_page_count: [c_int; 4], + virtual_wire_count: [c_int; 4], + virtual_active_count: [c_int; 4], + virtual_cache_count: [c_int; 4], + virtual_inactive_count: [c_int; 4], + virtual_free_count: [c_int; 4], + os_type: [c_int; 2], + os_release: [c_int; 2], + kern_version: [c_int; 2], + hostname: [c_int; 2], + buf_space: [c_int; 2], + nb_cpus: c_int, + kd: NonNull<libc::kvm_t>, + // For these two fields, we could use `kvm_getcptime` but the function isn't very efficient... + mib_cp_time: [c_int; 2], + mib_cp_times: [c_int; 2], + // For the global CPU usage. + cp_time: utils::VecSwitcher<libc::c_ulong>, + // For each CPU usage. + cp_times: utils::VecSwitcher<libc::c_ulong>, + /// From FreeBSD manual: "The kernel fixed-point scale factor". It's used when computing + /// processes' CPU usage. + fscale: f32, + procstat: *mut libc::procstat, + zfs: Zfs, +} + +// This is needed because `kd: *mut libc::kvm_t` isn't thread-safe. +unsafe impl Send for SystemInfo {} +unsafe impl Sync for SystemInfo {} + +impl SystemInfo { + fn new() -> Self { + unsafe { + let mut errbuf = + MaybeUninit::<[libc::c_char; libc::_POSIX2_LINE_MAX as usize]>::uninit(); + let kd = NonNull::new(libc::kvm_openfiles( + std::ptr::null(), + b"/dev/null\0".as_ptr() as *const _, + std::ptr::null(), + 0, + errbuf.as_mut_ptr() as *mut _, + )) + .expect("kvm_openfiles failed"); + + let mut smp: c_int = 0; + let mut nb_cpus: c_int = 1; + if !get_sys_value_by_name(b"kern.smp.active\0", &mut smp) { + smp = 0; + } + #[allow(clippy::collapsible_if)] // I keep as is for readability reasons. + if smp != 0 { + if !get_sys_value_by_name(b"kern.smp.cpus\0", &mut nb_cpus) || nb_cpus < 1 { + nb_cpus = 1; + } + } + + let mut si = SystemInfo { + hw_physical_memory: Default::default(), + page_size: 0, + virtual_page_count: Default::default(), + virtual_wire_count: Default::default(), + virtual_active_count: Default::default(), + virtual_cache_count: Default::default(), + virtual_inactive_count: Default::default(), + virtual_free_count: Default::default(), + buf_space: Default::default(), + os_type: Default::default(), + os_release: Default::default(), + kern_version: Default::default(), + hostname: Default::default(), + nb_cpus, + kd, + mib_cp_time: Default::default(), + mib_cp_times: Default::default(), + cp_time: utils::VecSwitcher::new(vec![0; libc::CPUSTATES as usize]), + cp_times: utils::VecSwitcher::new(vec![ + 0; + nb_cpus as usize * libc::CPUSTATES as usize + ]), + fscale: 0., + procstat: std::ptr::null_mut(), + zfs: Zfs::new(), + }; + let mut fscale: c_int = 0; + if !get_sys_value_by_name(b"kern.fscale\0", &mut fscale) { + // Default value used in htop. + fscale = 2048; + } + si.fscale = fscale as f32; + + if !get_sys_value_by_name(b"vm.stats.vm.v_page_size\0", &mut si.page_size) { + panic!("cannot get page size..."); + } + + init_mib(b"hw.physmem\0", &mut si.hw_physical_memory); + init_mib(b"vm.stats.vm.v_page_count\0", &mut si.virtual_page_count); + init_mib(b"vm.stats.vm.v_wire_count\0", &mut si.virtual_wire_count); + init_mib( + b"vm.stats.vm.v_active_count\0", + &mut si.virtual_active_count, + ); + init_mib(b"vm.stats.vm.v_cache_count\0", &mut si.virtual_cache_count); + init_mib( + b"vm.stats.vm.v_inactive_count\0", + &mut si.virtual_inactive_count, + ); + init_mib(b"vm.stats.vm.v_free_count\0", &mut si.virtual_free_count); + init_mib(b"vfs.bufspace\0", &mut si.buf_space); + + init_mib(b"kern.ostype\0", &mut si.os_type); + init_mib(b"kern.osrelease\0", &mut si.os_release); + init_mib(b"kern.version\0", &mut si.kern_version); + init_mib(b"kern.hostname\0", &mut si.hostname); + + init_mib(b"kern.cp_time\0", &mut si.mib_cp_time); + init_mib(b"kern.cp_times\0", &mut si.mib_cp_times); + + si + } + } + + fn get_os_name(&self) -> Option<String> { + get_system_info(&[self.os_type[0], self.os_type[1]], Some("FreeBSD")) + } + + fn get_kernel_version(&self) -> Option<String> { + get_system_info(&[self.kern_version[0], self.kern_version[1]], None) + } + + fn get_os_release_long(&self) -> Option<String> { + get_system_info(&[self.os_release[0], self.os_release[1]], None) + } + + fn get_os_release(&self) -> Option<String> { + // It returns something like "13.0-RELEASE". We want to keep everything until the "-". + get_system_info(&[self.os_release[0], self.os_release[1]], None) + .and_then(|s| s.split('-').next().map(|s| s.to_owned())) + } + + fn get_hostname(&self) -> Option<String> { + get_system_info(&[self.hostname[0], self.hostname[1]], Some("")) + } + + /// Returns (used, total). + fn get_swap_info(&self) -> (u64, u64) { + // Magic number used in htop. Cannot find how they got it when reading `kvm_getswapinfo` + // source code so here we go... + const LEN: usize = 16; + let mut swap = MaybeUninit::<[libc::kvm_swap; LEN]>::uninit(); + unsafe { + let nswap = + libc::kvm_getswapinfo(self.kd.as_ptr(), swap.as_mut_ptr() as *mut _, LEN as _, 0) + as usize; + if nswap < 1 { + return (0, 0); + } + let swap = + std::slice::from_raw_parts(swap.as_ptr() as *mut libc::kvm_swap, nswap.min(LEN)); + let (used, total) = swap.iter().fold((0, 0), |(used, total): (u64, u64), swap| { + ( + used.saturating_add(swap.ksw_used as _), + total.saturating_add(swap.ksw_total as _), + ) + }); + ( + used.saturating_mul(self.page_size as _) / 1_000, + total.saturating_mul(self.page_size as _) / 1_000, + ) + } + } + + fn get_total_memory(&self) -> u64 { + let mut nb_pages: u64 = 0; + unsafe { + if get_sys_value(&self.virtual_page_count, &mut nb_pages) { + return nb_pages.saturating_mul(self.page_size as _) / 1_000; + } + + // This is a fallback. It includes all the available memory, not just the one available for + // the users. + let mut total_memory: u64 = 0; + get_sys_value(&self.hw_physical_memory, &mut total_memory); + total_memory / 1_000 + } + } + + fn get_used_memory(&self) -> u64 { + let mut mem_active: u64 = 0; + let mut mem_wire: u64 = 0; + + unsafe { + get_sys_value(&self.virtual_active_count, &mut mem_active); + get_sys_value(&self.virtual_wire_count, &mut mem_wire); + + let mut mem_wire = mem_wire.saturating_mul(self.page_size as _); + // We need to subtract "ZFS ARC" from the "wired memory" because it should belongs to cache + // but the kernel reports it as "wired memory" instead... + if let Some(arc_size) = self.zfs.arc_size() { + mem_wire -= arc_size; + } + let used = mem_active + .saturating_mul(self.page_size as _) + .saturating_add(mem_wire); + used / 1_000 + } + } + + fn get_free_memory(&self) -> u64 { + let mut buffers_mem: u64 = 0; + let mut inactive_mem: u64 = 0; + let mut cached_mem: u64 = 0; + let mut free_mem: u64 = 0; + + unsafe { + get_sys_value(&self.buf_space, &mut buffers_mem); + get_sys_value(&self.virtual_inactive_count, &mut inactive_mem); + get_sys_value(&self.virtual_cache_count, &mut cached_mem); + get_sys_value(&self.virtual_free_count, &mut free_mem); + // For whatever reason, buffers_mem is already the right value... + let free = buffers_mem + .saturating_add(inactive_mem.saturating_mul(self.page_size as u64)) + .saturating_add(cached_mem.saturating_mul(self.page_size as u64)) + .saturating_add(free_mem.saturating_mul(self.page_size as u64)); + free / 1_000 + } + } + + fn get_cpu_usage(&mut self, global: &mut Cpu, cpus: &mut [Cpu]) { + unsafe { + get_sys_value_array(&self.mib_cp_time, self.cp_time.get_mut()); + get_sys_value_array(&self.mib_cp_times, self.cp_times.get_mut()); + } + + fn fill_cpu(proc_: &mut Cpu, new_cp_time: &[libc::c_ulong], old_cp_time: &[libc::c_ulong]) { + let mut total_new: u64 = 0; + let mut total_old: u64 = 0; + let mut cp_diff: libc::c_ulong = 0; + + for i in 0..(libc::CPUSTATES as usize) { + // We obviously don't want to get the idle part of the CPU usage, otherwise + // we would always be at 100%... + if i != libc::CP_IDLE as usize { + cp_diff += new_cp_time[i] - old_cp_time[i]; + } + total_new += new_cp_time[i] as u64; + total_old += old_cp_time[i] as u64; + } + + let total_diff = total_new - total_old; + if total_diff < 1 { + proc_.cpu_usage = 0.; + } else { + proc_.cpu_usage = cp_diff as f32 / total_diff as f32 * 100.; + } + } + + fill_cpu(global, self.cp_time.get_new(), self.cp_time.get_old()); + let old_cp_times = self.cp_times.get_old(); + let new_cp_times = self.cp_times.get_new(); + for (pos, proc_) in cpus.iter_mut().enumerate() { + let index = pos * libc::CPUSTATES as usize; + + fill_cpu(proc_, &new_cp_times[index..], &old_cp_times[index..]); + } + } + + #[allow(clippy::collapsible_if)] // I keep as is for readability reasons. + unsafe fn get_proc_missing_info(&mut self, kproc: &libc::kinfo_proc, proc_: &mut Process) { + if self.procstat.is_null() { + self.procstat = libc::procstat_open_sysctl(); + } + if self.procstat.is_null() { + return; + } + let head = libc::procstat_getfiles(self.procstat, kproc as *const _ as usize as *mut _, 0); + if head.is_null() { + return; + } + let mut entry = (*head).stqh_first; + let mut done = 0; + while !entry.is_null() && done < 2 { + { + let tmp = &*entry; + if tmp.fs_uflags & libc::PS_FST_UFLAG_CDIR != 0 { + if !tmp.fs_path.is_null() { + if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() { + proc_.cwd = PathBuf::from(p); + done += 1; + } + } + } else if tmp.fs_uflags & libc::PS_FST_UFLAG_RDIR != 0 { + if !tmp.fs_path.is_null() { + if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() { + proc_.root = PathBuf::from(p); + done += 1; + } + } + } + } + entry = (*entry).next.stqe_next; + } + libc::procstat_freefiles(self.procstat, head); + } +} + +impl Drop for SystemInfo { + fn drop(&mut self) { + unsafe { + libc::kvm_close(self.kd.as_ptr()); + if !self.procstat.is_null() { + libc::procstat_close(self.procstat); + } + } + } +} diff --git a/vendor/sysinfo/src/freebsd/utils.rs b/vendor/sysinfo/src/freebsd/utils.rs new file mode 100644 index 000000000..00de3e9d9 --- /dev/null +++ b/vendor/sysinfo/src/freebsd/utils.rs @@ -0,0 +1,296 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{Pid, Process}; +use libc::{c_char, c_int, timeval}; +use std::cell::UnsafeCell; +use std::collections::HashMap; +use std::ffi::CStr; +use std::mem; +use std::time::SystemTime; + +/// This struct is used to switch between the "old" and "new" every time you use "get_mut". +#[derive(Debug)] +pub(crate) struct VecSwitcher<T> { + v1: Vec<T>, + v2: Vec<T>, + first: bool, +} + +impl<T: Clone> VecSwitcher<T> { + pub fn new(v1: Vec<T>) -> Self { + let v2 = v1.clone(); + + Self { + v1, + v2, + first: true, + } + } + + pub fn get_mut(&mut self) -> &mut [T] { + self.first = !self.first; + if self.first { + // It means that `v2` will be the "new". + &mut self.v2 + } else { + // It means that `v1` will be the "new". + &mut self.v1 + } + } + + pub fn get_old(&self) -> &[T] { + if self.first { + &self.v1 + } else { + &self.v2 + } + } + + pub fn get_new(&self) -> &[T] { + if self.first { + &self.v2 + } else { + &self.v1 + } + } +} + +#[inline] +pub unsafe fn init_mib(name: &[u8], mib: &mut [c_int]) { + let mut len = mib.len(); + libc::sysctlnametomib(name.as_ptr() as _, mib.as_mut_ptr(), &mut len); +} + +pub(crate) fn boot_time() -> u64 { + let mut boot_time = timeval { + tv_sec: 0, + tv_usec: 0, + }; + let mut len = std::mem::size_of::<timeval>(); + let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME]; + unsafe { + if libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + &mut boot_time as *mut timeval as *mut _, + &mut len, + std::ptr::null_mut(), + 0, + ) < 0 + { + 0 + } else { + boot_time.tv_sec as _ + } + } +} + +pub(crate) unsafe fn get_sys_value<T: Sized>(mib: &[c_int], value: &mut T) -> bool { + let mut len = mem::size_of::<T>() as libc::size_t; + libc::sysctl( + mib.as_ptr(), + mib.len() as _, + value as *mut _ as *mut _, + &mut len, + std::ptr::null_mut(), + 0, + ) == 0 +} + +pub(crate) unsafe fn get_sys_value_array<T: Sized>(mib: &[c_int], value: &mut [T]) -> bool { + let mut len = (mem::size_of::<T>() * value.len()) as libc::size_t; + libc::sysctl( + mib.as_ptr(), + mib.len() as _, + value.as_mut_ptr() as *mut _, + &mut len as *mut _, + std::ptr::null_mut(), + 0, + ) == 0 +} + +pub(crate) fn c_buf_to_str(buf: &[libc::c_char]) -> Option<&str> { + unsafe { + let buf: &[u8] = std::slice::from_raw_parts(buf.as_ptr() as _, buf.len()); + if let Some(pos) = buf.iter().position(|x| *x == 0) { + // Shrink buffer to terminate the null bytes + std::str::from_utf8(&buf[..pos]).ok() + } else { + std::str::from_utf8(buf).ok() + } + } +} + +pub(crate) fn c_buf_to_string(buf: &[libc::c_char]) -> Option<String> { + c_buf_to_str(buf).map(|s| s.to_owned()) +} + +pub(crate) unsafe fn get_sys_value_str(mib: &[c_int], buf: &mut [libc::c_char]) -> Option<String> { + let mut len = (mem::size_of::<libc::c_char>() * buf.len()) as libc::size_t; + if libc::sysctl( + mib.as_ptr(), + mib.len() as _, + buf.as_mut_ptr() as *mut _, + &mut len, + std::ptr::null_mut(), + 0, + ) != 0 + { + return None; + } + c_buf_to_string(&buf[..len / mem::size_of::<libc::c_char>()]) +} + +pub(crate) unsafe fn get_sys_value_by_name<T: Sized>(name: &[u8], value: &mut T) -> bool { + let mut len = mem::size_of::<T>() as libc::size_t; + let original = len; + + libc::sysctlbyname( + name.as_ptr() as *const c_char, + value as *mut _ as *mut _, + &mut len, + std::ptr::null_mut(), + 0, + ) == 0 + && original == len +} + +pub(crate) fn get_sys_value_str_by_name(name: &[u8]) -> Option<String> { + let mut size = 0; + + unsafe { + if libc::sysctlbyname( + name.as_ptr() as *const c_char, + std::ptr::null_mut(), + &mut size, + std::ptr::null_mut(), + 0, + ) == 0 + && size > 0 + { + // now create a buffer with the size and get the real value + let mut buf: Vec<libc::c_char> = vec![0; size as usize]; + + if libc::sysctlbyname( + name.as_ptr() as *const c_char, + buf.as_mut_ptr() as *mut _, + &mut size, + std::ptr::null_mut(), + 0, + ) == 0 + && size > 0 + { + c_buf_to_string(&buf) + } else { + // getting the system value failed + None + } + } else { + None + } + } +} + +pub(crate) fn get_system_info(mib: &[c_int], default: Option<&str>) -> Option<String> { + let mut size = 0; + + unsafe { + // Call first to get size + libc::sysctl( + mib.as_ptr(), + mib.len() as _, + std::ptr::null_mut(), + &mut size, + std::ptr::null_mut(), + 0, + ); + + // exit early if we did not update the size + if size == 0 { + default.map(|s| s.to_owned()) + } else { + // set the buffer to the correct size + let mut buf: Vec<libc::c_char> = vec![0; size as usize]; + + if libc::sysctl( + mib.as_ptr(), + mib.len() as _, + buf.as_mut_ptr() as _, + &mut size, + std::ptr::null_mut(), + 0, + ) == -1 + { + // If command fails return default + default.map(|s| s.to_owned()) + } else { + c_buf_to_string(&buf) + } + } + } +} + +pub(crate) unsafe fn from_cstr_array(ptr: *const *const c_char) -> Vec<String> { + if ptr.is_null() { + return Vec::new(); + } + let mut max = 0; + loop { + let ptr = ptr.add(max); + if (*ptr).is_null() { + break; + } + max += 1; + } + if max == 0 { + return Vec::new(); + } + let mut ret = Vec::with_capacity(max); + + for pos in 0..max { + let p = ptr.add(pos); + if let Ok(s) = CStr::from_ptr(*p).to_str() { + ret.push(s.to_owned()); + } + } + ret +} + +pub(crate) fn get_now() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map(|n| n.as_secs()) + .unwrap_or(0) +} + +// All this is needed because `kinfo_proc` doesn't implement `Send` (because it contains pointers). +pub(crate) struct WrapMap<'a>(pub UnsafeCell<&'a mut HashMap<Pid, Process>>); + +unsafe impl<'a> Send for WrapMap<'a> {} +unsafe impl<'a> Sync for WrapMap<'a> {} + +#[repr(transparent)] +pub(crate) struct KInfoProc(libc::kinfo_proc); +unsafe impl Send for KInfoProc {} +unsafe impl Sync for KInfoProc {} + +impl std::ops::Deref for KInfoProc { + type Target = libc::kinfo_proc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub(crate) unsafe fn get_frequency_for_cpu(cpu_nb: c_int) -> u64 { + let mut frequency = 0; + + // The information can be missing if it's running inside a VM. + if !get_sys_value_by_name( + format!("dev.cpu.{}.freq\0", cpu_nb).as_bytes(), + &mut frequency, + ) { + frequency = 0; + } + frequency as _ +} |