Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

604
vendor/sysinfo/src/c_interface.rs vendored Normal file
View File

@@ -0,0 +1,604 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{Disks, Networks, Pid, Process, ProcessesToUpdate, System};
use libc::{self, c_char, c_float, c_uint, c_void, size_t};
use std::borrow::BorrowMut;
use std::ffi::CString;
/// on windows, libc has not include pid_t.
#[cfg(target_os = "windows")]
pub type PID = usize;
/// other platforms, use libc::pid_t
#[cfg(not(target_os = "windows"))]
pub type PID = libc::pid_t;
/// Equivalent of [`System`][crate::System] struct.
pub type CSystem = *mut c_void;
/// Equivalent of [`Process`][crate::Process] struct.
pub type CProcess = *const c_void;
/// C string returned from `CString::into_raw`.
pub type RString = *const c_char;
/// Callback used by [`processes`][crate::System#method.processes].
pub type ProcessLoop = extern "C" fn(pid: PID, process: CProcess, data: *mut c_void) -> bool;
/// Callback used by [`tasks`][crate::Process#method.tasks].
pub type ProcessPidLoop = extern "C" fn(pid: PID, data: *mut c_void) -> bool;
/// Equivalent of [`Networks`][crate::Networks] struct.
pub type CNetworks = *mut c_void;
/// Equivalent of [`Disks`][crate::Disks] struct.
pub type CDisks = *mut c_void;
/// Equivalent of [`System::new()`][crate::System#method.new].
#[no_mangle]
pub extern "C" fn sysinfo_init() -> CSystem {
let system = Box::new(System::new());
Box::into_raw(system) as CSystem
}
/// Equivalent of `System::drop()`. Important in C to cleanup memory.
#[no_mangle]
pub extern "C" fn sysinfo_destroy(system: CSystem) {
assert!(!system.is_null());
unsafe {
drop(Box::from_raw(system as *mut System));
}
}
/// Equivalent of [`System::refresh_memory()`][crate::System#method.refresh_memory].
#[no_mangle]
pub extern "C" fn sysinfo_refresh_memory(system: CSystem) {
assert!(!system.is_null());
unsafe {
let mut system: Box<System> = Box::from_raw(system as *mut System);
{
let system: &mut System = system.borrow_mut();
system.refresh_memory();
}
let _ = Box::into_raw(system);
}
}
/// Equivalent of [`System::refresh_cpu_usage()`][crate::System#method.refresh_cpu_usage].
#[no_mangle]
pub extern "C" fn sysinfo_refresh_cpu(system: CSystem) {
assert!(!system.is_null());
unsafe {
let mut system: Box<System> = Box::from_raw(system as *mut System);
{
let system: &mut System = system.borrow_mut();
system.refresh_cpu_usage();
}
let _ = Box::into_raw(system);
}
}
/// Equivalent of [`System::refresh_all()`][crate::System#method.refresh_all].
#[no_mangle]
pub extern "C" fn sysinfo_refresh_all(system: CSystem) {
assert!(!system.is_null());
unsafe {
let mut system: Box<System> = Box::from_raw(system as *mut System);
{
let system: &mut System = system.borrow_mut();
system.refresh_all();
}
let _ = Box::into_raw(system);
}
}
/// Equivalent of [`System::refresh_processes(ProcessesToUpdate::All)`].
///
/// [`System::refresh_processes(ProcessesToUpdate::All)`]: crate::System#method.refresh_processes
#[no_mangle]
pub extern "C" fn sysinfo_refresh_processes(system: CSystem) {
assert!(!system.is_null());
unsafe {
let mut system: Box<System> = Box::from_raw(system as *mut System);
{
let system: &mut System = system.borrow_mut();
system.refresh_processes(ProcessesToUpdate::All, true);
}
let _ = Box::into_raw(system);
}
}
/// Equivalent of [`System::refresh_processes(ProcessesToUpdate::Some(pid))`].
///
/// [`System::refresh_processes(ProcessesToUpdate::Some(pid))`]: crate::System#method.refresh_processes
#[no_mangle]
pub extern "C" fn sysinfo_refresh_process(system: CSystem, pid: PID) {
assert!(!system.is_null());
unsafe {
let mut system: Box<System> = Box::from_raw(system as *mut System);
{
let system: &mut System = system.borrow_mut();
system.refresh_processes(ProcessesToUpdate::Some(&[Pid::from_u32(pid as _)]), true);
}
let _ = Box::into_raw(system);
}
}
/// Equivalent of [`Disks::new()`][crate::Disks#method.new].
#[no_mangle]
pub extern "C" fn sysinfo_disks_init() -> CDisks {
let disks = Box::new(Disks::new());
Box::into_raw(disks) as CDisks
}
/// Equivalent of `Disks::drop()`. Important in C to cleanup memory.
#[no_mangle]
pub extern "C" fn sysinfo_disks_destroy(disks: CDisks) {
assert!(!disks.is_null());
unsafe {
drop(Box::from_raw(disks as *mut Disks));
}
}
/// Equivalent of [`Disks::refresh()`][crate::Disks#method.refresh].
#[no_mangle]
pub extern "C" fn sysinfo_disks_refresh(disks: CDisks) {
assert!(!disks.is_null());
unsafe {
let mut disks: Box<Disks> = Box::from_raw(disks as *mut Disks);
{
let disks: &mut Disks = disks.borrow_mut();
disks.refresh(true);
}
let _ = Box::into_raw(disks);
}
}
/// Equivalent of [`System::total_memory()`][crate::System#method.total_memory].
#[no_mangle]
pub extern "C" fn sysinfo_total_memory(system: CSystem) -> size_t {
assert!(!system.is_null());
unsafe {
let system: Box<System> = Box::from_raw(system as *mut System);
let ret = system.total_memory() as size_t;
let _ = Box::into_raw(system);
ret
}
}
/// Equivalent of [`System::free_memory()`][crate::System#method.free_memory].
#[no_mangle]
pub extern "C" fn sysinfo_free_memory(system: CSystem) -> size_t {
assert!(!system.is_null());
unsafe {
let system: Box<System> = Box::from_raw(system as *mut System);
let ret = system.free_memory() as size_t;
let _ = Box::into_raw(system);
ret
}
}
/// Equivalent of [`System::used_memory()`][crate::System#method.used_memory].
#[no_mangle]
pub extern "C" fn sysinfo_used_memory(system: CSystem) -> size_t {
assert!(!system.is_null());
let system: Box<System> = unsafe { Box::from_raw(system as *mut System) };
let ret = system.used_memory() as size_t;
let _ = Box::into_raw(system);
ret
}
/// Equivalent of [`System::total_swap()`][crate::System#method.total_swap].
#[no_mangle]
pub extern "C" fn sysinfo_total_swap(system: CSystem) -> size_t {
assert!(!system.is_null());
unsafe {
let system: Box<System> = Box::from_raw(system as *mut System);
let ret = system.total_swap() as size_t;
let _ = Box::into_raw(system);
ret
}
}
/// Equivalent of [`System::free_swap()`][crate::System#method.free_swap].
#[no_mangle]
pub extern "C" fn sysinfo_free_swap(system: CSystem) -> size_t {
assert!(!system.is_null());
unsafe {
let system: Box<System> = Box::from_raw(system as *mut System);
let ret = system.free_swap() as size_t;
let _ = Box::into_raw(system);
ret
}
}
/// Equivalent of [`System::used_swap()`][crate::System#method.used_swap].
#[no_mangle]
pub extern "C" fn sysinfo_used_swap(system: CSystem) -> size_t {
assert!(!system.is_null());
unsafe {
let system: Box<System> = Box::from_raw(system as *mut System);
let ret = system.used_swap() as size_t;
let _ = Box::into_raw(system);
ret
}
}
/// Equivalent of [`Networks::new()`][crate::Networks#method.new].
#[no_mangle]
pub extern "C" fn sysinfo_networks_init() -> CNetworks {
let networks = Box::new(Networks::new());
Box::into_raw(networks) as CNetworks
}
/// Equivalent of `Networks::drop()`. Important in C to cleanup memory.
#[no_mangle]
pub extern "C" fn sysinfo_networks_destroy(networks: CNetworks) {
assert!(!networks.is_null());
unsafe {
drop(Box::from_raw(networks as *mut Networks));
}
}
/// Equivalent of [`Networks::refresh()`][crate::Networks#method.refresh].
#[no_mangle]
pub extern "C" fn sysinfo_networks_refresh(networks: CNetworks) {
assert!(!networks.is_null());
unsafe {
let mut networks: Box<Networks> = Box::from_raw(networks as *mut Networks);
{
let networks: &mut Networks = networks.borrow_mut();
networks.refresh(true);
}
let _ = Box::into_raw(networks);
}
}
/// Equivalent of
/// `system::networks().iter().fold(0, |acc, (_, data)| acc + data.received() as size_t)`.
#[no_mangle]
pub extern "C" fn sysinfo_networks_received(networks: CNetworks) -> size_t {
assert!(!networks.is_null());
unsafe {
let networks: Box<Networks> = Box::from_raw(networks as *mut Networks);
let ret = networks.iter().fold(0, |acc: size_t, (_, data)| {
acc.saturating_add(data.received() as size_t)
});
let _ = Box::into_raw(networks);
ret
}
}
/// Equivalent of
/// `system::networks().iter().fold(0, |acc, (_, data)| acc + data.transmitted() as size_t)`.
#[no_mangle]
pub extern "C" fn sysinfo_networks_transmitted(networks: CNetworks) -> size_t {
assert!(!networks.is_null());
unsafe {
let networks: Box<Networks> = Box::from_raw(networks as *mut Networks);
let ret = networks.iter().fold(0, |acc: size_t, (_, data)| {
acc.saturating_add(data.transmitted() as size_t)
});
let _ = Box::into_raw(networks);
ret
}
}
/// Equivalent of [`System::cpus_usage()`][crate::System#method.cpus_usage].
///
/// * `length` will contain the number of CPU usage added into `procs`.
/// * `procs` will be allocated if it's null and will contain of CPU usage.
#[no_mangle]
pub extern "C" fn sysinfo_cpus_usage(
system: CSystem,
length: *mut c_uint,
procs: *mut *mut c_float,
) {
assert!(!system.is_null());
if procs.is_null() || length.is_null() {
return;
}
unsafe {
let system: Box<System> = Box::from_raw(system as *mut System);
{
let cpus = system.cpus();
if (*procs).is_null() {
(*procs) =
libc::malloc(::std::mem::size_of::<c_float>() * cpus.len()) as *mut c_float;
}
for (pos, cpu) in cpus.iter().skip(1).enumerate() {
(*(*procs).offset(pos as isize)) = cpu.cpu_usage();
}
*length = cpus.len() as c_uint - 1;
}
let _ = Box::into_raw(system);
}
}
/// Equivalent of [`System::processes()`][crate::System#method.processes]. Returns an
/// array ended by a null pointer. Must be freed.
///
/// # ⚠️ WARNING ⚠️
///
/// While having this method returned processes, you should *never* call any refresh method!
#[no_mangle]
pub extern "C" fn sysinfo_processes(
system: CSystem,
fn_pointer: Option<ProcessLoop>,
data: *mut c_void,
) -> size_t {
assert!(!system.is_null());
if let Some(fn_pointer) = fn_pointer {
unsafe {
let system: Box<System> = Box::from_raw(system as *mut System);
let len = {
let entries = system.processes();
for (pid, process) in entries {
if !fn_pointer(pid.0 as _, process as *const Process as CProcess, data) {
break;
}
}
entries.len() as size_t
};
let _ = Box::into_raw(system);
len
}
} else {
0
}
}
/// Equivalent of [`System::process()`][crate::System#method.process].
///
/// # ⚠️ WARNING ⚠️
///
/// While having this method returned process, you should *never* call any
/// refresh method!
#[no_mangle]
pub extern "C" fn sysinfo_process_by_pid(system: CSystem, pid: PID) -> CProcess {
assert!(!system.is_null());
unsafe {
let system: Box<System> = Box::from_raw(system as *mut System);
let ret = if let Some(process) = system.process(Pid(pid as _)) {
process as *const Process as CProcess
} else {
std::ptr::null()
};
let _ = Box::into_raw(system);
ret
}
}
/// Equivalent of iterating over [`Process::tasks()`][crate::Process#method.tasks].
///
/// # ⚠️ WARNING ⚠️
///
/// While having this method processes, you should *never* call any refresh method!
#[no_mangle]
pub extern "C" fn sysinfo_process_tasks(
process: CProcess,
fn_pointer: Option<ProcessPidLoop>,
data: *mut c_void,
) -> size_t {
assert!(!process.is_null());
if let Some(fn_pointer) = fn_pointer {
unsafe {
let process = process as *const Process;
if let Some(tasks) = (*process).tasks() {
for pid in tasks {
if !fn_pointer(pid.0 as _, data) {
break;
}
}
tasks.len() as size_t
} else {
0
}
}
} else {
0
}
}
/// Equivalent of [`Process::pid()`][crate::Process#method.pid].
#[no_mangle]
pub extern "C" fn sysinfo_process_pid(process: CProcess) -> PID {
assert!(!process.is_null());
let process = process as *const Process;
unsafe { (*process).pid().0 as _ }
}
/// Equivalent of [`Process::parent()`][crate::Process#method.parent].
///
/// In case there is no known parent, it returns `0`.
#[no_mangle]
pub extern "C" fn sysinfo_process_parent_pid(process: CProcess) -> PID {
assert!(!process.is_null());
let process = process as *const Process;
unsafe { (*process).parent().unwrap_or(Pid(0)).0 as _ }
}
/// Equivalent of [`Process::cpu_usage()`][crate::Process#method.cpu_usage].
#[no_mangle]
pub extern "C" fn sysinfo_process_cpu_usage(process: CProcess) -> c_float {
assert!(!process.is_null());
let process = process as *const Process;
unsafe { (*process).cpu_usage() }
}
/// Equivalent of [`Process::memory()`][crate::Process#method.memory].
#[no_mangle]
pub extern "C" fn sysinfo_process_memory(process: CProcess) -> size_t {
assert!(!process.is_null());
let process = process as *const Process;
unsafe { (*process).memory() as usize }
}
/// Equivalent of [`Process::virtual_memory()`][crate::Process#method.virtual_memory].
#[no_mangle]
pub extern "C" fn sysinfo_process_virtual_memory(process: CProcess) -> size_t {
assert!(!process.is_null());
let process = process as *const Process;
unsafe { (*process).virtual_memory() as usize }
}
/// Equivalent of [`Process::exe()`][crate::Process#method.exe].
#[no_mangle]
pub extern "C" fn sysinfo_process_executable_path(process: CProcess) -> RString {
assert!(!process.is_null());
let process = process as *const Process;
unsafe {
if let Some(p) = (*process).exe().and_then(|exe| exe.to_str()) {
if let Ok(c) = CString::new(p) {
return c.into_raw() as _;
}
}
std::ptr::null()
}
}
/// Equivalent of [`Process::root()`][crate::Process#method.root].
#[no_mangle]
pub extern "C" fn sysinfo_process_root_directory(process: CProcess) -> RString {
assert!(!process.is_null());
let process = process as *const Process;
unsafe {
if let Some(p) = (*process).root().and_then(|root| root.to_str()) {
if let Ok(c) = CString::new(p) {
return c.into_raw() as _;
}
}
std::ptr::null()
}
}
/// Equivalent of [`Process::cwd()`][crate::Process#method.cwd].
#[no_mangle]
pub extern "C" fn sysinfo_process_current_directory(process: CProcess) -> RString {
assert!(!process.is_null());
let process = process as *const Process;
unsafe {
if let Some(p) = (*process).cwd().and_then(|cwd| cwd.to_str()) {
if let Ok(c) = CString::new(p) {
return c.into_raw() as _;
}
}
std::ptr::null()
}
}
/// Frees a C string created with `CString::into_raw()`.
#[no_mangle]
pub extern "C" fn sysinfo_rstring_free(s: RString) {
if !s.is_null() {
unsafe {
let _ = CString::from_raw(s as usize as *mut _);
}
}
}
/// Equivalent of [`cpu::vendor_id()`].
#[no_mangle]
pub extern "C" fn sysinfo_cpu_vendor_id(system: CSystem) -> RString {
assert!(!system.is_null());
unsafe {
let system: Box<System> = Box::from_raw(system as *mut System);
let c_string = if let Some(c) = system
.cpus()
.first()
.and_then(|cpu| CString::new(cpu.vendor_id()).ok())
{
c.into_raw() as RString
} else {
std::ptr::null()
};
let _ = Box::into_raw(system);
c_string
}
}
/// Equivalent of [`cpu::brand()`].
#[no_mangle]
pub extern "C" fn sysinfo_cpu_brand(system: CSystem) -> RString {
assert!(!system.is_null());
unsafe {
let system: Box<System> = Box::from_raw(system as *mut System);
let c_string = if let Some(c) = system
.cpus()
.first()
.and_then(|cpu| CString::new(cpu.brand()).ok())
{
c.into_raw() as RString
} else {
std::ptr::null()
};
let _ = Box::into_raw(system);
c_string
}
}
/// Equivalent of [`cpu::frequency()`].
#[no_mangle]
pub extern "C" fn sysinfo_cpu_frequency(system: CSystem) -> u64 {
assert!(!system.is_null());
unsafe {
let system: Box<System> = Box::from_raw(system as *mut System);
let freq = system
.cpus()
.first()
.map(|cpu| cpu.frequency())
.unwrap_or(0);
let _ = Box::into_raw(system);
freq
}
}
/// Equivalent of [`System::name()`][crate::System#method.name].
#[no_mangle]
pub extern "C" fn sysinfo_system_name() -> RString {
if let Some(c) = System::name().and_then(|p| CString::new(p).ok()) {
c.into_raw() as _
} else {
std::ptr::null()
}
}
/// Equivalent of [`System::version()`][crate::System#method.version].
#[no_mangle]
pub extern "C" fn sysinfo_system_version() -> RString {
if let Some(c) = System::os_version().and_then(|c| CString::new(c).ok()) {
c.into_raw() as _
} else {
std::ptr::null()
}
}
/// Equivalent of [`System::kernel_version()`][crate::System#method.kernel_version].
#[no_mangle]
pub extern "C" fn sysinfo_system_kernel_version() -> RString {
if let Some(c) = System::kernel_version().and_then(|c| CString::new(c).ok()) {
c.into_raw() as _
} else {
std::ptr::null()
}
}
/// Equivalent of [`System::host_name()`][crate::System#method.host_name].
#[no_mangle]
pub extern "C" fn sysinfo_system_host_name() -> RString {
if let Some(c) = System::host_name().and_then(|c| CString::new(c).ok()) {
c.into_raw() as _
} else {
std::ptr::null()
}
}
/// Equivalent of [`System::long_os_version()`][crate::System#method.long_os_version].
#[no_mangle]
pub extern "C" fn sysinfo_system_long_version() -> RString {
if let Some(c) = System::long_os_version().and_then(|c| CString::new(c).ok()) {
c.into_raw() as _
} else {
std::ptr::null()
}
}
/// Equivalent of [`system::physical_core_count()`].
#[no_mangle]
pub extern "C" fn sysinfo_cpu_physical_cores() -> u32 {
System::physical_core_count().unwrap_or(0) as u32
}

297
vendor/sysinfo/src/common/component.rs vendored Normal file
View File

@@ -0,0 +1,297 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{ComponentInner, ComponentsInner};
/// Interacting with components.
///
/// ```no_run
/// use sysinfo::Components;
///
/// let components = Components::new_with_refreshed_list();
/// for component in &components {
/// println!("{component:?}");
/// }
/// ```
pub struct Components {
pub(crate) inner: ComponentsInner,
}
impl Default for Components {
fn default() -> Self {
Self::new()
}
}
impl From<Components> for Vec<Component> {
fn from(components: Components) -> Self {
components.inner.into_vec()
}
}
impl From<Vec<Component>> for Components {
fn from(components: Vec<Component>) -> Self {
Self {
inner: ComponentsInner::from_vec(components),
}
}
}
impl std::ops::Deref for Components {
type Target = [Component];
fn deref(&self) -> &Self::Target {
self.list()
}
}
impl std::ops::DerefMut for Components {
fn deref_mut(&mut self) -> &mut Self::Target {
self.list_mut()
}
}
impl<'a> IntoIterator for &'a Components {
type Item = &'a Component;
type IntoIter = std::slice::Iter<'a, Component>;
fn into_iter(self) -> Self::IntoIter {
self.list().iter()
}
}
impl<'a> IntoIterator for &'a mut Components {
type Item = &'a mut Component;
type IntoIter = std::slice::IterMut<'a, Component>;
fn into_iter(self) -> Self::IntoIter {
self.list_mut().iter_mut()
}
}
impl Components {
/// Creates a new empty [`Components`][crate::Components] type.
///
/// If you want it to be filled directly, take a look at
/// [`Components::new_with_refreshed_list`].
///
/// ```no_run
/// use sysinfo::Components;
///
/// let mut components = Components::new();
/// components.refresh(false);
/// for component in &components {
/// println!("{component:?}");
/// }
/// ```
pub fn new() -> Self {
Self {
inner: ComponentsInner::new(),
}
}
/// Creates a new [`Components`][crate::Components] type with the components list
/// loaded.
///
/// ```no_run
/// use sysinfo::Components;
///
/// let mut components = Components::new_with_refreshed_list();
/// for component in components.list() {
/// println!("{component:?}");
/// }
/// ```
pub fn new_with_refreshed_list() -> Self {
let mut components = Self::new();
components.refresh(true);
components
}
/// Returns the components list.
///
/// ```no_run
/// use sysinfo::Components;
///
/// let components = Components::new_with_refreshed_list();
/// for component in components.list() {
/// println!("{component:?}");
/// }
/// ```
pub fn list(&self) -> &[Component] {
self.inner.list()
}
/// Returns the components list.
///
/// ```no_run
/// use sysinfo::Components;
///
/// let mut components = Components::new_with_refreshed_list();
/// for component in components.list_mut() {
/// component.refresh();
/// println!("{component:?}");
/// }
/// ```
pub fn list_mut(&mut self) -> &mut [Component] {
self.inner.list_mut()
}
/// Refreshes the components list.
///
/// ```no_run
/// use sysinfo::Components;
///
/// let mut components = Components::new_with_refreshed_list();
/// // We wait some time...?
/// components.refresh(false);
/// ```
pub fn refresh(&mut self, remove_not_listed_components: bool) {
self.inner.refresh();
if remove_not_listed_components {
// Remove interfaces which are gone.
self.inner.components.retain_mut(|c| {
if !c.inner.updated {
return false;
}
c.inner.updated = false;
true
});
}
}
}
/// Getting a component temperature information.
///
/// ```no_run
/// use sysinfo::Components;
///
/// let components = Components::new_with_refreshed_list();
/// for component in &components {
/// if let Some(temperature) = component.temperature() {
/// println!("{} {temperature}°C", component.label());
/// } else {
/// println!("{} (unknown temperature)", component.label());
/// }
/// }
/// ```
pub struct Component {
pub(crate) inner: ComponentInner,
}
impl Component {
/// Returns the temperature of the component (in celsius degree).
///
/// ## Linux
///
/// Returns `f32::NAN` if it failed to retrieve it.
///
/// ```no_run
/// use sysinfo::Components;
///
/// let components = Components::new_with_refreshed_list();
/// for component in &components {
/// if let Some(temperature) = component.temperature() {
/// println!("{temperature}°C");
/// }
/// }
/// ```
pub fn temperature(&self) -> Option<f32> {
self.inner.temperature()
}
/// Returns the maximum temperature of the component (in celsius degree).
///
/// Note: if `temperature` is higher than the current `max`,
/// `max` value will be updated on refresh.
///
/// ## Linux
///
/// May be computed by `sysinfo` from kernel.
/// Returns `f32::NAN` if it failed to retrieve it.
///
/// ```no_run
/// use sysinfo::Components;
///
/// let components = Components::new_with_refreshed_list();
/// for component in &components {
/// if let Some(max) = component.max() {
/// println!("{max}°C");
/// }
/// }
/// ```
pub fn max(&self) -> Option<f32> {
self.inner.max()
}
/// Returns the highest temperature before the component halts (in celsius degree).
///
/// ## Linux
///
/// Critical threshold defined by chip or kernel.
///
/// ```no_run
/// use sysinfo::Components;
///
/// let components = Components::new_with_refreshed_list();
/// for component in &components {
/// if let Some(critical) = component.critical() {
/// println!("{critical}°C");
/// }
/// }
/// ```
pub fn critical(&self) -> Option<f32> {
self.inner.critical()
}
/// Returns the label of the component.
///
/// ## Linux
///
/// Since components information is retrieved thanks to `hwmon`,
/// the labels are generated as follows.
/// Note: it may change and it was inspired by `sensors` own formatting.
///
/// | name | label | device_model | id_sensor | Computed label by `sysinfo` |
/// |---------|--------|------------|----------|----------------------|
/// | ✓ | ✓ | ✓ | ✓ | `"{name} {label} {device_model} temp{id}"` |
/// | ✓ | ✓ | ✗ | ✓ | `"{name} {label} {id}"` |
/// | ✓ | ✗ | ✓ | ✓ | `"{name} {device_model}"` |
/// | ✓ | ✗ | ✗ | ✓ | `"{name} temp{id}"` |
///
/// ```no_run
/// use sysinfo::Components;
///
/// let components = Components::new_with_refreshed_list();
/// for component in &components {
/// println!("{}", component.label());
/// }
/// ```
pub fn label(&self) -> &str {
self.inner.label()
}
/// Refreshes component.
///
/// ```no_run
/// use sysinfo::Components;
///
/// let mut components = Components::new_with_refreshed_list();
/// for component in components.iter_mut() {
/// component.refresh();
/// }
/// ```
pub fn refresh(&mut self) {
self.inner.refresh()
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn test_components_mac_m1() {
let mut components = Components::new();
components.refresh(false);
components.refresh(false);
}
}

455
vendor/sysinfo/src/common/disk.rs vendored Normal file
View File

@@ -0,0 +1,455 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use std::ffi::OsStr;
use std::fmt;
use std::path::Path;
use crate::common::impl_get_set::impl_get_set;
use crate::DiskUsage;
/// Struct containing a disk information.
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("{:?}: {:?}", disk.name(), disk.kind());
/// }
/// ```
pub struct Disk {
pub(crate) inner: crate::DiskInner,
}
impl Disk {
/// Returns the kind of disk.
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("[{:?}] {:?}", disk.name(), disk.kind());
/// }
/// ```
pub fn kind(&self) -> DiskKind {
self.inner.kind()
}
/// Returns the disk name.
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("{:?}", disk.name());
/// }
/// ```
pub fn name(&self) -> &OsStr {
self.inner.name()
}
/// Returns the file system used on this disk (so for example: `EXT4`, `NTFS`, etc...).
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("[{:?}] {:?}", disk.name(), disk.file_system());
/// }
/// ```
pub fn file_system(&self) -> &OsStr {
self.inner.file_system()
}
/// Returns the mount point of the disk (`/` for example).
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("[{:?}] {:?}", disk.name(), disk.mount_point());
/// }
/// ```
pub fn mount_point(&self) -> &Path {
self.inner.mount_point()
}
/// Returns the total disk size, in bytes.
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("[{:?}] {}B", disk.name(), disk.total_space());
/// }
/// ```
pub fn total_space(&self) -> u64 {
self.inner.total_space()
}
/// Returns the available disk size, in bytes.
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("[{:?}] {}B", disk.name(), disk.available_space());
/// }
/// ```
pub fn available_space(&self) -> u64 {
self.inner.available_space()
}
/// Returns `true` if the disk is removable.
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("[{:?}] {}", disk.name(), disk.is_removable());
/// }
/// ```
pub fn is_removable(&self) -> bool {
self.inner.is_removable()
}
/// Returns `true` if the disk is read-only.
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("[{:?}] is read-only: {}", disk.name(), disk.is_read_only());
/// }
/// ```
pub fn is_read_only(&self) -> bool {
self.inner.is_read_only()
}
/// Updates the disk' information with everything loaded.
///
/// Equivalent to <code>[Disk::refresh_specifics]\([DiskRefreshKind::everything]\())</code>.
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let mut disks = Disks::new_with_refreshed_list();
/// for disk in disks.list_mut() {
/// disk.refresh();
/// }
/// ```
pub fn refresh(&mut self) -> bool {
self.refresh_specifics(DiskRefreshKind::everything())
}
/// Updates the disk's information corresponding to the given [`DiskRefreshKind`].
///
/// ```no_run
/// use sysinfo::{Disks, DiskRefreshKind};
///
/// let mut disks = Disks::new_with_refreshed_list();
/// for disk in disks.list_mut() {
/// disk.refresh_specifics(DiskRefreshKind::nothing());
/// }
/// ```
pub fn refresh_specifics(&mut self, refreshes: DiskRefreshKind) -> bool {
self.inner.refresh_specifics(refreshes)
}
/// Returns number of bytes read and written by the disk
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("[{:?}] disk usage: {:?}", disk.name(), disk.usage());
/// }
/// ```
pub fn usage(&self) -> DiskUsage {
self.inner.usage()
}
}
/// Disks interface.
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("{disk:?}");
/// }
/// ```
///
/// ⚠️ Note that tmpfs mounts are excluded by default under Linux.
/// To display tmpfs mount points, the `linux-tmpfs` feature must be enabled.
///
/// ⚠️ Note that network devices are excluded by default under Linux.
/// To display mount points using the CIFS and NFS protocols, the `linux-netdevs`
/// feature must be enabled. Note, however, that sysinfo may hang under certain
/// circumstances. For example, if a CIFS or NFS share has been mounted with
/// the _hard_ option, but the connection has an error, such as the share server has stopped.
pub struct Disks {
inner: crate::DisksInner,
}
impl Default for Disks {
fn default() -> Self {
Self::new()
}
}
impl From<Disks> for Vec<Disk> {
fn from(disks: Disks) -> Vec<Disk> {
disks.inner.into_vec()
}
}
impl From<Vec<Disk>> for Disks {
fn from(disks: Vec<Disk>) -> Self {
Self {
inner: crate::DisksInner::from_vec(disks),
}
}
}
impl<'a> IntoIterator for &'a Disks {
type Item = &'a Disk;
type IntoIter = std::slice::Iter<'a, Disk>;
fn into_iter(self) -> Self::IntoIter {
self.list().iter()
}
}
impl<'a> IntoIterator for &'a mut Disks {
type Item = &'a mut Disk;
type IntoIter = std::slice::IterMut<'a, Disk>;
fn into_iter(self) -> Self::IntoIter {
self.list_mut().iter_mut()
}
}
impl Disks {
/// Creates a new empty [`Disks`][crate::Disks] type.
///
/// If you want it to be filled directly, take a look at [`Disks::new_with_refreshed_list`].
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let mut disks = Disks::new();
/// disks.refresh(false);
/// for disk in disks.list() {
/// println!("{disk:?}");
/// }
/// ```
pub fn new() -> Self {
Self {
inner: crate::DisksInner::new(),
}
}
/// Creates a new [`Disks`][crate::Disks] type with the disk list loaded.
///
/// Equivalent to <code>[Disks::new_with_refreshed_list_specifics]\([DiskRefreshKind::everything]\())</code>.
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let mut disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("{disk:?}");
/// }
/// ```
pub fn new_with_refreshed_list() -> Self {
Self::new_with_refreshed_list_specifics(DiskRefreshKind::everything())
}
/// Creates a new [`Disks`][crate::Disks] type with the disk list loaded
/// and refreshed according to the given [`DiskRefreshKind`].
///
/// ```no_run
/// use sysinfo::{Disks, DiskRefreshKind};
///
/// let mut disks = Disks::new_with_refreshed_list_specifics(DiskRefreshKind::nothing());
/// for disk in disks.list() {
/// println!("{disk:?}");
/// }
/// ```
pub fn new_with_refreshed_list_specifics(refreshes: DiskRefreshKind) -> Self {
let mut disks = Self::new();
disks.refresh_specifics(false, refreshes);
disks
}
/// Returns the disks list.
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("{disk:?}");
/// }
/// ```
pub fn list(&self) -> &[Disk] {
self.inner.list()
}
/// Returns the disks list.
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let mut disks = Disks::new_with_refreshed_list();
/// for disk in disks.list_mut() {
/// disk.refresh();
/// println!("{disk:?}");
/// }
/// ```
pub fn list_mut(&mut self) -> &mut [Disk] {
self.inner.list_mut()
}
/// Refreshes the listed disks' information.
///
/// Equivalent to <code>[Disks::refresh_specifics]\([DiskRefreshKind::everything]\())</code>.
pub fn refresh(&mut self, remove_not_listed_disks: bool) {
self.inner
.refresh_specifics(remove_not_listed_disks, DiskRefreshKind::everything());
}
/// Refreshes the disks' information according to the given [`DiskRefreshKind`].
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let mut disks = Disks::new_with_refreshed_list();
/// // We wait some time...?
/// disks.refresh(true);
/// ```
pub fn refresh_specifics(&mut self, remove_not_listed_disks: bool, refreshes: DiskRefreshKind) {
self.inner
.refresh_specifics(remove_not_listed_disks, refreshes);
}
}
impl std::ops::Deref for Disks {
type Target = [Disk];
fn deref(&self) -> &Self::Target {
self.list()
}
}
impl std::ops::DerefMut for Disks {
fn deref_mut(&mut self) -> &mut Self::Target {
self.list_mut()
}
}
/// Enum containing the different supported kinds of disks.
///
/// This type is returned by [`Disk::kind`](`crate::Disk::kind`).
///
/// ```no_run
/// use sysinfo::Disks;
///
/// let disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("{:?}: {:?}", disk.name(), disk.kind());
/// }
/// ```
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub enum DiskKind {
/// HDD type.
HDD,
/// SSD type.
SSD,
/// Unknown type.
Unknown(isize),
}
impl fmt::Display for DiskKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match *self {
DiskKind::HDD => "HDD",
DiskKind::SSD => "SSD",
_ => "Unknown",
})
}
}
/// Used to determine what you want to refresh specifically on the [`Disk`] type.
///
/// * `kind` is about refreshing the [`Disk::kind`] information.
/// * `storage` is about refreshing the [`Disk::available_space`] and [`Disk::total_space`] information.
/// * `io_usage` is about refreshing the [`Disk::usage`] information.
///
/// ```no_run
/// use sysinfo::{Disks, DiskRefreshKind};
///
/// let mut disks = Disks::new_with_refreshed_list_specifics(DiskRefreshKind::everything());
///
/// for disk in disks.list() {
/// assert!(disk.total_space() != 0);
/// }
/// ```
#[derive(Clone, Copy, Debug, Default)]
pub struct DiskRefreshKind {
kind: bool,
storage: bool,
io_usage: bool,
}
impl DiskRefreshKind {
/// Creates a new `DiskRefreshKind` with every refresh set to false.
///
/// ```
/// use sysinfo::DiskRefreshKind;
///
/// let r = DiskRefreshKind::nothing();
///
/// assert_eq!(r.kind(), false);
/// assert_eq!(r.storage(), false);
/// assert_eq!(r.io_usage(), false);
/// ```
pub fn nothing() -> Self {
Self::default()
}
/// Creates a new `DiskRefreshKind` with every refresh set to true.
///
/// ```
/// use sysinfo::DiskRefreshKind;
///
/// let r = DiskRefreshKind::everything();
///
/// assert_eq!(r.kind(), true);
/// assert_eq!(r.storage(), true);
/// assert_eq!(r.io_usage(), true);
/// ```
pub fn everything() -> Self {
Self {
kind: true,
storage: true,
io_usage: true,
}
}
impl_get_set!(DiskRefreshKind, kind, with_kind, without_kind);
impl_get_set!(DiskRefreshKind, storage, with_storage, without_storage);
impl_get_set!(DiskRefreshKind, io_usage, with_io_usage, without_io_usage);
}

View File

@@ -0,0 +1,173 @@
// Take a look at the license at the top of the repository in the LICENSE file.
macro_rules! impl_get_set {
($ty_name:ident, $name:ident, $with:ident, $without:ident $(, $extra_doc:literal)? $(,)?) => {
#[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind.")]
$(#[doc = concat!("
", $extra_doc, "
")])?
#[doc = concat!("
```
use sysinfo::", stringify!($ty_name), ";
let r = ", stringify!($ty_name), "::nothing();
let r = r.with_", stringify!($name), "();
assert_eq!(r.", stringify!($name), "(), true);
let r = r.without_", stringify!($name), "();
assert_eq!(r.", stringify!($name), "(), false);
```")]
pub fn $name(&self) -> bool {
self.$name
}
#[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `true`.
```
use sysinfo::", stringify!($ty_name), ";
let r = ", stringify!($ty_name), "::nothing();
let r = r.with_", stringify!($name), "();
assert_eq!(r.", stringify!($name), "(), true);
```")]
#[must_use]
pub fn $with(mut self) -> Self {
self.$name = true;
self
}
#[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `false`.
```
use sysinfo::", stringify!($ty_name), ";
let r = ", stringify!($ty_name), "::everything();
assert_eq!(r.", stringify!($name), "(), true);
let r = r.without_", stringify!($name), "();
assert_eq!(r.", stringify!($name), "(), false);
```")]
#[must_use]
pub fn $without(mut self) -> Self {
self.$name = false;
self
}
};
// To handle `UpdateKind`.
($ty_name:ident, $name:ident, $with:ident, $without:ident, UpdateKind $(, $extra_doc:literal)? $(,)?) => {
#[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind.")]
$(#[doc = concat!("
", $extra_doc, "
")])?
#[doc = concat!("
```
use sysinfo::{", stringify!($ty_name), ", UpdateKind};
let r = ", stringify!($ty_name), "::nothing();
assert_eq!(r.", stringify!($name), "(), UpdateKind::Never);
let r = r.with_", stringify!($name), "(UpdateKind::OnlyIfNotSet);
assert_eq!(r.", stringify!($name), "(), UpdateKind::OnlyIfNotSet);
let r = r.without_", stringify!($name), "();
assert_eq!(r.", stringify!($name), "(), UpdateKind::Never);
```")]
pub fn $name(&self) -> UpdateKind {
self.$name
}
#[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind.
```
use sysinfo::{", stringify!($ty_name), ", UpdateKind};
let r = ", stringify!($ty_name), "::nothing();
assert_eq!(r.", stringify!($name), "(), UpdateKind::Never);
let r = r.with_", stringify!($name), "(UpdateKind::OnlyIfNotSet);
assert_eq!(r.", stringify!($name), "(), UpdateKind::OnlyIfNotSet);
```")]
#[must_use]
pub fn $with(mut self, kind: UpdateKind) -> Self {
self.$name = kind;
self
}
#[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `UpdateKind::Never`.
```
use sysinfo::{", stringify!($ty_name), ", UpdateKind};
let r = ", stringify!($ty_name), "::everything();
assert_eq!(r.", stringify!($name), "(), UpdateKind::OnlyIfNotSet);
let r = r.without_", stringify!($name), "();
assert_eq!(r.", stringify!($name), "(), UpdateKind::Never);
```")]
#[must_use]
pub fn $without(mut self) -> Self {
self.$name = UpdateKind::Never;
self
}
};
// To handle `*RefreshKind`.
($ty_name:ident, $name:ident, $with:ident, $without:ident, $typ:ty $(,)?) => {
#[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind.
```
use sysinfo::{", stringify!($ty_name), ", ", stringify!($typ), "};
let r = ", stringify!($ty_name), "::nothing();
assert_eq!(r.", stringify!($name), "().is_some(), false);
let r = r.with_", stringify!($name), "(", stringify!($typ), "::everything());
assert_eq!(r.", stringify!($name), "().is_some(), true);
let r = r.without_", stringify!($name), "();
assert_eq!(r.", stringify!($name), "().is_some(), false);
```")]
pub fn $name(&self) -> Option<$typ> {
self.$name
}
#[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `Some(...)`.
```
use sysinfo::{", stringify!($ty_name), ", ", stringify!($typ), "};
let r = ", stringify!($ty_name), "::nothing();
assert_eq!(r.", stringify!($name), "().is_some(), false);
let r = r.with_", stringify!($name), "(", stringify!($typ), "::everything());
assert_eq!(r.", stringify!($name), "().is_some(), true);
```")]
#[must_use]
pub fn $with(mut self, kind: $typ) -> Self {
self.$name = Some(kind);
self
}
#[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `None`.
```
use sysinfo::", stringify!($ty_name), ";
let r = ", stringify!($ty_name), "::everything();
assert_eq!(r.", stringify!($name), "().is_some(), true);
let r = r.without_", stringify!($name), "();
assert_eq!(r.", stringify!($name), "().is_some(), false);
```")]
#[must_use]
pub fn $without(mut self) -> Self {
self.$name = None;
self
}
};
}
pub(crate) use impl_get_set;

148
vendor/sysinfo/src/common/mod.rs vendored Normal file
View File

@@ -0,0 +1,148 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[cfg(feature = "component")]
pub(crate) mod component;
#[cfg(feature = "disk")]
pub(crate) mod disk;
#[cfg(any(feature = "system", feature = "disk"))]
pub(crate) mod impl_get_set;
#[cfg(feature = "network")]
pub(crate) mod network;
#[cfg(feature = "system")]
pub(crate) mod system;
#[cfg(feature = "user")]
pub(crate) mod user;
/// Type containing read and written bytes.
///
/// It is returned by [`Process::disk_usage`][crate::Process::disk_usage] and [`Disk::usage`][crate::Disk::usage].
///
#[cfg_attr(not(all(feature = "system", feature = "disk")), doc = "```ignore")]
/// ```no_run
/// use sysinfo::{Disks, System};
///
/// let s = System::new_all();
/// for (pid, process) in s.processes() {
/// let disk_usage = process.disk_usage();
/// println!("[{}] read bytes : new/total => {}/{} B",
/// pid,
/// disk_usage.read_bytes,
/// disk_usage.total_read_bytes,
/// );
/// println!("[{}] written bytes: new/total => {}/{} B",
/// pid,
/// disk_usage.written_bytes,
/// disk_usage.total_written_bytes,
/// );
/// }
///
/// let disks = Disks::new_with_refreshed_list();
/// for disk in disks.list() {
/// println!("[{:?}] disk usage: {:?}", disk.name(), disk.usage());
/// }
/// ```
#[cfg(any(feature = "disk", feature = "system"))]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd)]
pub struct DiskUsage {
/// Total number of written bytes.
pub total_written_bytes: u64,
/// Number of written bytes since the last refresh.
pub written_bytes: u64,
/// Total number of read bytes.
pub total_read_bytes: u64,
/// Number of read bytes since the last refresh.
pub read_bytes: u64,
}
macro_rules! xid {
($(#[$outer:meta])+ $name:ident, $type:ty $(, $trait:ty)?) => {
#[cfg(any(feature = "system", feature = "user"))]
$(#[$outer])+
#[repr(transparent)]
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub struct $name(pub(crate) $type);
#[cfg(any(feature = "system", feature = "user"))]
impl std::ops::Deref for $name {
type Target = $type;
fn deref(&self) -> &Self::Target {
&self.0
}
}
$(
#[cfg(any(feature = "system", feature = "user"))]
impl TryFrom<usize> for $name {
type Error = <$type as TryFrom<usize>>::Error;
fn try_from(t: usize) -> Result<Self, <$type as TryFrom<usize>>::Error> {
Ok(Self(<$type>::try_from(t)?))
}
}
#[cfg(any(feature = "system", feature = "user"))]
impl $trait for $name {
type Err = <$type as $trait>::Err;
fn from_str(t: &str) -> Result<Self, <$type as $trait>::Err> {
Ok(Self(<$type>::from_str(t)?))
}
}
)?
};
}
macro_rules! uid {
($type:ty$(, $trait:ty)?) => {
xid!(
/// A user id wrapping a platform specific type.
Uid,
$type
$(, $trait)?
);
};
}
macro_rules! gid {
($type:ty) => {
xid!(
/// A group id wrapping a platform specific type.
#[derive(Copy)]
Gid,
$type,
std::str::FromStr
);
};
}
cfg_if! {
if #[cfg(all(
not(feature = "unknown-ci"),
any(
target_os = "freebsd",
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "ios",
)
))] {
uid!(libc::uid_t, std::str::FromStr);
gid!(libc::gid_t);
} else if #[cfg(windows)] {
uid!(crate::windows::Sid);
gid!(u32);
// Manual implementation outside of the macro...
#[cfg(any(feature = "system", feature = "user"))]
impl std::str::FromStr for Uid {
type Err = <crate::windows::Sid as std::str::FromStr>::Err;
fn from_str(t: &str) -> Result<Self, Self::Err> {
Ok(Self(t.parse()?))
}
}
} else {
uid!(u32, std::str::FromStr);
gid!(u32);
}
}

657
vendor/sysinfo/src/common/network.rs vendored Normal file
View File

@@ -0,0 +1,657 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use std::collections::HashMap;
use std::fmt;
use std::net::{AddrParseError, IpAddr};
use std::num::ParseIntError;
use std::str::FromStr;
use crate::{NetworkDataInner, NetworksInner};
/// Interacting with network interfaces.
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let networks = Networks::new_with_refreshed_list();
/// for (interface_name, network) in &networks {
/// println!("[{interface_name}]: {network:?}");
/// }
/// ```
pub struct Networks {
pub(crate) inner: NetworksInner,
}
impl<'a> IntoIterator for &'a Networks {
type Item = (&'a String, &'a NetworkData);
type IntoIter = std::collections::hash_map::Iter<'a, String, NetworkData>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl Default for Networks {
fn default() -> Self {
Networks::new()
}
}
impl Networks {
/// Creates a new empty [`Networks`][crate::Networks] type.
///
/// If you want it to be filled directly, take a look at [`Networks::new_with_refreshed_list`].
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let mut networks = Networks::new();
/// networks.refresh(true);
/// for (interface_name, network) in &networks {
/// println!("[{interface_name}]: {network:?}");
/// }
/// ```
pub fn new() -> Self {
Self {
inner: NetworksInner::new(),
}
}
/// Creates a new [`Networks`][crate::Networks] type with the network interfaces
/// list loaded.
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let networks = Networks::new_with_refreshed_list();
/// for network in &networks {
/// println!("{network:?}");
/// }
/// ```
pub fn new_with_refreshed_list() -> Self {
let mut networks = Self::new();
networks.refresh(false);
networks
}
/// Returns the network interfaces map.
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let networks = Networks::new_with_refreshed_list();
/// for network in networks.list() {
/// println!("{network:?}");
/// }
/// ```
pub fn list(&self) -> &HashMap<String, NetworkData> {
self.inner.list()
}
/// Refreshes the network interfaces.
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let mut networks = Networks::new_with_refreshed_list();
/// // Wait some time...? Then refresh the data of each network.
/// networks.refresh(true);
/// ```
pub fn refresh(&mut self, remove_not_listed_interfaces: bool) {
self.inner.refresh(remove_not_listed_interfaces)
}
}
impl std::ops::Deref for Networks {
type Target = HashMap<String, NetworkData>;
fn deref(&self) -> &Self::Target {
self.list()
}
}
/// Getting volume of received and transmitted data.
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let networks = Networks::new_with_refreshed_list();
/// for (interface_name, network) in &networks {
/// println!("[{interface_name}] {network:?}");
/// }
/// ```
pub struct NetworkData {
pub(crate) inner: NetworkDataInner,
}
impl NetworkData {
/// Returns the number of received bytes since the last refresh.
///
/// If you want the total number of bytes received, take a look at the
/// [`total_received`](NetworkData::total_received) method.
///
/// ```no_run
/// use sysinfo::Networks;
/// use std::{thread, time};
///
/// let mut networks = Networks::new_with_refreshed_list();
/// // Waiting a bit to get data from network...
/// thread::sleep(time::Duration::from_millis(10));
/// // Refreshing again to generate diff.
/// networks.refresh(true);
///
/// for (interface_name, network) in &networks {
/// println!("in: {} B", network.received());
/// }
/// ```
pub fn received(&self) -> u64 {
self.inner.received()
}
/// Returns the total number of received bytes.
///
/// If you want the amount of received bytes since the last refresh, take a look at the
/// [`received`](NetworkData::received) method.
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let networks = Networks::new_with_refreshed_list();
/// for (interface_name, network) in &networks {
/// println!("in: {} B", network.total_received());
/// }
/// ```
pub fn total_received(&self) -> u64 {
self.inner.total_received()
}
/// Returns the number of transmitted bytes since the last refresh.
///
/// If you want the total number of bytes transmitted, take a look at the
/// [`total_transmitted`](NetworkData::total_transmitted) method.
///
/// ```no_run
/// use sysinfo::Networks;
/// use std::{thread, time};
///
/// let mut networks = Networks::new_with_refreshed_list();
/// // Waiting a bit to get data from network...
/// thread::sleep(time::Duration::from_millis(10));
/// // Refreshing again to generate diff.
/// networks.refresh(true);
///
/// for (interface_name, network) in &networks {
/// println!("out: {} B", network.transmitted());
/// }
/// ```
pub fn transmitted(&self) -> u64 {
self.inner.transmitted()
}
/// Returns the total number of transmitted bytes.
///
/// If you want the amount of transmitted bytes since the last refresh, take a look at the
/// [`transmitted`](NetworkData::transmitted) method.
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let networks = Networks::new_with_refreshed_list();
/// for (interface_name, network) in &networks {
/// println!("out: {} B", network.total_transmitted());
/// }
/// ```
pub fn total_transmitted(&self) -> u64 {
self.inner.total_transmitted()
}
/// Returns the number of incoming packets since the last refresh.
///
/// If you want the total number of packets received, take a look at the
/// [`total_packets_received`](NetworkData::total_packets_received) method.
///
/// ```no_run
/// use sysinfo::Networks;
/// use std::{thread, time};
///
/// let mut networks = Networks::new_with_refreshed_list();
/// // Waiting a bit to get data from network...
/// thread::sleep(time::Duration::from_millis(10));
/// // Refreshing again to generate diff.
/// networks.refresh(true);
///
/// for (interface_name, network) in &networks {
/// println!("in: {}", network.packets_received());
/// }
/// ```
pub fn packets_received(&self) -> u64 {
self.inner.packets_received()
}
/// Returns the total number of incoming packets.
///
/// If you want the amount of received packets since the last refresh, take a look at the
/// [`packets_received`](NetworkData::packets_received) method.
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let networks = Networks::new_with_refreshed_list();
/// for (interface_name, network) in &networks {
/// println!("in: {}", network.total_packets_received());
/// }
/// ```
pub fn total_packets_received(&self) -> u64 {
self.inner.total_packets_received()
}
/// Returns the number of outcoming packets since the last refresh.
///
/// If you want the total number of packets transmitted, take a look at the
/// [`total_packets_transmitted`](NetworkData::total_packets_transmitted) method.
///
/// ```no_run
/// use sysinfo::Networks;
/// use std::{thread, time};
///
/// let mut networks = Networks::new_with_refreshed_list();
/// // Waiting a bit to get data from network...
/// thread::sleep(time::Duration::from_millis(10));
/// // Refreshing again to generate diff.
/// networks.refresh(true);
///
/// for (interface_name, network) in &networks {
/// println!("out: {}", network.packets_transmitted());
/// }
/// ```
pub fn packets_transmitted(&self) -> u64 {
self.inner.packets_transmitted()
}
/// Returns the total number of outcoming packets.
///
/// If you want the amount of transmitted packets since the last refresh, take a look at the
/// [`packets_transmitted`](NetworkData::packets_transmitted) method.
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let networks = Networks::new_with_refreshed_list();
/// for (interface_name, network) in &networks {
/// println!("out: {}", network.total_packets_transmitted());
/// }
/// ```
pub fn total_packets_transmitted(&self) -> u64 {
self.inner.total_packets_transmitted()
}
/// Returns the number of incoming errors since the last refresh.
///
/// If you want the total number of errors on received packets, take a look at the
/// [`total_errors_on_received`](NetworkData::total_errors_on_received) method.
///
/// ```no_run
/// use sysinfo::Networks;
/// use std::{thread, time};
///
/// let mut networks = Networks::new_with_refreshed_list();
/// // Waiting a bit to get data from network...
/// thread::sleep(time::Duration::from_millis(10));
/// // Refreshing again to generate diff.
/// networks.refresh(true);
///
/// for (interface_name, network) in &networks {
/// println!("in: {}", network.errors_on_received());
/// }
/// ```
pub fn errors_on_received(&self) -> u64 {
self.inner.errors_on_received()
}
/// Returns the total number of incoming errors.
///
/// If you want the amount of errors on received packets since the last refresh, take a look at
/// the [`errors_on_received`](NetworkData::errors_on_received) method.
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let networks = Networks::new_with_refreshed_list();
/// for (interface_name, network) in &networks {
/// println!("in: {}", network.total_errors_on_received());
/// }
/// ```
pub fn total_errors_on_received(&self) -> u64 {
self.inner.total_errors_on_received()
}
/// Returns the number of outcoming errors since the last refresh.
///
/// If you want the total number of errors on transmitted packets, take a look at the
/// [`total_errors_on_transmitted`](NetworkData::total_errors_on_transmitted) method.
///
/// ```no_run
/// use sysinfo::Networks;
/// use std::{thread, time};
///
/// let mut networks = Networks::new_with_refreshed_list();
/// // Waiting a bit to get data from network...
/// thread::sleep(time::Duration::from_millis(10));
/// // Refreshing again to generate diff.
/// networks.refresh(true);
///
/// for (interface_name, network) in &networks {
/// println!("out: {}", network.errors_on_transmitted());
/// }
/// ```
pub fn errors_on_transmitted(&self) -> u64 {
self.inner.errors_on_transmitted()
}
/// Returns the total number of outcoming errors.
///
/// If you want the amount of errors on transmitted packets since the last refresh, take a look at
/// the [`errors_on_transmitted`](NetworkData::errors_on_transmitted) method.
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let networks = Networks::new_with_refreshed_list();
/// for (interface_name, network) in &networks {
/// println!("out: {}", network.total_errors_on_transmitted());
/// }
/// ```
pub fn total_errors_on_transmitted(&self) -> u64 {
self.inner.total_errors_on_transmitted()
}
/// Returns the MAC address associated to current interface.
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let mut networks = Networks::new_with_refreshed_list();
/// for (interface_name, network) in &networks {
/// println!("MAC address: {}", network.mac_address());
/// }
/// ```
pub fn mac_address(&self) -> MacAddr {
self.inner.mac_address()
}
/// Returns the Ip Networks associated to current interface.
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let mut networks = Networks::new_with_refreshed_list();
/// for (interface_name, network) in &networks {
/// println!("Ip Networks: {:?}", network.ip_networks());
/// }
/// ```
pub fn ip_networks(&self) -> &[IpNetwork] {
self.inner.ip_networks()
}
/// Returns the Maximum Transfer Unit (MTU) of the interface.
///
/// ```no_run
/// use sysinfo::Networks;
///
/// let mut networks = Networks::new_with_refreshed_list();
/// for (interface_name, network) in &networks {
/// println!("mtu: {}", network.mtu());
/// }
/// ```
pub fn mtu(&self) -> u64 {
self.inner.mtu()
}
}
/// MAC address for network interface.
///
/// It is returned by [`NetworkData::mac_address`][crate::NetworkData::mac_address].
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct MacAddr(pub [u8; 6]);
impl MacAddr {
/// A `MacAddr` with all bytes set to `0`.
pub const UNSPECIFIED: Self = MacAddr([0; 6]);
/// Checks if this `MacAddr` has all bytes equal to `0`.
pub fn is_unspecified(&self) -> bool {
self == &MacAddr::UNSPECIFIED
}
}
impl fmt::Display for MacAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let data = &self.0;
write!(
f,
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
data[0], data[1], data[2], data[3], data[4], data[5],
)
}
}
/// Error type returned from `MacAddr::from_str` implementation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MacAddrFromStrError {
/// A number is not in hexadecimal format.
IntError(ParseIntError),
/// Input is not of format `{02X}:{02X}:{02X}:{02X}:{02X}:{02X}`.
InvalidAddrFormat,
}
impl FromStr for MacAddr {
type Err = MacAddrFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s
.split(':')
.map(|s| u8::from_str_radix(s, 16).map_err(MacAddrFromStrError::IntError));
let Some(data0) = parts.next() else {
return Err(MacAddrFromStrError::InvalidAddrFormat);
};
let Some(data1) = parts.next() else {
return Err(MacAddrFromStrError::InvalidAddrFormat);
};
let Some(data2) = parts.next() else {
return Err(MacAddrFromStrError::InvalidAddrFormat);
};
let Some(data3) = parts.next() else {
return Err(MacAddrFromStrError::InvalidAddrFormat);
};
let Some(data4) = parts.next() else {
return Err(MacAddrFromStrError::InvalidAddrFormat);
};
let Some(data5) = parts.next() else {
return Err(MacAddrFromStrError::InvalidAddrFormat);
};
if parts.next().is_some() {
return Err(MacAddrFromStrError::InvalidAddrFormat);
}
Ok(MacAddr([data0?, data1?, data2?, data3?, data4?, data5?]))
}
}
/// IP networks address for network interface.
///
/// It is returned by [`NetworkData::ip_networks`][crate::NetworkData::ip_networks].
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct IpNetwork {
/// The IP of the network interface.
pub addr: IpAddr,
/// The netmask, prefix of the IP address.
pub prefix: u8,
}
impl fmt::Display for IpNetwork {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.addr, self.prefix)
}
}
/// Error type returned from `MacAddr::from_str` implementation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IpNetworkFromStrError {
/// Prefix is not an integer.
PrefixError(ParseIntError),
/// Failed to parse IP address.
AddrParseError(AddrParseError),
/// Input is not of format `[IP address]/[number]`.
InvalidAddrFormat,
}
impl FromStr for IpNetwork {
type Err = IpNetworkFromStrError;
#[allow(clippy::from_str_radix_10)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split('/');
let Some(addr) = parts.next() else {
return Err(IpNetworkFromStrError::InvalidAddrFormat);
};
let Some(prefix) = parts.next() else {
return Err(IpNetworkFromStrError::InvalidAddrFormat);
};
if parts.next().is_some() {
return Err(IpNetworkFromStrError::InvalidAddrFormat);
}
Ok(IpNetwork {
addr: IpAddr::from_str(addr).map_err(IpNetworkFromStrError::AddrParseError)?,
prefix: u8::from_str_radix(prefix, 10).map_err(IpNetworkFromStrError::PrefixError)?,
})
}
}
#[cfg(test)]
mod tests {
use crate::*;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
// Ensure that the `Display` and `Debug` traits are implemented on the `MacAddr` struct
#[test]
fn check_display_impl_mac_address() {
println!(
"{} {:?}",
MacAddr([0x1, 0x2, 0x3, 0x4, 0x5, 0x6]),
MacAddr([0xa, 0xb, 0xc, 0xd, 0xe, 0xf])
);
}
#[test]
fn check_mac_address_is_unspecified_true() {
assert!(MacAddr::UNSPECIFIED.is_unspecified());
assert!(MacAddr([0; 6]).is_unspecified());
}
#[test]
fn check_mac_address_is_unspecified_false() {
assert!(!MacAddr([1, 2, 3, 4, 5, 6]).is_unspecified());
}
#[test]
fn check_mac_address_conversions() {
let mac = MacAddr([0xa, 0xb, 0xc, 0xd, 0xe, 0xf]);
let mac_s = mac.to_string();
assert_eq!("0a:0b:0c:0d:0e:0f", mac_s);
assert_eq!(Ok(mac), MacAddr::from_str(&mac_s));
assert_eq!(
MacAddr::from_str("0a:0b:0c:0d:0e:0f:01"),
Err(MacAddrFromStrError::InvalidAddrFormat)
);
assert_eq!(
MacAddr::from_str("0a:0b:0c:0d:0e"),
Err(MacAddrFromStrError::InvalidAddrFormat)
);
}
// Ensure that the `Display` and `Debug` traits are implemented on the `IpNetwork` struct
#[test]
fn check_display_impl_ip_network_ipv4() {
println!(
"{} {:?}",
IpNetwork {
addr: IpAddr::from(Ipv4Addr::new(1, 2, 3, 4)),
prefix: 3
},
IpNetwork {
addr: IpAddr::from(Ipv4Addr::new(255, 255, 255, 0)),
prefix: 21
}
);
}
#[test]
fn check_display_impl_ip_network_ipv6() {
println!(
"{} {:?}",
IpNetwork {
addr: IpAddr::from(Ipv6Addr::new(0xffff, 0xaabb, 00, 0, 0, 0x000c, 11, 21)),
prefix: 127
},
IpNetwork {
addr: IpAddr::from(Ipv6Addr::new(0xffcc, 0, 0, 0xffcc, 0, 0xffff, 0, 0xccaa)),
prefix: 120
}
)
}
#[test]
fn check_ip_networks() {
if !IS_SUPPORTED_SYSTEM {
return;
}
let networks = Networks::new_with_refreshed_list();
if networks.iter().any(|(_, n)| !n.ip_networks().is_empty()) {
return;
}
panic!("Networks should have at least one IP network ");
}
#[test]
fn check_ip_network_conversions() {
let addr = IpNetwork {
addr: IpAddr::from(Ipv6Addr::new(0xff, 0xa, 0x8, 0x12, 0x7, 0xc, 0xa, 0xb)),
prefix: 12,
};
let addr_s = addr.to_string();
assert_eq!("ff:a:8:12:7:c:a:b/12", addr_s);
assert_eq!(Ok(addr), IpNetwork::from_str(&addr_s));
let addr = IpNetwork {
addr: IpAddr::from(Ipv4Addr::new(255, 255, 255, 0)),
prefix: 21,
};
let addr_s = addr.to_string();
assert_eq!("255.255.255.0/21", addr_s);
assert_eq!(Ok(addr), IpNetwork::from_str(&addr_s));
assert_eq!(
IpNetwork::from_str("ff:a:8:12:7:c:a:b"),
Err(IpNetworkFromStrError::InvalidAddrFormat)
);
assert_eq!(
IpNetwork::from_str("ff:a:8:12:7:c:a:b/12/12"),
Err(IpNetworkFromStrError::InvalidAddrFormat)
);
match IpNetwork::from_str("0a:0b:0c:0d:0e/12") {
Err(IpNetworkFromStrError::AddrParseError(_)) => {}
x => panic!("expected `IpNetworkFromStrError::AddrParseError`, found {x:?}"),
}
}
}

2810
vendor/sysinfo/src/common/system.rs vendored Normal file

File diff suppressed because it is too large Load Diff

534
vendor/sysinfo/src/common/user.rs vendored Normal file
View File

@@ -0,0 +1,534 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use std::cmp::Ordering;
use crate::{Gid, Uid, UserInner};
/// Type containing user information.
///
/// It is returned by [`Users`][crate::Users].
///
/// ```no_run
/// use sysinfo::Users;
///
/// let users = Users::new_with_refreshed_list();
/// for user in users.list() {
/// println!("{:?}", user);
/// }
/// ```
pub struct User {
pub(crate) inner: UserInner,
}
impl PartialEq for User {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
&& self.group_id() == other.group_id()
&& self.name() == other.name()
}
}
impl Eq for User {}
impl PartialOrd for User {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for User {
fn cmp(&self, other: &Self) -> Ordering {
self.name().cmp(other.name())
}
}
impl User {
/// Returns the ID of the user.
///
/// ```no_run
/// use sysinfo::Users;
///
/// let users = Users::new_with_refreshed_list();
/// for user in users.list() {
/// println!("{:?}", *user.id());
/// }
/// ```
pub fn id(&self) -> &Uid {
self.inner.id()
}
/// Returns the group ID of the user.
///
/// ⚠️ This information is not set on Windows. Windows doesn't have a `username` specific
/// group assigned to the user. They do however have unique
/// [Security Identifiers](https://docs.microsoft.com/en-us/windows/win32/secauthz/security-identifiers)
/// made up of various [Components](https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-components).
/// Pieces of the SID may be a candidate for this field, but it doesn't map well to a single
/// group ID.
///
/// ```no_run
/// use sysinfo::Users;
///
/// let users = Users::new_with_refreshed_list();
/// for user in users.list() {
/// println!("{}", *user.group_id());
/// }
/// ```
pub fn group_id(&self) -> Gid {
self.inner.group_id()
}
/// Returns the name of the user.
///
/// ```no_run
/// use sysinfo::Users;
///
/// let users = Users::new_with_refreshed_list();
/// for user in users.list() {
/// println!("{}", user.name());
/// }
/// ```
pub fn name(&self) -> &str {
self.inner.name()
}
/// Returns the groups of the user.
///
/// ⚠️ This is computed every time this method is called.
///
/// ```no_run
/// use sysinfo::Users;
///
/// let users = Users::new_with_refreshed_list();
/// for user in users.list() {
/// println!("{} is in {:?}", user.name(), user.groups());
/// }
/// ```
pub fn groups(&self) -> Vec<Group> {
self.inner.groups()
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
pub(crate) struct GroupInner {
pub(crate) id: Gid,
pub(crate) name: String,
}
/// Type containing group information.
///
/// It is returned by [`User::groups`] or [`Groups::list`].
///
/// ```no_run
/// use sysinfo::Users;
///
/// let mut users = Users::new_with_refreshed_list();
///
/// for user in users.list() {
/// println!(
/// "user: (ID: {:?}, group ID: {:?}, name: {:?})",
/// user.id(),
/// user.group_id(),
/// user.name(),
/// );
/// for group in user.groups() {
/// println!("group: (ID: {:?}, name: {:?})", group.id(), group.name());
/// }
/// }
/// ```
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Group {
pub(crate) inner: GroupInner,
}
impl Group {
/// Returns the ID of the group.
///
/// ⚠️ This information is not set on Windows.
///
/// ```no_run
/// use sysinfo::Users;
///
/// let mut users = Users::new_with_refreshed_list();
///
/// for user in users.list() {
/// for group in user.groups() {
/// println!("{:?}", group.id());
/// }
/// }
/// ```
pub fn id(&self) -> &Gid {
self.inner.id()
}
/// Returns the name of the group.
///
/// ```no_run
/// use sysinfo::Users;
///
/// let mut users = Users::new_with_refreshed_list();
///
/// for user in users.list() {
/// for group in user.groups() {
/// println!("{}", group.name());
/// }
/// }
/// ```
pub fn name(&self) -> &str {
self.inner.name()
}
}
/// Interacting with users.
///
/// ```no_run
/// use sysinfo::Users;
///
/// let mut users = Users::new();
/// for user in users.list() {
/// println!("{} is in {} groups", user.name(), user.groups().len());
/// }
/// ```
pub struct Users {
users: Vec<User>,
}
impl Default for Users {
fn default() -> Self {
Self::new()
}
}
impl From<Users> for Vec<User> {
fn from(users: Users) -> Self {
users.users
}
}
impl From<Vec<User>> for Users {
fn from(users: Vec<User>) -> Self {
Self { users }
}
}
impl std::ops::Deref for Users {
type Target = [User];
fn deref(&self) -> &Self::Target {
self.list()
}
}
impl std::ops::DerefMut for Users {
fn deref_mut(&mut self) -> &mut Self::Target {
self.list_mut()
}
}
impl<'a> IntoIterator for &'a Users {
type Item = &'a User;
type IntoIter = std::slice::Iter<'a, User>;
fn into_iter(self) -> Self::IntoIter {
self.list().iter()
}
}
impl<'a> IntoIterator for &'a mut Users {
type Item = &'a mut User;
type IntoIter = std::slice::IterMut<'a, User>;
fn into_iter(self) -> Self::IntoIter {
self.list_mut().iter_mut()
}
}
impl Users {
/// Creates a new empty [`Users`][crate::Users] type.
///
/// If you want it to be filled directly, take a look at [`Users::new_with_refreshed_list`].
///
/// ```no_run
/// use sysinfo::Users;
///
/// let mut users = Users::new();
/// users.refresh();
/// for user in users.list() {
/// println!("{user:?}");
/// }
/// ```
pub fn new() -> Self {
Self { users: Vec::new() }
}
/// Creates a new [`Users`][crate::Users] type with the user list loaded.
///
/// ```no_run
/// use sysinfo::Users;
///
/// let mut users = Users::new_with_refreshed_list();
/// for user in users.list() {
/// println!("{user:?}");
/// }
/// ```
pub fn new_with_refreshed_list() -> Self {
let mut users = Self::new();
users.refresh();
users
}
/// Returns the users list.
///
/// ```no_run
/// use sysinfo::Users;
///
/// let users = Users::new_with_refreshed_list();
/// for user in users.list() {
/// println!("{user:?}");
/// }
/// ```
pub fn list(&self) -> &[User] {
&self.users
}
/// Returns the users list.
///
/// ```no_run
/// use sysinfo::Users;
///
/// let mut users = Users::new_with_refreshed_list();
/// users.list_mut().sort_by(|user1, user2| {
/// user1.name().partial_cmp(user2.name()).unwrap()
/// });
/// ```
pub fn list_mut(&mut self) -> &mut [User] {
&mut self.users
}
/// The user list will be emptied then completely recomputed.
///
/// ```no_run
/// use sysinfo::Users;
///
/// let mut users = Users::new();
/// users.refresh();
/// ```
pub fn refresh(&mut self) {
crate::sys::get_users(&mut self.users);
}
/// Returns the [`User`] matching the given `user_id`.
///
/// **Important**: The user list must be filled before using this method, otherwise it will
/// always return `None` (through the `refresh_*` methods).
///
/// It is a shorthand for:
///
/// ```ignore
/// # use sysinfo::Users;
/// let users = Users::new_with_refreshed_list();
/// users.list().find(|user| user.id() == user_id);
/// ```
///
/// Full example:
///
#[cfg_attr(feature = "system", doc = "```no_run")]
#[cfg_attr(not(feature = "system"), doc = "```ignore")]
/// use sysinfo::{Pid, System, Users};
///
/// let mut s = System::new_all();
/// let users = Users::new_with_refreshed_list();
///
/// if let Some(process) = s.process(Pid::from(1337)) {
/// if let Some(user_id) = process.user_id() {
/// println!("User for process 1337: {:?}", users.get_user_by_id(user_id));
/// }
/// }
/// ```
pub fn get_user_by_id(&self, user_id: &Uid) -> Option<&User> {
self.users.iter().find(|user| user.id() == user_id)
}
}
/// Interacting with groups.
///
/// ```no_run
/// use sysinfo::Groups;
///
/// let mut groups = Groups::new();
/// for group in groups.list() {
/// println!("{}", group.name());
/// }
/// ```
pub struct Groups {
groups: Vec<Group>,
}
impl Default for Groups {
fn default() -> Self {
Self::new()
}
}
impl From<Groups> for Vec<Group> {
fn from(groups: Groups) -> Self {
groups.groups
}
}
impl From<Vec<Group>> for Groups {
fn from(groups: Vec<Group>) -> Self {
Self { groups }
}
}
impl std::ops::Deref for Groups {
type Target = [Group];
fn deref(&self) -> &Self::Target {
self.list()
}
}
impl std::ops::DerefMut for Groups {
fn deref_mut(&mut self) -> &mut Self::Target {
self.list_mut()
}
}
impl<'a> IntoIterator for &'a Groups {
type Item = &'a Group;
type IntoIter = std::slice::Iter<'a, Group>;
fn into_iter(self) -> Self::IntoIter {
self.list().iter()
}
}
impl<'a> IntoIterator for &'a mut Groups {
type Item = &'a mut Group;
type IntoIter = std::slice::IterMut<'a, Group>;
fn into_iter(self) -> Self::IntoIter {
self.list_mut().iter_mut()
}
}
impl Groups {
/// Creates a new empty [`Groups`][crate::Groups] type.
///
/// If you want it to be filled directly, take a look at [`Groups::new_with_refreshed_list`].
///
/// ```no_run
/// use sysinfo::Groups;
///
/// let mut groups = Groups::new();
/// groups.refresh();
/// for group in groups.list() {
/// println!("{group:?}");
/// }
/// ```
pub fn new() -> Self {
Self { groups: Vec::new() }
}
/// Creates a new [`Groups`][crate::Groups] type with the group list loaded.
///
/// ```no_run
/// use sysinfo::Groups;
///
/// let mut groups = Groups::new_with_refreshed_list();
/// for group in groups.list() {
/// println!("{group:?}");
/// }
/// ```
pub fn new_with_refreshed_list() -> Self {
let mut groups = Self::new();
groups.refresh();
groups
}
/// Returns the groups list.
///
/// ```no_run
/// use sysinfo::Groups;
///
/// let groups = Groups::new_with_refreshed_list();
/// for group in groups.list() {
/// println!("{group:?}");
/// }
/// ```
pub fn list(&self) -> &[Group] {
&self.groups
}
/// Returns the groups list.
///
/// ```no_run
/// use sysinfo::Groups;
///
/// let mut groups = Groups::new_with_refreshed_list();
/// groups.list_mut().sort_by(|user1, user2| {
/// user1.name().partial_cmp(user2.name()).unwrap()
/// });
/// ```
pub fn list_mut(&mut self) -> &mut [Group] {
&mut self.groups
}
/// The group list will be emptied then completely recomputed.
///
/// ```no_run
/// use sysinfo::Groups;
///
/// let mut groups = Groups::new();
/// groups.refresh();
/// ```
pub fn refresh(&mut self) {
crate::sys::get_groups(&mut self.groups);
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn check_list() {
let mut users = Users::new();
assert!(users.list().is_empty());
users.refresh();
assert!(users.list().len() >= MIN_USERS);
}
// This test exists to ensure that the `TryFrom<usize>` and `FromStr` traits are implemented
// on `Uid`, `Gid` and `Pid`.
#[allow(clippy::unnecessary_fallible_conversions)]
#[test]
fn check_uid_gid_from_impls() {
use std::convert::TryFrom;
use std::str::FromStr;
#[cfg(not(windows))]
{
assert!(crate::Uid::try_from(0usize).is_ok());
assert!(crate::Uid::from_str("0").is_ok());
}
#[cfg(windows)]
{
assert!(crate::Uid::from_str("S-1-5-18").is_ok()); // SECURITY_LOCAL_SYSTEM_RID
assert!(crate::Uid::from_str("0").is_err());
}
assert!(crate::Gid::try_from(0usize).is_ok());
assert!(crate::Gid::from_str("0").is_ok());
}
#[test]
fn check_groups() {
if !crate::IS_SUPPORTED_SYSTEM {
return;
}
assert!(!Groups::new_with_refreshed_list().is_empty());
}
}

154
vendor/sysinfo/src/debug.rs vendored Normal file
View File

@@ -0,0 +1,154 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[cfg(feature = "system")]
impl std::fmt::Debug for crate::Cpu {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Cpu")
.field("name", &self.name())
.field("CPU usage", &self.cpu_usage())
.field("frequency", &self.frequency())
.field("vendor ID", &self.vendor_id())
.field("brand", &self.brand())
.finish()
}
}
#[cfg(feature = "system")]
impl std::fmt::Debug for crate::System {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("System")
.field("global CPU usage", &self.global_cpu_usage())
.field("load average", &Self::load_average())
.field("total memory", &self.total_memory())
.field("free memory", &self.free_memory())
.field("total swap", &self.total_swap())
.field("free swap", &self.free_swap())
.field("nb CPUs", &self.cpus().len())
.field("nb processes", &self.processes().len())
.finish()
}
}
#[cfg(feature = "system")]
impl std::fmt::Debug for crate::Process {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Process")
.field("pid", &self.pid())
.field("parent", &self.parent())
.field("name", &self.name())
.field("environ", &self.environ())
.field("command", &self.cmd())
.field("executable path", &self.exe())
.field("current working directory", &self.cwd())
.field("memory usage", &self.memory())
.field("virtual memory usage", &self.virtual_memory())
.field("CPU usage", &self.cpu_usage())
.field("accumulated CPU time", &self.accumulated_cpu_time())
.field("status", &self.status())
.field("root", &self.root())
.field("disk_usage", &self.disk_usage())
.field("user_id", &self.user_id())
.field("effective_user_id", &self.effective_user_id())
.finish()
}
}
#[cfg(feature = "disk")]
impl std::fmt::Debug for crate::Disk {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
fmt,
"Disk({:?})[FS: {:?}][Type: {:?}][removable: {}][I/O: {:?}] mounted on {:?}: {}/{} B",
self.name(),
self.file_system(),
self.kind(),
if self.is_removable() { "yes" } else { "no" },
self.usage(),
self.mount_point(),
self.available_space(),
self.total_space(),
)
}
}
#[cfg(feature = "disk")]
impl std::fmt::Debug for crate::Disks {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
#[cfg(feature = "component")]
impl std::fmt::Debug for crate::Components {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
#[cfg(feature = "component")]
impl std::fmt::Debug for crate::Component {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} ", self.label())?;
if let Some(temperature) = self.temperature() {
write!(f, "temperature: {temperature}°C (")?;
} else {
f.write_str("temperature: unknown (")?;
}
if let Some(max) = self.max() {
write!(f, "max: {max}°C / ")?;
} else {
f.write_str("max: unknown / ")?;
}
if let Some(critical) = self.critical() {
write!(f, "critical: {critical}°C)")
} else {
f.write_str("critical: unknown)")
}
}
}
#[cfg(feature = "network")]
impl std::fmt::Debug for crate::Networks {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
#[cfg(feature = "network")]
impl std::fmt::Debug for crate::NetworkData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NetworkData")
.field("income", &self.received())
.field("total income", &self.total_received())
.field("outcome", &self.transmitted())
.field("total outcome", &self.total_transmitted())
.field("packets income", &self.packets_received())
.field("total packets income", &self.total_packets_received())
.field("packets outcome", &self.packets_transmitted())
.field("total packets outcome", &self.total_packets_transmitted())
.field("errors income", &self.errors_on_received())
.field("total errors income", &self.total_errors_on_received())
.field("errors outcome", &self.errors_on_transmitted())
.field("total errors outcome", &self.total_errors_on_transmitted())
.field("maximum transfer unit", &self.mtu())
.finish()
}
}
#[cfg(feature = "user")]
impl std::fmt::Debug for crate::Users {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
#[cfg(feature = "user")]
impl std::fmt::Debug for crate::User {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("User")
.field("uid", &self.id())
.field("gid", &self.group_id())
.field("name", &self.name())
.finish()
}
}

357
vendor/sysinfo/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,357 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#![cfg_attr(
all(feature = "system", feature = "disk", feature = "component", feature = "system"),
doc = include_str!("../README.md")
)]
#![cfg_attr(
not(all(
feature = "system",
feature = "disk",
feature = "component",
feature = "system"
)),
doc = "For crate-level documentation, all features need to be enabled."
)]
#![cfg_attr(feature = "serde", doc = include_str!("../md_doc/serde.md"))]
#![allow(unknown_lints)]
#![deny(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::non_send_fields_in_send_ty)]
#![allow(renamed_and_removed_lints)]
#![allow(clippy::assertions_on_constants)]
#[macro_use]
mod macros;
cfg_if! {
if #[cfg(feature = "unknown-ci")] {
// This is used in CI to check that the build for unknown targets is compiling fine.
mod unknown;
use crate::unknown as sys;
#[cfg(test)]
pub(crate) const MIN_USERS: usize = 0;
} else if #[cfg(any(
target_os = "macos", target_os = "ios",
target_os = "linux", target_os = "android",
target_os = "freebsd"))]
{
mod unix;
use crate::unix::sys as sys;
#[cfg(feature = "network")]
mod network;
#[cfg(feature = "network")]
use crate::unix::network_helper;
#[cfg(test)]
pub(crate) const MIN_USERS: usize = 1;
} else if #[cfg(windows)] {
mod windows;
use crate::windows as sys;
#[cfg(feature = "network")]
mod network;
#[cfg(feature = "network")]
use crate::windows::network_helper;
#[cfg(test)]
pub(crate) const MIN_USERS: usize = 1;
} else {
mod unknown;
use crate::unknown as sys;
#[cfg(test)]
pub(crate) const MIN_USERS: usize = 0;
}
}
#[cfg(feature = "component")]
pub use crate::common::component::{Component, Components};
#[cfg(feature = "disk")]
pub use crate::common::disk::{Disk, DiskKind, DiskRefreshKind, Disks};
#[cfg(feature = "network")]
pub use crate::common::network::{
IpNetwork, IpNetworkFromStrError, MacAddr, MacAddrFromStrError, NetworkData, Networks,
};
#[cfg(feature = "system")]
pub use crate::common::system::{
get_current_pid, CGroupLimits, Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process,
ProcessRefreshKind, ProcessStatus, ProcessesToUpdate, RefreshKind, Signal, System, ThreadKind,
UpdateKind,
};
#[cfg(feature = "user")]
pub use crate::common::user::{Group, Groups, User, Users};
#[cfg(any(feature = "user", feature = "system"))]
pub use crate::common::{Gid, Uid};
#[cfg(feature = "system")]
pub use crate::sys::{MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS};
#[cfg(any(feature = "system", feature = "disk"))]
pub use crate::common::DiskUsage;
#[cfg(feature = "user")]
pub(crate) use crate::common::user::GroupInner;
#[cfg(feature = "user")]
pub(crate) use crate::sys::UserInner;
#[cfg(feature = "component")]
pub(crate) use crate::sys::{ComponentInner, ComponentsInner};
#[cfg(feature = "system")]
pub(crate) use crate::sys::{CpuInner, ProcessInner, SystemInner};
#[cfg(feature = "disk")]
pub(crate) use crate::sys::{DiskInner, DisksInner};
#[cfg(feature = "network")]
pub(crate) use crate::sys::{NetworkDataInner, NetworksInner};
pub use crate::sys::IS_SUPPORTED_SYSTEM;
#[cfg(feature = "c-interface")]
pub use crate::c_interface::*;
#[cfg(feature = "c-interface")]
mod c_interface;
mod common;
mod debug;
#[cfg(feature = "serde")]
mod serde;
pub(crate) mod utils;
// Make formattable by rustfmt.
#[cfg(any())]
mod network;
#[cfg(any())]
mod unix;
#[cfg(any())]
mod unknown;
#[cfg(any())]
mod windows;
/// This function is only used on Linux targets, when the `system` feature is enabled. In other
/// cases, it does nothing and returns `false`.
///
/// On Linux, to improve performance, we keep a `/proc` file open for each process we index with
/// a maximum number of files open equivalent to half of the system limit.
///
/// The problem is that some users might need all the available file descriptors so we need to
/// allow them to change this limit.
///
/// Note that if you set a limit bigger than the system limit, the system limit will be set.
///
/// Returns `true` if the new value has been set.
///
#[cfg_attr(feature = "system", doc = "```no_run")]
#[cfg_attr(not(feature = "system"), doc = "```ignore")]
/// use sysinfo::{System, set_open_files_limit};
///
/// // We call the function before any call to the processes update.
/// if !set_open_files_limit(10) {
/// // It'll always return false on non-linux targets.
/// eprintln!("failed to update the open files limit...");
/// }
/// let s = System::new_all();
/// ```
pub fn set_open_files_limit(mut _new_limit: isize) -> bool {
cfg_if! {
if #[cfg(all(feature = "system", not(feature = "unknown-ci"), any(target_os = "linux", target_os = "android")))]
{
use crate::sys::system::remaining_files;
use std::sync::atomic::Ordering;
if _new_limit < 0 {
_new_limit = 0;
}
let max = sys::system::get_max_nb_fds();
if _new_limit > max {
_new_limit = max;
}
// If files are already open, to be sure that the number won't be bigger when those
// files are closed, we subtract the current number of opened files to the new
// limit.
remaining_files().fetch_update(Ordering::SeqCst, Ordering::SeqCst, |remaining| {
let diff = max.saturating_sub(remaining);
Some(_new_limit.saturating_sub(diff))
}).unwrap();
true
} else {
false
}
}
}
#[cfg(doctest)]
mod doctest {
macro_rules! compile_fail_import {
($mod_name:ident => $($imports:ident),+ $(,)?) => {
$(#[doc = concat!(r"```compile_fail
use sysinfo::", stringify!($imports), r";
```
")])+
mod $mod_name {}
};
}
#[cfg(not(feature = "system"))]
compile_fail_import!(
no_system_feature =>
get_current_pid,
CGroupLimits,
Cpu,
CpuRefreshKind,
DiskUsage,
LoadAvg,
MemoryRefreshKind,
Pid,
Process,
ProcessesToUpdate,
ProcessRefreshKind,
ProcessStatus,
RefreshKind,
Signal,
System,
ThreadKind,
UpdateKind,
);
#[cfg(not(feature = "disk"))]
compile_fail_import!(
no_disk_feature =>
Disk,
Disks,
DiskKind,
);
#[cfg(not(feature = "component"))]
compile_fail_import!(
no_component_feature =>
Component,
Components,
);
#[cfg(not(feature = "network"))]
compile_fail_import!(
no_network_feature =>
IpNetwork,
MacAddr,
NetworkData,
Networks,
);
#[cfg(not(feature = "user"))]
compile_fail_import!(
no_user_feature =>
Group,
Groups,
User,
Users,
);
}
#[cfg(test)]
mod test {
use crate::*;
#[cfg(feature = "unknown-ci")]
#[test]
fn check_unknown_ci_feature() {
assert!(!IS_SUPPORTED_SYSTEM);
}
// If this test doesn't compile, it means the current OS doesn't implement them correctly.
#[test]
fn check_macro_types() {
fn check_is_supported(_: bool) {}
check_is_supported(IS_SUPPORTED_SYSTEM);
}
// If this test doesn't compile, it means the current OS doesn't implement them correctly.
#[cfg(feature = "system")]
#[test]
fn check_macro_types2() {
fn check_supported_signals(_: &'static [Signal]) {}
fn check_minimum_cpu_update_interval(_: std::time::Duration) {}
check_supported_signals(SUPPORTED_SIGNALS);
check_minimum_cpu_update_interval(MINIMUM_CPU_UPDATE_INTERVAL);
}
#[cfg(feature = "user")]
#[test]
fn check_uid_gid() {
let mut users = Users::new();
assert!(users.list().is_empty());
users.refresh();
let user_list = users.list();
assert!(user_list.len() >= MIN_USERS);
if IS_SUPPORTED_SYSTEM {
#[cfg(not(target_os = "windows"))]
{
let user = user_list
.iter()
.find(|u| u.name() == "root")
.expect("no root user");
assert_eq!(**user.id(), 0);
assert_eq!(*user.group_id(), 0);
if let Some(user) = users.iter().find(|u| *u.group_id() > 0) {
assert!(**user.id() > 0);
assert!(*user.group_id() > 0);
}
assert!(user_list.iter().filter(|u| **u.id() > 0).count() > 0);
}
#[cfg(feature = "system")]
{
// And now check that our `get_user_by_id` method works.
let s =
System::new_with_specifics(RefreshKind::nothing().with_processes(
ProcessRefreshKind::nothing().with_user(UpdateKind::Always),
));
assert!(s
.processes()
.iter()
.filter_map(|(_, p)| p.user_id())
.any(|uid| users.get_user_by_id(uid).is_some()));
}
}
}
#[cfg(all(feature = "system", feature = "user"))]
#[test]
fn check_all_process_uids_resolvable() {
// On linux, some user IDs don't have an associated user (no idea why though).
// If `getent` doesn't find them, we can assume it's a dark secret from the linux land.
if IS_SUPPORTED_SYSTEM && cfg!(not(target_os = "linux")) {
let s = System::new_with_specifics(
RefreshKind::nothing()
.with_processes(ProcessRefreshKind::nothing().with_user(UpdateKind::Always)),
);
let users = Users::new_with_refreshed_list();
// For every process where we can get a user ID, we should also be able
// to find that user ID in the global user list
for process in s.processes().values() {
if let Some(uid) = process.user_id() {
assert!(
users.get_user_by_id(uid).is_some(),
"No UID {:?} found",
uid
);
}
}
}
}
#[test]
fn ensure_is_supported_is_set_correctly() {
if MIN_USERS > 0 {
assert!(IS_SUPPORTED_SYSTEM);
} else {
assert!(!IS_SUPPORTED_SYSTEM);
}
}
}

323
vendor/sysinfo/src/macros.rs vendored Normal file
View File

@@ -0,0 +1,323 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[cfg(feature = "debug")]
#[doc(hidden)]
#[allow(unused)]
macro_rules! sysinfo_debug {
($($x:tt)*) => {{
eprintln!($($x)*);
}}
}
#[cfg(not(feature = "debug"))]
#[doc(hidden)]
#[allow(unused)]
macro_rules! sysinfo_debug {
($($x:tt)*) => {{}};
}
#[cfg(feature = "system")]
macro_rules! declare_signals {
($kind:ty, _ => None,) => (
use crate::Signal;
pub(crate) const fn supported_signals() -> &'static [Signal] {
&[]
}
);
($kind:ty, $(Signal::$signal:ident => $map:expr,)+ _ => None,) => (
use crate::Signal;
pub(crate) const fn supported_signals() -> &'static [Signal] {
&[$(Signal::$signal,)*]
}
#[inline]
pub(crate) fn convert_signal(s: Signal) -> Option<$kind> {
match s {
$(Signal::$signal => Some($map),)*
_ => None,
}
}
);
($kind:ty, $(Signal::$signal:ident => $map:expr,)+) => (
use crate::Signal;
pub(crate) const fn supported_signals() -> &'static [Signal] {
&[$(Signal::$signal,)*]
}
#[inline]
pub(crate) fn convert_signal(s: Signal) -> Option<$kind> {
match s {
$(Signal::$signal => Some($map),)*
}
}
)
}
#[cfg(all(unix, not(feature = "unknown-ci")))]
#[allow(unused_macros)]
macro_rules! retry_eintr {
(set_to_0 => $($t:tt)+) => {{
let errno = crate::unix::libc_errno();
if !errno.is_null() {
*errno = 0;
}
retry_eintr!($($t)+)
}};
($errno_value:ident => $($t:tt)+) => {{
loop {
let ret = $($t)+;
if ret < 0 {
let tmp = std::io::Error::last_os_error();
if tmp.kind() == std::io::ErrorKind::Interrupted {
continue;
}
$errno_value = tmp.raw_os_error().unwrap_or(0);
}
break ret;
}
}};
($($t:tt)+) => {{
loop {
let ret = $($t)+;
if ret < 0 && std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted {
continue;
}
break ret;
}
}};
}
//FIXME: Remove this code if https://github.com/rust-lang/cfg-if/pull/78 is ever merged.
macro_rules! cfg_if {
// match if/else chains with a final `else`
(
$(
if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* }
) else+
else { $( $e_tokens:tt )* }
) => {
cfg_if! {
@__items () ;
$(
(( $i_meta ) ( $( $i_tokens )* )) ,
)+
(() ( $( $e_tokens )* )) ,
}
};
// Allow to multiple conditions in a same call.
(
$(
if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* }
) else+
else { $( $e_tokens:tt )* }
if $($extra_conditions:tt)+
) => {
cfg_if! {
@__items () ;
$(
(( $i_meta ) ( $( $i_tokens )* )) ,
)+
(() ( $( $e_tokens )* )) ,
}
cfg_if! {
if $($extra_conditions)+
}
};
// match if/else chains lacking a final `else`
(
if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* }
$(
else if #[cfg( $e_meta:meta )] { $( $e_tokens:tt )* }
)*
) => {
cfg_if! {
@__items () ;
(( $i_meta ) ( $( $i_tokens )* )) ,
$(
(( $e_meta ) ( $( $e_tokens )* )) ,
)*
}
};
// Allow to multiple conditions in a same call.
(
if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* }
$(
else if #[cfg( $e_meta:meta )] { $( $e_tokens:tt )* }
)*
if $($extra_conditions:tt)+
) => {
cfg_if! {
@__items () ;
(( $i_meta ) ( $( $i_tokens )* )) ,
$(
(( $e_meta ) ( $( $e_tokens )* )) ,
)*
}
cfg_if! {
if $($extra_conditions)+
}
};
// Internal and recursive macro to emit all the items
//
// Collects all the previous cfgs in a list at the beginning, so they can be
// negated. After the semicolon is all the remaining items.
(@__items ( $( $_:meta , )* ) ; ) => {};
(
@__items ( $( $no:meta , )* ) ;
(( $( $yes:meta )? ) ( $( $tokens:tt )* )) ,
$( $rest:tt , )*
) => {
// Emit all items within one block, applying an appropriate #[cfg]. The
// #[cfg] will require all `$yes` matchers specified and must also negate
// all previous matchers.
#[cfg(all(
$( $yes , )?
not(any( $( $no ),* ))
))]
cfg_if! { @__identity $( $tokens )* }
// Recurse to emit all other items in `$rest`, and when we do so add all
// our `$yes` matchers to the list of `$no` matchers as future emissions
// will have to negate everything we just matched as well.
cfg_if! {
@__items ( $( $no , )* $( $yes , )? ) ;
$( $rest , )*
}
};
// Internal macro to make __apply work out right for different match types,
// because of how macros match/expand stuff.
(@__identity $( $tokens:tt )* ) => {
$( $tokens )*
};
}
#[cfg(test)]
#[allow(unexpected_cfgs)]
mod tests {
cfg_if! {
if #[cfg(test)] {
use core::option::Option as Option2;
fn works1() -> Option2<u32> { Some(1) }
} else {
fn works1() -> Option<u32> { None }
}
}
cfg_if! {
if #[cfg(foo)] {
fn works2() -> bool { false }
} else if #[cfg(test)] {
fn works2() -> bool { true }
} else {
fn works2() -> bool { false }
}
}
cfg_if! {
if #[cfg(foo)] {
fn works3() -> bool { false }
} else {
fn works3() -> bool { true }
}
}
cfg_if! {
if #[cfg(test)] {
use core::option::Option as Option3;
fn works4() -> Option3<u32> { Some(1) }
}
}
cfg_if! {
if #[cfg(foo)] {
fn works5() -> bool { false }
} else if #[cfg(test)] {
fn works5() -> bool { true }
}
}
cfg_if! {
if #[cfg(foo)] {
fn works6() -> bool { false }
} else if #[cfg(test)] {
fn works6() -> bool { true }
}
if #[cfg(test)] {
fn works7() -> bool { true }
} else {
fn works7() -> bool { false }
}
}
cfg_if! {
if #[cfg(test)] {
fn works8() -> bool { true }
} else if #[cfg(foo)] {
fn works8() -> bool { false }
}
if #[cfg(foo)] {
fn works9() -> bool { false }
} else if #[cfg(test)] {
fn works9() -> bool { true }
}
}
#[test]
fn it_works() {
assert!(works1().is_some());
assert!(works2());
assert!(works3());
assert!(works4().is_some());
assert!(works5());
assert!(works6());
assert!(works7());
assert!(works8());
assert!(works9());
}
#[test]
#[allow(clippy::assertions_on_constants)]
fn test_usage_within_a_function() {
cfg_if! {if #[cfg(debug_assertions)] {
// we want to put more than one thing here to make sure that they
// all get configured properly.
assert!(cfg!(debug_assertions));
assert_eq!(4, 2+2);
} else {
assert!(works1().is_some());
assert_eq!(10, 5+5);
}}
}
#[allow(dead_code)]
trait Trait {
fn blah(&self);
}
#[allow(dead_code)]
struct Struct;
impl Trait for Struct {
cfg_if! {
if #[cfg(feature = "blah")] {
fn blah(&self) {
unimplemented!();
}
} else {
fn blah(&self) {
unimplemented!();
}
}
}
}
}

28
vendor/sysinfo/src/network.rs vendored Normal file
View File

@@ -0,0 +1,28 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use std::collections::HashMap;
use crate::network_helper::{get_interface_address, get_interface_ip_networks};
use crate::NetworkData;
/// Interface addresses are OS-independent
pub(crate) fn refresh_networks_addresses(interfaces: &mut HashMap<String, NetworkData>) {
let interface_networks = unsafe { get_interface_ip_networks() };
for (interface_name, ip_networks) in interface_networks {
if let Some(interface) = interfaces.get_mut(&interface_name) {
interface.inner.ip_networks = ip_networks.into_iter().collect::<Vec<_>>();
}
}
match unsafe { get_interface_address() } {
Ok(ifa_iterator) => {
for (name, ifa) in ifa_iterator {
if let Some(interface) = interfaces.get_mut(&name) {
interface.inner.mac_addr = ifa;
}
}
}
Err(_e) => {
sysinfo_debug!("refresh_networks_addresses failed: {:?}", _e);
}
}
}

463
vendor/sysinfo/src/serde.rs vendored Normal file
View File

@@ -0,0 +1,463 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[cfg(any(
feature = "component",
feature = "disk",
feature = "network",
feature = "system",
feature = "user"
))]
use serde::{ser::SerializeStruct, Serialize, Serializer};
#[cfg(feature = "disk")]
impl Serialize for crate::Disk {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// `7` corresponds to the (maximum) number of fields.
let mut state = serializer.serialize_struct("Disk", 7)?;
state.serialize_field("DiskKind", &self.kind())?;
if let Some(s) = self.name().to_str() {
state.serialize_field("name", s)?;
}
state.serialize_field("file_system", &self.file_system())?;
state.serialize_field("mount_point", &self.mount_point())?;
state.serialize_field("total_space", &self.total_space())?;
state.serialize_field("available_space", &self.available_space())?;
state.serialize_field("is_removable", &self.is_removable())?;
state.end()
}
}
#[cfg(feature = "disk")]
impl Serialize for crate::Disks {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_seq(self.iter())
}
}
#[cfg(feature = "disk")]
impl Serialize for crate::DiskKind {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let (index, variant, maybe_value) = match *self {
Self::HDD => (0, "HDD", None),
Self::SSD => (1, "SSD", None),
Self::Unknown(ref s) => (2, "Unknown", Some(s)),
};
if let Some(ref value) = maybe_value {
serializer.serialize_newtype_variant("DiskKind", index, variant, value)
} else {
serializer.serialize_unit_variant("DiskKind", index, variant)
}
}
}
#[cfg(feature = "system")]
impl Serialize for crate::Pid {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_newtype_struct("Pid", &self.to_string())
}
}
#[cfg(feature = "system")]
impl Serialize for crate::Process {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// `19` corresponds to the (maximum) number of fields.
let mut state = serializer.serialize_struct("Process", 19)?;
state.serialize_field("name", &self.name())?;
state.serialize_field("cmd", &self.cmd())?;
state.serialize_field("exe", &self.exe())?;
state.serialize_field("pid", &self.pid().as_u32())?;
state.serialize_field("environ", &self.environ())?;
state.serialize_field("cwd", &self.cwd())?;
state.serialize_field("root", &self.root())?;
state.serialize_field("memory", &self.memory())?;
state.serialize_field("virtual_memory", &self.virtual_memory())?;
state.serialize_field("parent", &self.parent())?;
state.serialize_field("status", &self.status())?;
state.serialize_field("start_time", &self.start_time())?;
state.serialize_field("run_time", &self.run_time())?;
state.serialize_field("cpu_usage", &self.cpu_usage())?;
state.serialize_field("accumulated_cpu_time", &self.accumulated_cpu_time())?;
state.serialize_field("disk_usage", &self.disk_usage())?;
state.serialize_field("user_id", &self.user_id())?;
state.serialize_field("group_id", &self.group_id())?;
state.serialize_field("session_id", &self.session_id())?;
state.end()
}
}
#[cfg(feature = "system")]
impl Serialize for crate::Cpu {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// `5` corresponds to the number of fields.
let mut state = serializer.serialize_struct("Cpu", 5)?;
state.serialize_field("cpu_usage", &self.cpu_usage())?;
state.serialize_field("name", &self.name())?;
state.serialize_field("vendor_id", &self.vendor_id())?;
state.serialize_field("brand", &self.brand())?;
state.serialize_field("frequency", &self.frequency())?;
state.end()
}
}
#[cfg(feature = "system")]
impl serde::Serialize for crate::System {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
// `19` corresponds to the number of fields.
let mut state = serializer.serialize_struct("System", 19)?;
state.serialize_field("global_cpu_usage", &self.global_cpu_usage())?;
state.serialize_field("cpus", &self.cpus())?;
state.serialize_field("physical_core_count", &Self::physical_core_count())?;
state.serialize_field("total_memory", &self.total_memory())?;
state.serialize_field("free_memory", &self.free_memory())?;
state.serialize_field("available_memory", &self.available_memory())?;
state.serialize_field("used_memory", &self.used_memory())?;
state.serialize_field("total_swap", &self.total_swap())?;
state.serialize_field("free_swap", &self.free_swap())?;
state.serialize_field("used_swap", &self.used_swap())?;
state.serialize_field("uptime", &Self::uptime())?;
state.serialize_field("boot_time", &Self::boot_time())?;
state.serialize_field("load_average", &Self::load_average())?;
state.serialize_field("name", &Self::name())?;
state.serialize_field("kernel_version", &Self::kernel_version())?;
state.serialize_field("os_version", &Self::os_version())?;
state.serialize_field("long_os_version", &Self::long_os_version())?;
state.serialize_field("distribution_id", &Self::distribution_id())?;
state.serialize_field("host_name", &Self::host_name())?;
state.end()
}
}
#[cfg(feature = "system")]
impl Serialize for crate::CGroupLimits {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// `3` corresponds to the number of fields.
let mut state = serializer.serialize_struct("CGroupLimits", 3)?;
state.serialize_field("total_memory", &self.total_memory)?;
state.serialize_field("free_memory", &self.free_memory)?;
state.serialize_field("free_swap", &self.free_swap)?;
state.end()
}
}
#[cfg(feature = "system")]
impl Serialize for crate::ThreadKind {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let (index, variant) = match *self {
Self::Kernel => (0, "Kernel"),
Self::Userland => (1, "Userland"),
};
serializer.serialize_unit_variant("ThreadKind", index, variant)
}
}
#[cfg(feature = "system")]
impl Serialize for crate::Signal {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let (index, variant) = match *self {
Self::Hangup => (0, "Hangup"),
Self::Interrupt => (1, "Interrupt"),
Self::Quit => (2, "Quit"),
Self::Illegal => (3, "Illegal"),
Self::Trap => (4, "Trap"),
Self::Abort => (5, "Abort"),
Self::IOT => (6, "IOT"),
Self::Bus => (7, "Bus"),
Self::FloatingPointException => (8, "FloatingPointException"),
Self::Kill => (9, "Kill"),
Self::User1 => (10, "User1"),
Self::Segv => (11, "Segv"),
Self::User2 => (12, "User2"),
Self::Pipe => (13, "Pipe"),
Self::Alarm => (14, "Alarm"),
Self::Term => (15, "Term"),
Self::Child => (16, "Child"),
Self::Continue => (17, "Continue"),
Self::Stop => (18, "Stop"),
Self::TSTP => (19, "TSTP"),
Self::TTIN => (20, "TTIN"),
Self::TTOU => (21, "TTOU"),
Self::Urgent => (22, "Urgent"),
Self::XCPU => (23, "XCPU"),
Self::XFSZ => (24, "XFSZ"),
Self::VirtualAlarm => (25, "VirtualAlarm"),
Self::Profiling => (26, "Profiling"),
Self::Winch => (27, "Winch"),
Self::IO => (28, "IO"),
Self::Poll => (29, "Poll"),
Self::Power => (30, "Power"),
Self::Sys => (31, "Sys"),
};
serializer.serialize_unit_variant("Signal", index, variant)
}
}
#[cfg(feature = "system")]
impl Serialize for crate::LoadAvg {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// `3` corresponds to the number of fields.
let mut state = serializer.serialize_struct("LoadAvg", 3)?;
state.serialize_field("one", &self.one)?;
state.serialize_field("five", &self.five)?;
state.serialize_field("fifteen", &self.fifteen)?;
state.end()
}
}
#[cfg(feature = "system")]
impl Serialize for crate::ProcessStatus {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let (index, variant, maybe_value) = match *self {
Self::Idle => (0, "Idle", None),
Self::Run => (1, "Run", None),
Self::Sleep => (2, "Sleep", None),
Self::Stop => (3, "Stop", None),
Self::Zombie => (4, "Zombie", None),
Self::Tracing => (5, "Tracing", None),
Self::Dead => (6, "Dead", None),
Self::Wakekill => (7, "Wakekill", None),
Self::Waking => (8, "Waking", None),
Self::Parked => (9, "Parked", None),
Self::LockBlocked => (10, "LockBlocked", None),
Self::UninterruptibleDiskSleep => (11, "UninterruptibleDiskSleep", None),
Self::Unknown(n) => (12, "Unknown", Some(n)),
};
if let Some(ref value) = maybe_value {
serializer.serialize_newtype_variant("ProcessStatus", index, variant, value)
} else {
serializer.serialize_unit_variant("ProcessStatus", index, variant)
}
}
}
#[cfg(feature = "system")]
impl Serialize for crate::DiskUsage {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// `4` corresponds to the number of fields.
let mut state = serializer.serialize_struct("DiskUsage", 4)?;
state.serialize_field("total_written_bytes", &self.total_written_bytes)?;
state.serialize_field("written_bytes", &self.written_bytes)?;
state.serialize_field("total_read_bytes", &self.total_read_bytes)?;
state.serialize_field("read_bytes", &self.read_bytes)?;
state.end()
}
}
#[cfg(feature = "component")]
impl Serialize for crate::Components {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_seq(self.iter())
}
}
#[cfg(feature = "component")]
impl Serialize for crate::Component {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// `4` corresponds to the number of fields.
let mut state = serializer.serialize_struct("Component", 4)?;
state.serialize_field("temperature", &self.temperature())?;
state.serialize_field("max", &self.max())?;
state.serialize_field("critical", &self.critical())?;
state.serialize_field("label", &self.label())?;
state.end()
}
}
#[cfg(feature = "network")]
impl Serialize for crate::Networks {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_seq(self.iter())
}
}
#[cfg(feature = "network")]
impl Serialize for crate::NetworkData {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// `14` corresponds to the number of fields.
let mut state = serializer.serialize_struct("NetworkData", 14)?;
state.serialize_field("received", &self.received())?;
state.serialize_field("total_received", &self.total_received())?;
state.serialize_field("transmitted", &self.transmitted())?;
state.serialize_field("total_transmitted", &self.total_transmitted())?;
state.serialize_field("packets_received", &self.packets_received())?;
state.serialize_field("total_packets_received", &self.total_packets_received())?;
state.serialize_field("packets_transmitted", &self.packets_transmitted())?;
state.serialize_field(
"total_packets_transmitted",
&self.total_packets_transmitted(),
)?;
state.serialize_field("errors_on_received", &self.errors_on_received())?;
state.serialize_field("total_errors_on_received", &self.total_errors_on_received())?;
state.serialize_field("errors_on_transmitted", &self.errors_on_transmitted())?;
state.serialize_field(
"total_errors_on_transmitted",
&self.total_errors_on_transmitted(),
)?;
state.serialize_field("mac_address", &self.mac_address())?;
state.serialize_field("ip_networks", &self.ip_networks())?;
state.serialize_field("mtu", &self.mtu())?;
state.end()
}
}
#[cfg(feature = "network")]
impl Serialize for crate::MacAddr {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_newtype_struct("MacAddr", &self.0)
}
}
#[cfg(feature = "network")]
impl Serialize for crate::IpNetwork {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("IpNetwork", 2)?;
state.serialize_field("addr", &self.addr)?;
state.serialize_field("prefix", &self.prefix)?;
state.end()
}
}
#[cfg(feature = "user")]
impl Serialize for crate::Users {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_seq(self.iter())
}
}
#[cfg(feature = "user")]
impl Serialize for crate::User {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// `4` corresponds to the number of fields.
let mut state = serializer.serialize_struct("User", 4)?;
state.serialize_field("id", &self.id())?;
state.serialize_field("group_id", &self.group_id())?;
state.serialize_field("name", &self.name())?;
state.serialize_field("groups", &self.groups())?;
state.end()
}
}
#[cfg(feature = "user")]
impl Serialize for crate::Group {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// `2` corresponds to the number of fields.
let mut state = serializer.serialize_struct("Group", 2)?;
state.serialize_field("id", &self.id())?;
state.serialize_field("name", &self.name())?;
state.end()
}
}
#[cfg(any(feature = "user", feature = "system"))]
impl Serialize for crate::Gid {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_newtype_struct("Gid", &self.to_string())
}
}
#[cfg(any(feature = "user", feature = "system"))]
impl Serialize for crate::Uid {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_newtype_struct("Uid", &self.to_string())
}
}

74
vendor/sysinfo/src/sysinfo.h vendored Normal file
View File

@@ -0,0 +1,74 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#pragma once
#include <sys/types.h>
#include <stdbool.h>
#include <stdint.h>
typedef void* CSystem;
typedef const void* CProcess;
typedef const char* RString;
typedef void* CNetworks;
typedef void* CDisks;
#ifdef WIN32
typedef size_t PID;
#else
typedef pid_t PID;
#endif
CSystem sysinfo_init(void);
void sysinfo_destroy(CSystem system);
CNetworks sysinfo_networks_init(void);
void sysinfo_networks_destroy(CNetworks networks);
void sysinfo_refresh_memory(CSystem system);
void sysinfo_refresh_cpu(CSystem system);
void sysinfo_refresh_all(CSystem system);
void sysinfo_refresh_processes(CSystem system);
void sysinfo_refresh_process(CSystem system, PID pid);
CDisks sysinfo_disks_init(void);
void sysinfo_disks_destroy(CDisks disks);
void sysinfo_disks_refresh(CDisks disks);
size_t sysinfo_total_memory(CSystem system);
size_t sysinfo_free_memory(CSystem system);
size_t sysinfo_used_memory(CSystem system);
size_t sysinfo_total_swap(CSystem system);
size_t sysinfo_free_swap(CSystem system);
size_t sysinfo_used_swap(CSystem system);
void sysinfo_cpus_usage(CSystem system, unsigned int *length, float **cpus);
size_t sysinfo_processes(CSystem system, bool (*fn_pointer)(PID, CProcess, void*),
void *data);
size_t sysinfo_process_tasks(CProcess process, bool (*fn_pointer)(PID, void*),
void *data);
CProcess sysinfo_process_by_pid(CSystem system, PID pid);
PID sysinfo_process_pid(CProcess process);
PID sysinfo_process_parent_pid(CProcess process);
float sysinfo_process_cpu_usage(CProcess process);
size_t sysinfo_process_memory(CProcess process);
size_t sysinfo_process_virtual_memory(CProcess process);
RString sysinfo_process_executable_path(CProcess process);
RString sysinfo_process_root_directory(CProcess process);
RString sysinfo_process_current_directory(CProcess process);
void sysinfo_networks_refresh(CNetworks networks);
size_t sysinfo_networks_received(CNetworks networks);
size_t sysinfo_networks_transmitted(CNetworks networks);
RString sysinfo_cpu_vendor_id(CSystem system);
RString sysinfo_cpu_brand(CSystem system);
uint64_t sysinfo_cpu_frequency(CSystem system);
RString sysinfo_system_name();
RString sysinfo_system_kernel_version();
RString sysinfo_system_version();
RString sysinfo_system_host_name();
RString sysinfo_system_long_version();
uint32_t sysinfo_cpu_physical_cores();
void sysinfo_rstring_free(RString str);

View File

@@ -0,0 +1,59 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::Component;
pub(crate) struct ComponentInner {
pub(crate) updated: bool,
}
impl ComponentInner {
pub(crate) fn temperature(&self) -> Option<f32> {
None
}
pub(crate) fn max(&self) -> Option<f32> {
None
}
pub(crate) fn critical(&self) -> Option<f32> {
None
}
pub(crate) fn label(&self) -> &str {
""
}
pub(crate) fn refresh(&mut self) {}
}
pub(crate) struct ComponentsInner {
pub(crate) components: Vec<Component>,
}
impl ComponentsInner {
pub(crate) fn new() -> Self {
Self {
components: Vec::new(),
}
}
pub(crate) fn from_vec(components: Vec<Component>) -> Self {
Self { components }
}
pub(crate) fn into_vec(self) -> Vec<Component> {
self.components
}
pub(crate) fn list(&self) -> &[Component] {
&self.components
}
pub(crate) fn list_mut(&mut self) -> &mut [Component] {
&mut self.components
}
pub(crate) fn refresh(&mut self) {
// Doesn't do anything.
}
}

View File

@@ -0,0 +1,6 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[cfg(feature = "component")]
pub mod component;
#[cfg(feature = "system")]
pub mod process;

View File

@@ -0,0 +1,113 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use std::ffi::{OsStr, OsString};
use std::path::Path;
use std::process::ExitStatus;
use crate::{DiskUsage, Gid, Pid, ProcessStatus, Signal, Uid};
pub(crate) struct ProcessInner;
impl ProcessInner {
pub(crate) fn kill_with(&self, _signal: Signal) -> Option<bool> {
None
}
pub(crate) fn name(&self) -> &OsStr {
OsStr::new("")
}
pub(crate) fn cmd(&self) -> &[OsString] {
&[]
}
pub(crate) fn exe(&self) -> Option<&Path> {
None
}
pub(crate) fn pid(&self) -> Pid {
Pid(0)
}
pub(crate) fn environ(&self) -> &[OsString] {
&[]
}
pub(crate) fn cwd(&self) -> Option<&Path> {
None
}
pub(crate) fn root(&self) -> Option<&Path> {
None
}
pub(crate) fn memory(&self) -> u64 {
0
}
pub(crate) fn virtual_memory(&self) -> u64 {
0
}
pub(crate) fn parent(&self) -> Option<Pid> {
None
}
pub(crate) fn status(&self) -> ProcessStatus {
ProcessStatus::Unknown(0)
}
pub(crate) fn start_time(&self) -> u64 {
0
}
pub(crate) fn run_time(&self) -> u64 {
0
}
pub(crate) fn cpu_usage(&self) -> f32 {
0.0
}
pub(crate) fn accumulated_cpu_time(&self) -> u64 {
0
}
pub(crate) fn disk_usage(&self) -> DiskUsage {
DiskUsage::default()
}
pub(crate) fn user_id(&self) -> Option<&Uid> {
None
}
pub(crate) fn effective_user_id(&self) -> Option<&Uid> {
None
}
pub(crate) fn group_id(&self) -> Option<Gid> {
None
}
pub(crate) fn effective_group_id(&self) -> Option<Gid> {
None
}
pub(crate) fn wait(&self) -> Option<ExitStatus> {
None
}
pub(crate) fn session_id(&self) -> Option<Pid> {
None
}
pub(crate) fn switch_updated(&mut self) -> bool {
false
}
pub(crate) fn set_nonexistent(&mut self) {}
pub(crate) fn exists(&self) -> bool {
false
}
}

View File

@@ -0,0 +1,3 @@
// Take a look at the license at the top of the repository in the LICENSE file.
pub(crate) use crate::sys::inner::component::*;

495
vendor/sysinfo/src/unix/apple/cpu.rs vendored Normal file
View File

@@ -0,0 +1,495 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::sys::utils::{get_sys_value, get_sys_value_by_name};
use crate::{Cpu, CpuRefreshKind};
#[allow(deprecated)]
use libc::mach_task_self;
use libc::{c_char, c_void, host_processor_info, mach_port_t};
use std::mem;
use std::ops::Deref;
use std::sync::Arc;
use std::time::Instant;
pub(crate) struct CpusWrapper {
pub(crate) global_cpu: CpuUsage,
pub(crate) cpus: Vec<Cpu>,
pub(crate) got_cpu_frequency: bool,
/// This field is needed to prevent updating when not enough time passed since last update.
last_update: Option<Instant>,
}
impl CpusWrapper {
pub(crate) fn new() -> Self {
Self {
global_cpu: CpuUsage::new(),
cpus: Vec::new(),
got_cpu_frequency: false,
last_update: None,
}
}
pub(crate) fn refresh(&mut self, refresh_kind: CpuRefreshKind, port: mach_port_t) {
let need_cpu_usage_update = self
.last_update
.is_some_and(|last_update| last_update.elapsed() > crate::MINIMUM_CPU_UPDATE_INTERVAL);
let cpus = &mut self.cpus;
if cpus.is_empty() {
init_cpus(port, cpus, &mut self.global_cpu, refresh_kind);
self.last_update = Some(Instant::now());
self.got_cpu_frequency = refresh_kind.frequency();
return;
}
if refresh_kind.frequency() && !self.got_cpu_frequency {
let frequency = unsafe { get_cpu_frequency() };
for proc_ in cpus.iter_mut() {
proc_.inner.set_frequency(frequency);
}
self.got_cpu_frequency = true;
}
if refresh_kind.cpu_usage() && need_cpu_usage_update {
self.last_update = Some(Instant::now());
update_cpu_usage(port, &mut self.global_cpu, |proc_data, cpu_info| {
let mut percentage = 0f32;
let mut offset = 0;
for proc_ in cpus.iter_mut() {
let cpu_usage = compute_usage_of_cpu(proc_, cpu_info, offset);
proc_.inner.update(cpu_usage, Arc::clone(&proc_data));
percentage += proc_.inner.cpu_usage();
offset += libc::CPU_STATE_MAX as isize;
}
(percentage, cpus.len())
});
}
}
}
pub(crate) struct UnsafePtr<T>(*mut T);
unsafe impl<T> Send for UnsafePtr<T> {}
unsafe impl<T> Sync for UnsafePtr<T> {}
impl<T> Deref for UnsafePtr<T> {
type Target = *mut T;
fn deref(&self) -> &*mut T {
&self.0
}
}
pub(crate) struct CpuData {
pub cpu_info: UnsafePtr<i32>,
pub num_cpu_info: u32,
}
impl CpuData {
pub fn new(cpu_info: *mut i32, num_cpu_info: u32) -> CpuData {
CpuData {
cpu_info: UnsafePtr(cpu_info),
num_cpu_info,
}
}
}
impl Drop for CpuData {
fn drop(&mut self) {
if !self.cpu_info.0.is_null() {
let prev_cpu_info_size = std::mem::size_of::<i32>() as u32 * self.num_cpu_info;
unsafe {
libc::vm_deallocate(
#[allow(deprecated)]
mach_task_self(),
self.cpu_info.0 as _,
prev_cpu_info_size as _,
);
}
self.cpu_info.0 = std::ptr::null_mut();
}
}
}
pub(crate) struct CpuUsage {
percent: f32,
data: Arc<CpuData>,
// Cannot be frequency for each CPU apparently so we store it in the CPU usage...
frequency: u64,
}
impl CpuUsage {
pub(crate) fn new() -> Self {
Self {
percent: 0.,
data: Arc::new(CpuData::new(std::ptr::null_mut(), 0)),
frequency: 0,
}
}
pub(crate) fn percent(&self) -> f32 {
self.percent
}
pub(crate) fn set_cpu_usage(&mut self, value: f32) {
self.percent = value;
}
}
pub(crate) struct CpuInner {
name: String,
vendor_id: String,
brand: String,
usage: CpuUsage,
}
impl CpuInner {
pub(crate) fn new(
name: String,
cpu_data: Arc<CpuData>,
frequency: u64,
vendor_id: String,
brand: String,
) -> Self {
Self {
name,
usage: CpuUsage {
percent: 0.,
data: cpu_data,
frequency,
},
vendor_id,
brand,
}
}
pub(crate) fn set_cpu_usage(&mut self, cpu_usage: f32) {
self.usage.set_cpu_usage(cpu_usage);
}
pub(crate) fn update(&mut self, cpu_usage: f32, cpu_data: Arc<CpuData>) {
self.usage.percent = cpu_usage;
self.usage.data = cpu_data;
}
pub(crate) fn data(&self) -> Arc<CpuData> {
Arc::clone(&self.usage.data)
}
pub(crate) fn set_frequency(&mut self, frequency: u64) {
self.usage.frequency = frequency;
}
pub(crate) fn cpu_usage(&self) -> f32 {
self.usage.percent()
}
pub(crate) fn name(&self) -> &str {
&self.name
}
pub(crate) fn frequency(&self) -> u64 {
self.usage.frequency
}
pub(crate) fn vendor_id(&self) -> &str {
&self.vendor_id
}
pub(crate) fn brand(&self) -> &str {
&self.brand
}
}
pub(crate) unsafe fn get_cpu_frequency() -> u64 {
let mut speed: u64 = 0;
let mut len = std::mem::size_of::<u64>();
if libc::sysctlbyname(
b"hw.cpufrequency\0".as_ptr() as *const _,
&mut speed as *mut _ as _,
&mut len,
std::ptr::null_mut(),
0,
) == 0
{
return speed / 1_000_000;
}
#[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
{
0
}
#[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))]
{
crate::sys::inner::cpu::get_cpu_frequency()
}
}
pub(crate) fn physical_core_count() -> Option<usize> {
let mut physical_core_count = 0;
unsafe {
if get_sys_value_by_name(
b"hw.physicalcpu\0",
&mut mem::size_of::<u32>(),
&mut physical_core_count as *mut usize as *mut c_void,
) {
Some(physical_core_count)
} else {
None
}
}
}
#[inline]
fn get_in_use(cpu_info: *mut i32, offset: isize) -> i64 {
unsafe {
let user = *cpu_info.offset(offset + libc::CPU_STATE_USER as isize) as i64;
let system = *cpu_info.offset(offset + libc::CPU_STATE_SYSTEM as isize) as i64;
let nice = *cpu_info.offset(offset + libc::CPU_STATE_NICE as isize) as i64;
user.saturating_add(system).saturating_add(nice)
}
}
#[inline]
fn get_idle(cpu_info: *mut i32, offset: isize) -> i32 {
unsafe { *cpu_info.offset(offset + libc::CPU_STATE_IDLE as isize) }
}
pub(crate) fn compute_usage_of_cpu(proc_: &Cpu, cpu_info: *mut i32, offset: isize) -> f32 {
let old_cpu_info = proc_.inner.data().cpu_info.0;
let in_use;
let idle;
// In case we are initializing cpus, there is no "old value" yet.
if std::ptr::eq(old_cpu_info, cpu_info) {
in_use = get_in_use(cpu_info, offset);
idle = get_idle(cpu_info, offset);
} else {
let new_in_use = get_in_use(cpu_info, offset);
let old_in_use = get_in_use(old_cpu_info, offset);
let new_idle = get_idle(cpu_info, offset);
let old_idle = get_idle(old_cpu_info, offset);
in_use = new_in_use.saturating_sub(old_in_use);
idle = new_idle.saturating_sub(old_idle) as _;
}
let total = in_use.saturating_add(idle as _);
let usage = (in_use as f32 / total as f32) * 100.;
if usage.is_nan() {
// If divided by zero, avoid returning a NaN
0.
} else {
usage
}
}
pub(crate) fn update_cpu_usage<F: FnOnce(Arc<CpuData>, *mut i32) -> (f32, usize)>(
port: libc::mach_port_t,
global_cpu: &mut CpuUsage,
f: F,
) {
let mut num_cpu_u = 0u32;
let mut cpu_info: *mut i32 = std::ptr::null_mut();
let mut num_cpu_info = 0u32;
let mut total_cpu_usage = 0f32;
unsafe {
if host_processor_info(
port,
libc::PROCESSOR_CPU_LOAD_INFO,
&mut num_cpu_u as *mut u32,
&mut cpu_info as *mut *mut i32,
&mut num_cpu_info as *mut u32,
) == libc::KERN_SUCCESS
{
let (total_percentage, len) =
f(Arc::new(CpuData::new(cpu_info, num_cpu_info)), cpu_info);
total_cpu_usage = total_percentage / len as f32;
}
global_cpu.set_cpu_usage(total_cpu_usage);
}
}
pub(crate) fn init_cpus(
port: libc::mach_port_t,
cpus: &mut Vec<Cpu>,
global_cpu: &mut CpuUsage,
refresh_kind: CpuRefreshKind,
) {
let mut num_cpu = 0;
let mut mib = [libc::CTL_HW as _, libc::HW_NCPU as _];
let (vendor_id, brand) = get_vendor_id_and_brand();
let frequency = if refresh_kind.frequency() {
unsafe { get_cpu_frequency() }
} else {
global_cpu.frequency
};
unsafe {
if !get_sys_value(
mem::size_of::<u32>(),
&mut num_cpu as *mut _ as *mut _,
&mut mib,
) {
num_cpu = 1;
}
}
update_cpu_usage(port, global_cpu, |proc_data, cpu_info| {
let mut percentage = 0f32;
let mut offset = 0;
for i in 0..num_cpu {
let mut cpu = Cpu {
inner: CpuInner::new(
format!("{}", i + 1),
Arc::clone(&proc_data),
frequency,
vendor_id.clone(),
brand.clone(),
),
};
if refresh_kind.cpu_usage() {
let cpu_usage = compute_usage_of_cpu(&cpu, cpu_info, offset);
cpu.inner.set_cpu_usage(cpu_usage);
percentage += cpu.cpu_usage();
}
cpus.push(cpu);
offset += libc::CPU_STATE_MAX as isize;
}
(percentage, cpus.len())
});
}
fn get_sysctl_str(s: &[u8]) -> String {
let mut len = 0;
unsafe {
libc::sysctlbyname(
s.as_ptr() as *const c_char,
std::ptr::null_mut(),
&mut len,
std::ptr::null_mut(),
0,
);
if len < 1 {
return String::new();
}
let mut buf = Vec::with_capacity(len);
libc::sysctlbyname(
s.as_ptr() as *const c_char,
buf.as_mut_ptr() as _,
&mut len,
std::ptr::null_mut(),
0,
);
if len > 0 {
buf.set_len(len);
while buf.last() == Some(&b'\0') {
buf.pop();
}
String::from_utf8(buf).unwrap_or_else(|_| String::new())
} else {
String::new()
}
}
}
pub(crate) fn get_vendor_id_and_brand() -> (String, String) {
// On apple M1, `sysctl machdep.cpu.vendor` returns "", so fallback to "Apple" if the result
// is empty.
let mut vendor = get_sysctl_str(b"machdep.cpu.vendor\0");
if vendor.is_empty() {
vendor = "Apple".to_string();
}
let brand = get_sysctl_str(b"machdep.cpu.brand_string\0");
if !brand.is_empty() {
return (vendor, brand);
}
let full_brand = get_sysctl_str(b"hw.machine\0");
// This is a fallback when the `sysctl` to get the CPU brand returns an empty string.
let mut iter = full_brand.split(',');
let brand = match (iter.next().unwrap_or(""), iter.next()) {
("iPhone1", Some("1" | "2")) => "S5L8900",
("iPhone2", Some("1")) => "S5L8920",
("iPhone3", Some("1" | "2" | "3")) => "A4",
("iPhone4", Some("1" | "2")) => "A5",
("iPhone5", Some("1" | "2")) => "A6",
("iPhone5", Some("3" | "4")) => "A6",
("iPhone6", Some("1" | "2")) => "A7",
("iPhone7", Some("1" | "2")) => "A8",
("iPhone8", Some("1" | "2" | "4")) => "A9",
("iPhone9", Some("1" | "2" | "3" | "4")) => "A10 Fusion",
("iPhone10", Some("1" | "2" | "3" | "4" | "5" | "6")) => "A11 Bionic",
("iPhone11", Some("1" | "2" | "4" | "6" | "8")) => "A12 Bionic",
("iPhone12", Some("1" | "3" | "5" | "8")) => "A13 Bionic",
("iPhone13", Some("1" | "2" | "3" | "4")) => "A14 Bionic",
("iPhone14", Some("1" | "2" | "3" | "4" | "5" | "6" | "7" | "8")) => "A15 Bionic",
("iPhone15", Some("2" | "3" | "4" | "5")) => "A16 Bionic",
("iPhone16", Some("1" | "2")) => "A17 Pro",
("iPhone17", Some("1" | "2")) => "A18 Pro",
("iPhone17", Some("3" | "4")) => "A18",
("iPad1", Some("1" | "2")) => "A4",
("iPad2", Some("1" | "2" | "3" | "4" | "5" | "6" | "7")) => "A5",
("iPad3", Some("1" | "2" | "3")) => "A5X",
("iPad3", Some("4" | "5" | "6")) => "A6X",
("iPad4", Some("1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9")) => "A7",
("iPad5", Some("1" | "2")) => "A8",
("iPad5", Some("3" | "4")) => "A8X",
("iPad6", Some("3" | "4" | "7" | "8")) => "A9X",
("iPad6", Some("11" | "12")) => "A9",
("iPad7", Some("1" | "2" | "3" | "4")) => "A10X Fusion",
("iPad7", Some("5" | "6" | "11" | "12")) => "A10 Fusion",
("iPad8", Some("1" | "2" | "3" | "4" | "5" | "6" | "7" | "8")) => "A12X Bionic",
("iPad8", Some("9" | "10" | "11" | "12")) => "A12Z Bionic",
("iPad11", Some("1" | "2" | "3" | "4" | "6" | "7")) => "A12 Bionic",
("iPad12", Some("1" | "2")) => "A13 Bionic",
("iPad13", Some("1" | "2" | "18" | "19")) => "A14 Bionic",
("iPad13", Some("4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "16" | "17")) => "M1",
("iPad14", Some("1" | "2")) => "A15 Bionic",
("iPad14", Some("3" | "4" | "5" | "6" | "10" | "11")) => "M2",
("iPad14", Some("8" | "9")) => "M1",
("iPad16", Some("3" | "4" | "5" | "6")) => "M3",
_ => "unknown",
};
(vendor, brand.to_string())
}
#[cfg(test)]
mod test {
use crate::*;
use std::process::Command;
#[test]
fn check_vendor_and_brand() {
let child = Command::new("sysctl")
.arg("-a")
.output()
.expect("Failed to start command...");
assert!(child.status.success());
let stdout = String::from_utf8(child.stdout).expect("Not valid UTF8");
let sys = System::new_with_specifics(
crate::RefreshKind::nothing().with_cpu(CpuRefreshKind::nothing().with_cpu_usage()),
);
let cpus = sys.cpus();
assert!(!cpus.is_empty(), "no CPU found");
if let Some(line) = stdout.lines().find(|l| l.contains("machdep.cpu.vendor")) {
let sysctl_value = line.split(':').nth(1).unwrap();
assert_eq!(cpus[0].vendor_id(), sysctl_value.trim());
}
if let Some(line) = stdout
.lines()
.find(|l| l.contains("machdep.cpu.brand_string"))
{
let sysctl_value = line.split(':').nth(1).unwrap();
assert_eq!(cpus[0].brand(), sysctl_value.trim());
}
}
}

545
vendor/sysinfo/src/unix/apple/disk.rs vendored Normal file
View File

@@ -0,0 +1,545 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{sys::ffi, DiskUsage};
use crate::{Disk, DiskKind, DiskRefreshKind};
use objc2_core_foundation::{
kCFAllocatorDefault, kCFTypeArrayCallBacks, kCFURLVolumeAvailableCapacityForImportantUsageKey,
kCFURLVolumeAvailableCapacityKey, kCFURLVolumeIsBrowsableKey, kCFURLVolumeIsEjectableKey,
kCFURLVolumeIsInternalKey, kCFURLVolumeIsLocalKey, kCFURLVolumeIsRemovableKey,
kCFURLVolumeNameKey, kCFURLVolumeTotalCapacityKey, kCFURLVolumeUUIDStringKey, CFArray,
CFArrayCreate, CFBoolean, CFDictionary, CFDictionaryGetValueIfPresent, CFNumber, CFRetained,
CFString, CFURLCopyResourcePropertiesForKeys, CFURLCreateFromFileSystemRepresentation, CFURL,
};
use libc::c_void;
use std::ffi::{CStr, OsStr, OsString};
use std::os::unix::ffi::{OsStrExt, OsStringExt};
use std::path::{Path, PathBuf};
use std::ptr;
pub(crate) struct DiskInner {
pub(crate) type_: DiskKind,
pub(crate) name: OsString,
#[cfg(target_os = "macos")]
bsd_name: Option<Vec<u8>>,
pub(crate) file_system: OsString,
pub(crate) mount_point: PathBuf,
volume_url: CFRetained<CFURL>,
pub(crate) total_space: u64,
pub(crate) available_space: u64,
pub(crate) is_removable: bool,
pub(crate) is_read_only: bool,
pub(crate) old_written_bytes: u64,
pub(crate) old_read_bytes: u64,
pub(crate) written_bytes: u64,
pub(crate) read_bytes: u64,
updated: bool,
uuid: OsString,
}
impl DiskInner {
pub(crate) fn kind(&self) -> DiskKind {
self.type_
}
pub(crate) fn name(&self) -> &OsStr {
&self.name
}
pub(crate) fn file_system(&self) -> &OsStr {
&self.file_system
}
pub(crate) fn mount_point(&self) -> &Path {
&self.mount_point
}
pub(crate) fn total_space(&self) -> u64 {
self.total_space
}
pub(crate) fn available_space(&self) -> u64 {
self.available_space
}
pub(crate) fn is_removable(&self) -> bool {
self.is_removable
}
pub(crate) fn is_read_only(&self) -> bool {
self.is_read_only
}
pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) -> bool {
self.refresh_kind(refresh_kind);
self.refresh_io(refresh_kind);
if refresh_kind.storage() {
unsafe {
if let Some(requested_properties) = build_requested_properties(&[
kCFURLVolumeTotalCapacityKey,
kCFURLVolumeAvailableCapacityKey,
kCFURLVolumeAvailableCapacityForImportantUsageKey,
]) {
match get_disk_properties(&self.volume_url, &requested_properties) {
Some(disk_props) => {
match get_int_value(&disk_props, kCFURLVolumeTotalCapacityKey) {
Some(total_space) => self.total_space = total_space,
None => {
sysinfo_debug!("Failed to get disk total space");
}
}
match get_available_volume_space(&disk_props) {
Some(available_space) => self.available_space = available_space,
None => {
sysinfo_debug!("Failed to get disk available space");
}
}
}
None => {
sysinfo_debug!("Failed to get disk properties");
}
}
} else {
sysinfo_debug!("failed to create volume key list, skipping refresh");
}
}
}
true
}
pub(crate) fn usage(&self) -> DiskUsage {
DiskUsage {
read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes),
total_read_bytes: self.read_bytes,
written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
total_written_bytes: self.written_bytes,
}
}
fn refresh_kind(&mut self, refresh_kind: DiskRefreshKind) {
if refresh_kind.kind() && self.type_ == DiskKind::Unknown(-1) {
#[cfg(target_os = "macos")]
{
match self
.bsd_name
.as_ref()
.and_then(|name| crate::sys::inner::disk::get_disk_type(name))
{
Some(type_) => self.type_ = type_,
None => {
sysinfo_debug!("Failed to retrieve `DiskKind`");
}
}
}
#[cfg(not(target_os = "macos"))]
{
self.type_ = DiskKind::SSD;
}
}
}
#[cfg(target_os = "macos")]
fn refresh_io(&mut self, refresh_kind: DiskRefreshKind) {
if refresh_kind.io_usage() {
match self
.bsd_name
.as_ref()
.and_then(|name| crate::sys::inner::disk::get_disk_io(name))
{
Some((read_bytes, written_bytes)) => {
self.old_read_bytes = self.read_bytes;
self.old_written_bytes = self.written_bytes;
self.read_bytes = read_bytes;
self.written_bytes = written_bytes;
}
None => {
sysinfo_debug!("Failed to update disk i/o stats");
}
}
}
}
#[cfg(not(target_os = "macos"))]
fn refresh_io(&mut self, _refresh_kind: DiskRefreshKind) {}
}
impl crate::DisksInner {
pub(crate) fn new() -> Self {
Self {
disks: Vec::with_capacity(2),
}
}
pub(crate) fn refresh_specifics(
&mut self,
remove_not_listed_disks: bool,
refresh_kind: DiskRefreshKind,
) {
unsafe {
// SAFETY: We don't keep any Objective-C objects around because we
// don't make any direct Objective-C calls in this code.
with_autorelease(|| {
get_list(&mut self.disks, refresh_kind);
})
}
if remove_not_listed_disks {
self.disks.retain_mut(|disk| {
if !disk.inner.updated {
return false;
}
disk.inner.updated = false;
true
});
} else {
for c in self.disks.iter_mut() {
c.inner.updated = false;
}
}
}
pub(crate) fn list(&self) -> &[Disk] {
&self.disks
}
pub(crate) fn list_mut(&mut self) -> &mut [Disk] {
&mut self.disks
}
}
unsafe fn get_list(container: &mut Vec<Disk>, refresh_kind: DiskRefreshKind) {
let raw_disks = {
let count = libc::getfsstat(ptr::null_mut(), 0, libc::MNT_NOWAIT);
if count < 1 {
return;
}
let bufsize = count * std::mem::size_of::<libc::statfs>() as libc::c_int;
let mut disks = Vec::with_capacity(count as _);
let count = libc::getfsstat(disks.as_mut_ptr(), bufsize, libc::MNT_NOWAIT);
if count < 1 {
return;
}
disks.set_len(count as usize);
disks
};
// Currently we query maximum 10 properties.
let mut properties = Vec::with_capacity(10);
// "mandatory" information
properties.push(kCFURLVolumeNameKey);
properties.push(kCFURLVolumeIsBrowsableKey);
properties.push(kCFURLVolumeIsLocalKey);
properties.push(kCFURLVolumeUUIDStringKey);
// is_removable
properties.push(kCFURLVolumeIsEjectableKey);
properties.push(kCFURLVolumeIsRemovableKey);
properties.push(kCFURLVolumeIsInternalKey);
if refresh_kind.storage() {
properties.push(kCFURLVolumeTotalCapacityKey);
properties.push(kCFURLVolumeAvailableCapacityForImportantUsageKey);
properties.push(kCFURLVolumeAvailableCapacityKey);
}
// Create a list of properties about the disk that we want to fetch.
let requested_properties = match build_requested_properties(&properties) {
Some(properties) => properties,
None => {
sysinfo_debug!("failed to create volume key list");
return;
}
};
for c_disk in raw_disks {
let volume_url = match CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
c_disk.f_mntonname.as_ptr() as *const _,
c_disk.f_mntonname.len() as _,
false as _,
) {
Some(url) => url,
None => {
sysinfo_debug!("getfsstat returned incompatible paths");
continue;
}
};
let prop_dict = match get_disk_properties(&volume_url, &requested_properties) {
Some(props) => props,
None => continue,
};
// Future note: There is a difference between `kCFURLVolumeIsBrowsableKey` and the
// `kCFURLEnumeratorSkipInvisibles` option of `CFURLEnumeratorOptions`. Specifically,
// the first one considers the writable `Data`(`/System/Volumes/Data`) partition to be
// browsable, while it is classified as "invisible" by CoreFoundation's volume emumerator.
let browsable = get_bool_value(&prop_dict, kCFURLVolumeIsBrowsableKey).unwrap_or_default();
// Do not return invisible "disks". Most of the time, these are APFS snapshots, hidden
// system volumes, etc. Browsable is defined to be visible in the system's UI like Finder,
// disk utility, system information, etc.
//
// To avoid seemingly duplicating many disks and creating an inaccurate view of the system's
// resources, these are skipped entirely.
if !browsable {
continue;
}
let local_only = get_bool_value(&prop_dict, kCFURLVolumeIsLocalKey).unwrap_or(true);
// Skip any drive that is not locally attached to the system.
//
// This includes items like SMB mounts, and matches the other platform's behavior.
if !local_only {
continue;
}
let mount_point = PathBuf::from(OsStr::from_bytes(
CStr::from_ptr(c_disk.f_mntonname.as_ptr()).to_bytes(),
));
let uuid = OsStr::from_bytes(CStr::from_ptr(c_disk.f_mntonname.as_ptr()).to_bytes())
.to_os_string();
let disk = container.iter_mut().find(|d| d.inner.uuid == uuid);
if let Some(disk) = new_disk(
disk,
mount_point,
volume_url,
c_disk,
&prop_dict,
refresh_kind,
uuid,
) {
container.push(disk);
}
}
}
unsafe fn build_requested_properties(
properties: &[Option<&CFString>],
) -> Option<CFRetained<CFArray>> {
CFArrayCreate(
None,
properties.as_ptr() as *mut *const c_void,
properties.len() as _,
&kCFTypeArrayCallBacks,
)
}
fn get_disk_properties(
volume_url: &CFURL,
requested_properties: &CFArray,
) -> Option<CFRetained<CFDictionary>> {
unsafe {
CFURLCopyResourcePropertiesForKeys(volume_url, Some(requested_properties), ptr::null_mut())
}
}
fn get_available_volume_space(disk_props: &CFDictionary) -> Option<u64> {
// We prefer `AvailableCapacityForImportantUsage` over `AvailableCapacity` because
// it takes more of the system's properties into account, like the trash, system-managed caches,
// etc. It generally also returns higher values too, because of the above, so it's a more
// accurate representation of what the system _could_ still use.
unsafe {
get_int_value(
disk_props,
kCFURLVolumeAvailableCapacityForImportantUsageKey,
)
.filter(|bytes| *bytes != 0)
.or_else(|| get_int_value(disk_props, kCFURLVolumeAvailableCapacityKey))
}
}
unsafe fn get_dict_value<T, F: FnOnce(*const c_void) -> Option<T>>(
dict: &CFDictionary,
key: Option<&CFString>,
callback: F,
) -> Option<T> {
let mut value = std::ptr::null();
let key: *const CFString = key.map(|key| key as *const CFString).unwrap_or(ptr::null());
if CFDictionaryGetValueIfPresent(dict, key.cast(), &mut value) {
callback(value)
} else {
None
}
}
pub(super) unsafe fn get_str_value(dict: &CFDictionary, key: Option<&CFString>) -> Option<String> {
get_dict_value(dict, key, |v| {
let v = unsafe { &*v.cast::<CFString>() };
Some(v.to_string())
})
}
unsafe fn get_bool_value(dict: &CFDictionary, key: Option<&CFString>) -> Option<bool> {
get_dict_value(dict, key, |v| {
let v = unsafe { &*v.cast::<CFBoolean>() };
Some(v.as_bool())
})
}
pub(super) unsafe fn get_int_value(dict: &CFDictionary, key: Option<&CFString>) -> Option<u64> {
get_dict_value(dict, key, |v| {
let v = unsafe { &*v.cast::<CFNumber>() };
Some(v.as_i64()? as u64)
})
}
unsafe fn new_disk(
disk: Option<&mut Disk>,
mount_point: PathBuf,
volume_url: CFRetained<CFURL>,
c_disk: libc::statfs,
disk_props: &CFDictionary,
refresh_kind: DiskRefreshKind,
uuid: OsString,
) -> Option<Disk> {
let (total_space, available_space) = if refresh_kind.storage() {
(
get_int_value(disk_props, kCFURLVolumeTotalCapacityKey),
get_available_volume_space(disk_props),
)
} else {
(None, None)
};
// We update the existing disk here to prevent having another call to get `storage` info.
if let Some(disk) = disk {
let disk = &mut disk.inner;
if let Some(total_space) = total_space {
disk.total_space = total_space;
}
if let Some(available_space) = available_space {
disk.available_space = available_space;
}
disk.refresh_io(refresh_kind);
disk.refresh_kind(refresh_kind);
disk.updated = true;
return None;
}
// Note: Since we requested these properties from the system, we don't expect
// these property retrievals to fail.
let name = get_str_value(disk_props, kCFURLVolumeNameKey).map(OsString::from)?;
let file_system = {
let len = c_disk
.f_fstypename
.iter()
.position(|&b| b == 0)
.unwrap_or(c_disk.f_fstypename.len());
OsString::from_vec(
c_disk.f_fstypename[..len]
.iter()
.map(|c| *c as u8)
.collect(),
)
};
#[cfg(target_os = "macos")]
let bsd_name = get_bsd_name(&c_disk);
// IOKit is not available on any but the most recent (16+) iOS and iPadOS versions.
// Due to this, we can't query the medium type and disk i/o stats. All iOS devices use flash-based storage
// so we just assume the disk type is an SSD and set disk i/o stats to 0 until Rust has a way to conditionally link to
// IOKit in more recent deployment versions.
let ejectable = get_bool_value(disk_props, kCFURLVolumeIsEjectableKey).unwrap_or(false);
let removable = get_bool_value(disk_props, kCFURLVolumeIsRemovableKey).unwrap_or(false);
let is_removable = if ejectable || removable {
true
} else {
// If neither `ejectable` or `removable` return `true`, fallback to checking
// if the disk is attached to the internal system.
let internal = get_bool_value(disk_props, kCFURLVolumeIsInternalKey).unwrap_or_default();
!internal
};
let is_read_only = (c_disk.f_flags & libc::MNT_RDONLY as u32) != 0;
let mut disk = DiskInner {
type_: DiskKind::Unknown(-1),
name,
#[cfg(target_os = "macos")]
bsd_name,
file_system,
mount_point,
volume_url,
total_space: total_space.unwrap_or(0),
available_space: available_space.unwrap_or(0),
is_removable,
is_read_only,
read_bytes: 0,
written_bytes: 0,
old_read_bytes: 0,
old_written_bytes: 0,
updated: true,
uuid,
};
disk.refresh_kind(refresh_kind);
disk.refresh_io(refresh_kind);
Some(Disk { inner: disk })
}
/// Calls the provided closure in the context of a new autorelease pool that is drained
/// before returning.
///
/// ## SAFETY:
/// You must not return an Objective-C object that is autoreleased from this function since it
/// will be freed before usable.
unsafe fn with_autorelease<T, F: FnOnce() -> T>(call: F) -> T {
// NB: This struct exists to help prevent memory leaking if `call` were to panic.
// Otherwise, the call to `objc_autoreleasePoolPop` would never be made as the stack unwinds.
// `Drop` destructors for existing types on the stack are run during unwinding, so we can
// ensure the autorelease pool is drained by using a RAII pattern here.
struct DrainPool {
ctx: *mut c_void,
}
impl Drop for DrainPool {
fn drop(&mut self) {
// SAFETY: We have not manipulated `pool_ctx` since it was received from a corresponding
// pool push call.
unsafe { ffi::objc_autoreleasePoolPop(self.ctx) }
}
}
// SAFETY: Creating a new pool is safe in any context. They can be arbitrarily nested
// as long as pool objects are not used in deeper layers, but we only have one and don't
// allow it to leave this scope.
let _pool_ctx = DrainPool {
ctx: unsafe { ffi::objc_autoreleasePoolPush() },
};
call()
// Pool is drained here before returning
}
#[cfg(target_os = "macos")]
fn get_bsd_name(disk: &libc::statfs) -> Option<Vec<u8>> {
// Removes `/dev/` from the value.
unsafe {
CStr::from_ptr(disk.f_mntfromname.as_ptr())
.to_bytes_with_nul()
.strip_prefix(b"/dev/")
.map(|slice| slice.to_vec())
.or_else(|| {
sysinfo_debug!("unknown disk mount path format");
None
})
}
}

33
vendor/sysinfo/src/unix/apple/ffi.rs vendored Normal file
View File

@@ -0,0 +1,33 @@
// Take a look at the license at the top of the repository in the LICENSE file.
// Reexport items defined in either macos or ios ffi module.
#[cfg(all(
not(target_os = "ios"),
any(
feature = "disk",
all(
not(feature = "apple-sandbox"),
any(feature = "component", feature = "system")
),
),
))]
pub use crate::sys::inner::ffi::*;
#[cfg(feature = "disk")]
#[link(name = "objc", kind = "dylib")]
extern "C" {
pub fn objc_autoreleasePoolPop(pool: *mut libc::c_void);
pub fn objc_autoreleasePoolPush() -> *mut libc::c_void;
}
#[cfg_attr(feature = "debug", derive(Eq, Hash, PartialEq))]
#[allow(unused)]
#[allow(non_camel_case_types)]
#[derive(Clone)]
#[repr(C)]
pub struct Val_t {
pub key: [i8; 5],
pub data_size: u32,
pub data_type: [i8; 5], // UInt32Char_t
pub bytes: [i8; 32], // SMCBytes_t
}

41
vendor/sysinfo/src/unix/apple/groups.rs vendored Normal file
View File

@@ -0,0 +1,41 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{Gid, Group, GroupInner};
use libc::{endgrent, getgrent, setgrent};
use std::collections::HashMap;
pub(crate) fn get_groups(groups: &mut Vec<Group>) {
groups.clear();
let mut groups_map = HashMap::with_capacity(10);
unsafe {
setgrent();
loop {
let gr = getgrent();
if gr.is_null() {
// The call was interrupted by a signal, retrying.
if std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted {
continue;
}
break;
}
if let Some(name) = crate::unix::utils::cstr_to_rust((*gr).gr_name) {
if groups_map.contains_key(&name) {
continue;
}
let gid = (*gr).gr_gid;
groups_map.insert(name, Gid(gid));
}
}
endgrent();
}
for (name, gid) in groups_map {
groups.push(Group {
inner: GroupInner::new(gid, name),
});
}
}

7
vendor/sysinfo/src/unix/apple/ios.rs vendored Normal file
View File

@@ -0,0 +1,7 @@
// Take a look at the license at the top of the repository in the LICENSE file.
pub mod ffi {}
#[cfg(feature = "component")]
pub use crate::sys::app_store::component;
#[cfg(feature = "system")]
pub use crate::sys::app_store::process;

View File

@@ -0,0 +1,180 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use std::ptr::NonNull;
use objc2_core_foundation::{
kCFAllocatorDefault, CFArrayGetCount, CFArrayGetValueAtIndex, CFRetained, CFString,
};
use crate::sys::inner::ffi::{
kHIDPage_AppleVendor, kHIDUsage_AppleVendor_TemperatureSensor, kIOHIDEventTypeTemperature,
matching, IOHIDEventFieldBase, IOHIDEventGetFloatValue, IOHIDEventSystemClientCopyServices,
IOHIDEventSystemClientCreate, IOHIDEventSystemClientSetMatching, IOHIDServiceClientCopyEvent,
IOHIDServiceClientCopyProperty, HID_DEVICE_PROPERTY_PRODUCT,
};
use crate::unix::apple::ffi::{IOHIDEventSystemClient, IOHIDServiceClient};
use crate::Component;
pub(crate) struct ComponentsInner {
pub(crate) components: Vec<Component>,
client: Option<CFRetained<IOHIDEventSystemClient>>,
}
impl ComponentsInner {
pub(crate) fn new() -> Self {
Self {
components: vec![],
client: None,
}
}
pub(crate) fn from_vec(components: Vec<Component>) -> Self {
Self {
components,
client: None,
}
}
pub(crate) fn into_vec(self) -> Vec<Component> {
self.components
}
pub(crate) fn list(&self) -> &[Component] {
&self.components
}
pub(crate) fn list_mut(&mut self) -> &mut [Component] {
&mut self.components
}
#[allow(unreachable_code)]
pub(crate) fn refresh(&mut self) {
unsafe {
let matches = match matching(
kHIDPage_AppleVendor,
kHIDUsage_AppleVendor_TemperatureSensor,
) {
Some(m) => m,
None => return,
};
if self.client.is_none() {
let client = match IOHIDEventSystemClientCreate(kCFAllocatorDefault) {
Some(c) => CFRetained::from_raw(c),
None => return,
};
self.client = Some(client);
}
let client = self.client.as_ref().unwrap();
let _ = IOHIDEventSystemClientSetMatching(client, &matches);
let services = match IOHIDEventSystemClientCopyServices(client) {
Some(s) => CFRetained::from_raw(s),
None => return,
};
let key = CFString::from_static_str(HID_DEVICE_PROPERTY_PRODUCT);
let count = CFArrayGetCount(&services);
for i in 0..count {
let service = CFArrayGetValueAtIndex(&services, i).cast::<IOHIDServiceClient>();
if service.is_null() {
continue;
}
// The 'service' should never be freed since it is returned by a 'Get' call.
// See issue https://github.com/GuillaumeGomez/sysinfo/issues/1279
let service = CFRetained::retain(NonNull::from(&*service));
let Some(name) = IOHIDServiceClientCopyProperty(&service, &key) else {
continue;
};
let name = CFRetained::from_raw(name);
let name_str = name.to_string();
if let Some(c) = self
.components
.iter_mut()
.find(|c| c.inner.label == name_str)
{
c.refresh();
c.inner.updated = true;
continue;
}
let mut component = ComponentInner::new(name_str, None, None, service);
component.refresh();
self.components.push(Component { inner: component });
}
}
}
}
pub(crate) struct ComponentInner {
service: CFRetained<IOHIDServiceClient>,
temperature: Option<f32>,
label: String,
max: f32,
critical: Option<f32>,
pub(crate) updated: bool,
}
unsafe impl Send for ComponentInner {}
unsafe impl Sync for ComponentInner {}
impl ComponentInner {
pub(crate) fn new(
label: String,
max: Option<f32>,
critical: Option<f32>,
service: CFRetained<IOHIDServiceClient>,
) -> Self {
Self {
service,
label,
max: max.unwrap_or(0.),
critical,
temperature: None,
updated: true,
}
}
pub(crate) fn temperature(&self) -> Option<f32> {
self.temperature
}
pub(crate) fn max(&self) -> Option<f32> {
Some(self.max)
}
pub(crate) fn critical(&self) -> Option<f32> {
self.critical
}
pub(crate) fn label(&self) -> &str {
&self.label
}
pub(crate) fn refresh(&mut self) {
unsafe {
let Some(event) =
IOHIDServiceClientCopyEvent(&self.service, kIOHIDEventTypeTemperature, 0, 0)
else {
self.temperature = None;
return;
};
let event = CFRetained::from_raw(event);
let temperature =
IOHIDEventGetFloatValue(&event, IOHIDEventFieldBase(kIOHIDEventTypeTemperature))
as _;
self.temperature = Some(temperature);
if temperature > self.max {
self.max = temperature;
}
}
}
}

View File

@@ -0,0 +1,13 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub(crate) mod x86;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub(crate) use self::x86::*;
#[cfg(target_arch = "aarch64")]
pub(crate) mod arm;
#[cfg(target_arch = "aarch64")]
pub(crate) use self::arm::*;

View File

@@ -0,0 +1,359 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::sys::{ffi, macos::utils::IOReleaser};
use crate::Component;
use libc::{c_char, c_int, c_void};
use std::mem;
const COMPONENTS_TEMPERATURE_IDS: &[(&str, &[i8])] = &[
("PECI CPU", &['T' as i8, 'C' as i8, 'X' as i8, 'C' as i8]), // PECI CPU "TCXC"
("PECI CPU", &['T' as i8, 'C' as i8, 'X' as i8, 'c' as i8]), // PECI CPU "TCXc"
(
"CPU Proximity",
&['T' as i8, 'C' as i8, '0' as i8, 'P' as i8],
), // CPU Proximity (heat spreader) "TC0P"
("GPU", &['T' as i8, 'G' as i8, '0' as i8, 'P' as i8]), // GPU "TG0P"
("Battery", &['T' as i8, 'B' as i8, '0' as i8, 'T' as i8]), // Battery "TB0T"
];
pub(crate) struct ComponentFFI {
input_structure: ffi::KeyData_t,
val: ffi::Val_t,
/// It is the `System::connection`. We need it to not require an extra argument
/// in `ComponentInner::refresh`.
connection: ffi::io_connect_t,
}
impl ComponentFFI {
fn new(key: &[i8], connection: ffi::io_connect_t) -> Option<ComponentFFI> {
unsafe {
get_key_size(connection, key)
.ok()
.map(|(input_structure, val)| ComponentFFI {
input_structure,
val,
connection,
})
}
}
fn temperature(&self) -> Option<f32> {
get_temperature_inner(self.connection, &self.input_structure, &self.val)
}
}
// Used to get CPU information, not supported on iOS, or inside the default macOS sandbox.
pub(crate) struct ComponentsInner {
pub(crate) components: Vec<Component>,
connection: Option<IoService>,
}
impl ComponentsInner {
pub(crate) fn new() -> Self {
Self {
components: Vec::with_capacity(2),
connection: IoService::new_connection(),
}
}
pub(crate) fn from_vec(components: Vec<Component>) -> Self {
Self {
components,
connection: IoService::new_connection(),
}
}
pub(crate) fn into_vec(self) -> Vec<Component> {
self.components
}
pub(crate) fn list(&self) -> &[Component] {
&self.components
}
pub(crate) fn list_mut(&mut self) -> &mut [Component] {
&mut self.components
}
pub(crate) fn refresh(&mut self) {
let Some(ref connection) = self.connection else {
sysinfo_debug!("No connection to IoService, skipping components refresh");
return;
};
let connection = connection.inner();
// getting CPU critical temperature
let critical_temp =
get_temperature(connection, &['T' as i8, 'C' as i8, '0' as i8, 'D' as i8, 0]);
for (id, v) in COMPONENTS_TEMPERATURE_IDS.iter() {
if let Some(c) = self.components.iter_mut().find(|c| c.inner.label == *id) {
c.refresh();
c.inner.updated = true;
} else if let Some(c) =
ComponentInner::new((*id).to_owned(), None, critical_temp, v, connection)
{
self.components.push(Component { inner: c });
}
}
}
}
pub(crate) struct ComponentInner {
temperature: Option<f32>,
max: f32,
critical: Option<f32>,
label: String,
ffi_part: ComponentFFI,
pub(crate) updated: bool,
}
impl ComponentInner {
/// Creates a new `ComponentInner` with the given information.
pub(crate) fn new(
label: String,
max: Option<f32>,
critical: Option<f32>,
key: &[i8],
connection: ffi::io_connect_t,
) -> Option<Self> {
let ffi_part = ComponentFFI::new(key, connection)?;
ffi_part.temperature().map(|temperature| Self {
temperature: Some(temperature),
label,
max: max.unwrap_or(temperature),
critical,
ffi_part,
updated: true,
})
}
pub(crate) fn temperature(&self) -> Option<f32> {
self.temperature
}
pub(crate) fn max(&self) -> Option<f32> {
Some(self.max)
}
pub(crate) fn critical(&self) -> Option<f32> {
self.critical
}
pub(crate) fn label(&self) -> &str {
&self.label
}
pub(crate) fn refresh(&mut self) {
self.temperature = self.ffi_part.temperature();
if let Some(temperature) = self.temperature {
if temperature > self.max {
self.max = temperature;
}
}
}
}
unsafe fn perform_call(
conn: ffi::io_connect_t,
index: c_int,
input_structure: *const ffi::KeyData_t,
output_structure: *mut ffi::KeyData_t,
) -> i32 {
let mut structure_output_size = mem::size_of::<ffi::KeyData_t>();
ffi::IOConnectCallStructMethod(
conn,
index as u32,
input_structure,
mem::size_of::<ffi::KeyData_t>(),
output_structure,
&mut structure_output_size,
)
}
// Adapted from https://github.com/lavoiesl/osx-cpu-temp/blob/master/smc.c#L28
#[inline]
fn strtoul(s: &[i8]) -> u32 {
unsafe {
((*s.get_unchecked(0) as u32) << (3u32 << 3))
+ ((*s.get_unchecked(1) as u32) << (2u32 << 3))
+ ((*s.get_unchecked(2) as u32) << (1u32 << 3))
+ (*s.get_unchecked(3) as u32)
}
}
#[inline]
unsafe fn ultostr(s: *mut c_char, val: u32) {
*s.offset(0) = ((val >> 24) % 128) as i8;
*s.offset(1) = ((val >> 16) % 128) as i8;
*s.offset(2) = ((val >> 8) % 128) as i8;
*s.offset(3) = (val % 128) as i8;
*s.offset(4) = 0;
}
unsafe fn get_key_size(
con: ffi::io_connect_t,
key: &[i8],
) -> Result<(ffi::KeyData_t, ffi::Val_t), i32> {
let mut input_structure: ffi::KeyData_t = mem::zeroed::<ffi::KeyData_t>();
let mut output_structure: ffi::KeyData_t = mem::zeroed::<ffi::KeyData_t>();
let mut val: ffi::Val_t = mem::zeroed::<ffi::Val_t>();
input_structure.key = strtoul(key);
input_structure.data8 = ffi::SMC_CMD_READ_KEYINFO;
let result = perform_call(
con,
ffi::KERNEL_INDEX_SMC,
&input_structure,
&mut output_structure,
);
if result != ffi::KIO_RETURN_SUCCESS {
return Err(result);
}
val.data_size = output_structure.key_info.data_size;
ultostr(
val.data_type.as_mut_ptr(),
output_structure.key_info.data_type,
);
input_structure.key_info.data_size = val.data_size;
input_structure.data8 = ffi::SMC_CMD_READ_BYTES;
Ok((input_structure, val))
}
unsafe fn read_key(
con: ffi::io_connect_t,
input_structure: &ffi::KeyData_t,
mut val: ffi::Val_t,
) -> Result<ffi::Val_t, i32> {
let mut output_structure: ffi::KeyData_t = mem::zeroed::<ffi::KeyData_t>();
match perform_call(
con,
ffi::KERNEL_INDEX_SMC,
input_structure,
&mut output_structure,
) {
ffi::KIO_RETURN_SUCCESS => {
libc::memcpy(
val.bytes.as_mut_ptr() as *mut c_void,
output_structure.bytes.as_mut_ptr() as *mut c_void,
mem::size_of::<[u8; 32]>(),
);
Ok(val)
}
result => Err(result),
}
}
fn get_temperature_inner(
con: ffi::io_connect_t,
input_structure: &ffi::KeyData_t,
original_val: &ffi::Val_t,
) -> Option<f32> {
unsafe {
if let Ok(val) = read_key(con, input_structure, (*original_val).clone()) {
if val.data_size > 0
&& libc::strcmp(val.data_type.as_ptr(), b"sp78\0".as_ptr() as *const i8) == 0
{
// convert sp78 value to temperature
let x = (i32::from(val.bytes[0]) << 6) + (i32::from(val.bytes[1]) >> 2);
return Some(x as f32 / 64f32);
}
}
}
None
}
fn get_temperature(con: ffi::io_connect_t, key: &[i8]) -> Option<f32> {
unsafe {
let (input_structure, val) = get_key_size(con, key).ok()?;
get_temperature_inner(con, &input_structure, &val)
}
}
pub(crate) struct IoService(ffi::io_connect_t);
impl IoService {
fn new(obj: ffi::io_connect_t) -> Option<Self> {
if obj == 0 {
None
} else {
Some(Self(obj))
}
}
pub(crate) fn inner(&self) -> ffi::io_connect_t {
self.0
}
// code from https://github.com/Chris911/iStats
// Not supported on iOS, or in the default macOS
pub(crate) fn new_connection() -> Option<Self> {
let mut iterator: ffi::io_iterator_t = 0;
unsafe {
let Some(matching_dictionary) =
ffi::IOServiceMatching(b"AppleSMC\0".as_ptr() as *const i8)
else {
sysinfo_debug!("IOServiceMatching call failed, `AppleSMC` not found");
return None;
};
let result = ffi::IOServiceGetMatchingServices(
ffi::kIOMasterPortDefault,
matching_dictionary,
&mut iterator,
);
if result != ffi::KIO_RETURN_SUCCESS {
sysinfo_debug!("Error: IOServiceGetMatchingServices() = {}", result);
return None;
}
let iterator = match IOReleaser::new(iterator) {
Some(i) => i,
None => {
sysinfo_debug!("Error: IOServiceGetMatchingServices() succeeded but returned invalid descriptor");
return None;
}
};
let device = match IOReleaser::new(ffi::IOIteratorNext(iterator.inner())) {
Some(d) => d,
None => {
sysinfo_debug!("Error: no SMC found");
return None;
}
};
let mut conn = 0;
let result = ffi::IOServiceOpen(
device.inner(),
#[allow(deprecated)]
libc::mach_task_self(),
0,
&mut conn,
);
if result != ffi::KIO_RETURN_SUCCESS {
sysinfo_debug!("Error: IOServiceOpen() = {}", result);
return None;
}
let conn = IoService::new(conn);
if conn.is_none() {
sysinfo_debug!(
"Error: IOServiceOpen() succeeded but returned invalid descriptor..."
);
}
conn
}
}
}
impl Drop for IoService {
fn drop(&mut self) {
unsafe {
ffi::IOServiceClose(self.0);
}
}
}

View File

@@ -0,0 +1,94 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[cfg(feature = "apple-sandbox")]
pub(crate) unsafe fn get_cpu_frequency() -> u64 {
0
}
#[cfg(not(feature = "apple-sandbox"))]
pub(crate) unsafe fn get_cpu_frequency() -> u64 {
use crate::sys::ffi;
use crate::sys::macos::utils::IOReleaser;
use objc2_core_foundation::{
kCFAllocatorDefault, CFData, CFDataGetBytes, CFDataGetLength, CFRange, CFRetained, CFString,
};
let Some(matching) = ffi::IOServiceMatching(b"AppleARMIODevice\0".as_ptr() as *const _) else {
sysinfo_debug!("IOServiceMatching call failed, `AppleARMIODevice` not found");
return 0;
};
// Starting from mac M1, the above call returns nothing for the CPU frequency
// so we try to get it from another source. This code comes from
// <https://github.com/giampaolo/psutil/pull/2222>.
let mut iterator: ffi::io_iterator_t = 0;
let result =
ffi::IOServiceGetMatchingServices(ffi::kIOMasterPortDefault, matching, &mut iterator);
if result != ffi::KIO_RETURN_SUCCESS {
sysinfo_debug!("Error: IOServiceGetMatchingServices() = {}", result);
return 0;
}
let iterator = match IOReleaser::new(iterator) {
Some(i) => i,
None => {
sysinfo_debug!(
"Error: IOServiceGetMatchingServices() succeeded but returned invalid descriptor"
);
return 0;
}
};
let mut name: ffi::io_name = std::mem::zeroed();
let entry = loop {
let entry = match IOReleaser::new(ffi::IOIteratorNext(iterator.inner())) {
Some(d) => d,
None => {
sysinfo_debug!("`pmgr` entry was not found in AppleARMIODevice service");
return 0;
}
};
let status = ffi::IORegistryEntryGetName(entry.inner(), name.as_mut_ptr());
if status != libc::KERN_SUCCESS {
continue;
} else if libc::strcmp(name.as_ptr(), b"pmgr\0".as_ptr() as *const _) == 0 {
break entry;
}
};
let node_name = CFString::from_static_str("voltage-states5-sram");
let core_ref = match ffi::IORegistryEntryCreateCFProperty(
entry.inner(),
&node_name,
kCFAllocatorDefault,
0,
) {
Some(c) => c,
None => {
sysinfo_debug!("`voltage-states5-sram` property not found");
return 0;
}
};
let core_ref = CFRetained::from_raw(core_ref);
let Ok(core_ref) = core_ref.downcast::<CFData>() else {
sysinfo_debug!("`voltage-states5-sram` property was not CFData");
return 0;
};
let core_length = CFDataGetLength(&core_ref);
if core_length < 8 {
sysinfo_debug!("expected `voltage-states5-sram` buffer to have at least size 8");
return 0;
}
let mut max: u64 = 0;
CFDataGetBytes(
&core_ref,
CFRange {
location: core_length - 8,
length: 4,
},
&mut max as *mut _ as *mut _,
);
max / 1_000_000
}

View File

@@ -0,0 +1,136 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::sys::ffi;
use crate::sys::{
disk::{get_int_value, get_str_value},
macos::utils::IOReleaser,
};
use crate::DiskKind;
use objc2_core_foundation::{kCFAllocatorDefault, CFDictionary, CFRetained, CFString};
fn iterate_service_tree<T, F>(bsd_name: &[u8], key: &CFString, eval: F) -> Option<T>
where
F: Fn(ffi::io_registry_entry_t, &CFDictionary) -> Option<T>,
{
// We don't need to wrap this in CFRetained because the following call to
// `IOServiceGetMatchingServices` will take ownership of one retain reference.
let matching =
unsafe { ffi::IOBSDNameMatching(ffi::kIOMasterPortDefault, 0, bsd_name.as_ptr().cast()) }?;
let mut service_iterator: ffi::io_iterator_t = 0;
if unsafe {
ffi::IOServiceGetMatchingServices(
ffi::kIOMasterPortDefault,
matching,
&mut service_iterator,
)
} != libc::KERN_SUCCESS
{
return None;
}
// Safety: We checked for success, so there is always a valid iterator, even if its empty.
let service_iterator = unsafe { IOReleaser::new_unchecked(service_iterator) };
let mut parent_entry: ffi::io_registry_entry_t = 0;
while let Some(mut current_service_entry) =
IOReleaser::new(unsafe { ffi::IOIteratorNext(service_iterator.inner()) })
{
// Note: This loop is required in a non-obvious way. Due to device properties existing as a tree
// in IOKit, we may need an arbitrary number of calls to `IORegistryEntryCreateCFProperty` in order to find
// the values we are looking for. The function may return nothing if we aren't deep enough into the registry
// tree, so we need to continue going from child->parent node until its found.
loop {
if unsafe {
ffi::IORegistryEntryGetParentEntry(
current_service_entry.inner(),
ffi::kIOServicePlane.as_ptr().cast(),
&mut parent_entry,
)
} != libc::KERN_SUCCESS
{
break;
}
current_service_entry = match IOReleaser::new(parent_entry) {
Some(service) => service,
// There were no more parents left
None => break,
};
let properties_result = unsafe {
ffi::IORegistryEntryCreateCFProperty(
current_service_entry.inner(),
key,
kCFAllocatorDefault,
0,
)
};
if let Some(properties) = properties_result {
let properties = unsafe { CFRetained::from_raw(properties) };
if let Ok(properties) = properties.downcast::<CFDictionary>() {
if let Some(result) = eval(parent_entry, &properties) {
return Some(result);
}
}
}
}
}
None
}
pub(crate) fn get_disk_type(bsd_name: &[u8]) -> Option<DiskKind> {
let characteristics_string =
CFString::from_static_str(ffi::kIOPropertyDeviceCharacteristicsKey);
iterate_service_tree(bsd_name, &characteristics_string, |_, properties| {
let medium = unsafe {
super::disk::get_str_value(
properties,
Some(&CFString::from_static_str(ffi::kIOPropertyMediumTypeKey)),
)
}?;
match medium.as_str() {
_ if medium == ffi::kIOPropertyMediumTypeSolidStateKey => Some(DiskKind::SSD),
_ if medium == ffi::kIOPropertyMediumTypeRotationalKey => Some(DiskKind::HDD),
_ => Some(DiskKind::Unknown(-1)),
}
})
}
/// Returns a tuple consisting of the total number of bytes read and written by the specified disk
pub(crate) fn get_disk_io(bsd_name: &[u8]) -> Option<(u64, u64)> {
let stat_string = CFString::from_static_str(ffi::kIOBlockStorageDriverStatisticsKey);
iterate_service_tree(bsd_name, &stat_string, |parent_entry, properties| {
if unsafe {
ffi::IOObjectConformsTo(parent_entry, b"IOBlockStorageDriver\0".as_ptr() as *const _)
} == 0
{
return None;
}
unsafe {
let read_bytes = super::disk::get_int_value(
properties,
Some(&CFString::from_static_str(
ffi::kIOBlockStorageDriverStatisticsBytesReadKey,
)),
)?;
let written_bytes = super::disk::get_int_value(
properties,
Some(&CFString::from_static_str(
ffi::kIOBlockStorageDriverStatisticsBytesWrittenKey,
)),
)?;
Some((read_bytes, written_bytes))
}
})
}

View File

@@ -0,0 +1,364 @@
// Take a look at the license at the top of the repository in the LICENSE file.
// Note: IOKit is only available on macOS up until very recent iOS versions: https://developer.apple.com/documentation/iokit
cfg_if! {
if #[cfg(any(
feature = "disk",
all(
not(feature = "apple-sandbox"),
any(
feature = "system",
all(
feature = "component",
any(target_arch = "x86", target_arch = "x86_64")
)
)
),
))] {
#[allow(non_camel_case_types)]
pub type io_object_t = libc::mach_port_t;
#[allow(non_camel_case_types)]
pub type io_iterator_t = io_object_t;
// Based on https://github.com/libusb/libusb/blob/bed8d3034eac74a6e1ba123b5c270ea63cb6cf1a/libusb/os/darwin_usb.c#L54-L55,
// we can simply set it to 0 (and is the same value as its
// replacement `kIOMainPortDefault`).
#[allow(non_upper_case_globals)]
pub const kIOMasterPortDefault: libc::mach_port_t = 0;
}
if #[cfg(any(
all(feature = "system", not(feature = "apple-sandbox")),
feature = "disk"
))] {
#[allow(non_camel_case_types)]
pub type io_registry_entry_t = io_object_t;
#[allow(non_camel_case_types)]
pub type io_name_t = *const libc::c_char;
// This is a hack, `io_name_t` should normally be `[c_char; 128]` but Rust makes it very annoying
// to deal with that so we go around it a bit.
#[allow(non_camel_case_types, dead_code)]
pub type io_name = [libc::c_char; 128];
}
}
#[cfg(any(
feature = "disk",
all(not(feature = "apple-sandbox"), feature = "system",),
))]
pub type IOOptionBits = u32;
cfg_if! {
if #[cfg(feature = "disk")] {
#[allow(non_upper_case_globals)]
pub const kIOServicePlane: &[u8] = b"IOService\0";
#[allow(non_upper_case_globals)]
pub const kIOPropertyDeviceCharacteristicsKey: &str = "Device Characteristics";
#[allow(non_upper_case_globals)]
pub const kIOPropertyMediumTypeKey: &str = "Medium Type";
#[allow(non_upper_case_globals)]
pub const kIOPropertyMediumTypeSolidStateKey: &str = "Solid State";
#[allow(non_upper_case_globals)]
pub const kIOPropertyMediumTypeRotationalKey: &str = "Rotational";
#[allow(non_upper_case_globals)]
pub const kIOBlockStorageDriverStatisticsKey: &str = "Statistics";
#[allow(non_upper_case_globals)]
pub const kIOBlockStorageDriverStatisticsBytesReadKey: &str = "Bytes (Read)";
#[allow(non_upper_case_globals)]
pub const kIOBlockStorageDriverStatisticsBytesWrittenKey: &str = "Bytes (Write)";
}
}
// Note: Obtaining information about disks using IOKIt is allowed inside the default macOS App Sandbox.
#[cfg(any(
feature = "disk",
all(
not(feature = "apple-sandbox"),
any(
feature = "system",
all(
feature = "component",
any(target_arch = "x86", target_arch = "x86_64")
)
)
),
))]
#[link(name = "IOKit", kind = "framework")]
extern "C" {
pub fn IOServiceGetMatchingServices(
mainPort: libc::mach_port_t,
matching: std::ptr::NonNull<objc2_core_foundation::CFMutableDictionary>, // CF_RELEASES_ARGUMENT
existing: *mut io_iterator_t,
) -> libc::kern_return_t;
#[cfg(all(
not(feature = "apple-sandbox"),
any(
feature = "system",
all(
feature = "component",
any(target_arch = "x86", target_arch = "x86_64")
),
),
))]
pub fn IOServiceMatching(
a: *const libc::c_char,
) -> Option<std::ptr::NonNull<objc2_core_foundation::CFMutableDictionary>>; // CF_RETURNS_RETAINED
pub fn IOIteratorNext(iterator: io_iterator_t) -> io_object_t;
pub fn IOObjectRelease(obj: io_object_t) -> libc::kern_return_t;
#[cfg(any(feature = "system", feature = "disk"))]
pub fn IORegistryEntryCreateCFProperty(
entry: io_registry_entry_t,
key: &objc2_core_foundation::CFString,
allocator: Option<&objc2_core_foundation::CFAllocator>,
options: IOOptionBits,
) -> Option<std::ptr::NonNull<objc2_core_foundation::CFType>>;
#[cfg(feature = "disk")]
pub fn IORegistryEntryGetParentEntry(
entry: io_registry_entry_t,
plane: io_name_t,
parent: *mut io_registry_entry_t,
) -> libc::kern_return_t;
#[cfg(feature = "disk")]
pub fn IOBSDNameMatching(
mainPort: libc::mach_port_t,
options: u32,
bsdName: *const libc::c_char,
) -> Option<std::ptr::NonNull<objc2_core_foundation::CFMutableDictionary>>; // CF_RETURNS_RETAINED
#[cfg(all(feature = "system", not(feature = "apple-sandbox")))]
pub fn IORegistryEntryGetName(
entry: io_registry_entry_t,
name: io_name_t,
) -> libc::kern_return_t;
#[cfg(feature = "disk")]
pub fn IOObjectConformsTo(
object: io_object_t,
className: *const libc::c_char,
) -> libc::boolean_t;
}
#[cfg(all(
not(feature = "apple-sandbox"),
any(
feature = "system",
all(
feature = "component",
any(target_arch = "x86", target_arch = "x86_64")
),
),
))]
pub const KIO_RETURN_SUCCESS: i32 = 0;
#[cfg(all(
not(feature = "apple-sandbox"),
all(
feature = "component",
any(target_arch = "x86", target_arch = "x86_64")
),
))]
mod io_service {
use super::io_object_t;
use libc::{kern_return_t, mach_port_t, size_t, task_t};
#[allow(non_camel_case_types)]
pub type io_connect_t = io_object_t;
#[allow(non_camel_case_types)]
pub type io_service_t = io_object_t;
#[allow(non_camel_case_types)]
pub type task_port_t = task_t;
extern "C" {
pub fn IOServiceOpen(
device: io_service_t,
owning_task: task_port_t,
type_: u32,
connect: *mut io_connect_t,
) -> kern_return_t;
pub fn IOServiceClose(a: io_connect_t) -> kern_return_t;
#[allow(dead_code)]
pub fn IOConnectCallStructMethod(
connection: mach_port_t,
selector: u32,
inputStruct: *const KeyData_t,
inputStructCnt: size_t,
outputStruct: *mut KeyData_t,
outputStructCnt: *mut size_t,
) -> kern_return_t;
}
#[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))]
#[repr(C)]
pub struct KeyData_vers_t {
pub major: u8,
pub minor: u8,
pub build: u8,
pub reserved: [u8; 1],
pub release: u16,
}
#[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))]
#[repr(C)]
pub struct KeyData_pLimitData_t {
pub version: u16,
pub length: u16,
pub cpu_plimit: u32,
pub gpu_plimit: u32,
pub mem_plimit: u32,
}
#[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))]
#[repr(C)]
pub struct KeyData_keyInfo_t {
pub data_size: u32,
pub data_type: u32,
pub data_attributes: u8,
}
#[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))]
#[repr(C)]
pub struct KeyData_t {
pub key: u32,
pub vers: KeyData_vers_t,
pub p_limit_data: KeyData_pLimitData_t,
pub key_info: KeyData_keyInfo_t,
pub result: u8,
pub status: u8,
pub data8: u8,
pub data32: u32,
pub bytes: [i8; 32], // SMCBytes_t
}
#[allow(dead_code)]
pub const KERNEL_INDEX_SMC: i32 = 2;
#[allow(dead_code)]
pub const SMC_CMD_READ_KEYINFO: u8 = 9;
#[allow(dead_code)]
pub const SMC_CMD_READ_BYTES: u8 = 5;
}
#[cfg(feature = "apple-sandbox")]
mod io_service {}
#[cfg(all(
feature = "component",
not(feature = "apple-sandbox"),
target_arch = "aarch64"
))]
mod io_service {
use std::ptr::NonNull;
use objc2_core_foundation::{
cf_type, kCFTypeDictionaryKeyCallBacks, kCFTypeDictionaryValueCallBacks, CFAllocator,
CFArray, CFDictionary, CFDictionaryCreate, CFNumber, CFRetained, CFString,
};
#[repr(C)]
pub struct IOHIDServiceClient(libc::c_void);
cf_type!(
#[encoding_name = "__IOHIDServiceClient"]
unsafe impl IOHIDServiceClient {}
);
#[repr(C)]
pub struct IOHIDEventSystemClient(libc::c_void);
cf_type!(
#[encoding_name = "__IOHIDEventSystemClient"]
unsafe impl IOHIDEventSystemClient {}
);
#[repr(C)]
pub struct IOHIDEvent(libc::c_void);
cf_type!(
#[encoding_name = "__IOHIDEvent"]
unsafe impl IOHIDEvent {}
);
#[allow(non_upper_case_globals)]
pub const kIOHIDEventTypeTemperature: i64 = 15;
#[inline]
#[allow(non_snake_case)]
pub fn IOHIDEventFieldBase(event_type: i64) -> i64 {
event_type << 16
}
#[cfg(not(feature = "apple-sandbox"))]
#[link(name = "IOKit", kind = "framework")]
extern "C" {
pub fn IOHIDEventSystemClientCreate(
allocator: Option<&CFAllocator>,
) -> Option<NonNull<IOHIDEventSystemClient>>;
pub fn IOHIDEventSystemClientSetMatching(
client: &IOHIDEventSystemClient,
matches: &CFDictionary,
) -> i32;
pub fn IOHIDEventSystemClientCopyServices(
client: &IOHIDEventSystemClient,
) -> Option<NonNull<CFArray>>;
pub fn IOHIDServiceClientCopyProperty(
service: &IOHIDServiceClient,
key: &CFString,
) -> Option<NonNull<CFString>>;
pub fn IOHIDServiceClientCopyEvent(
service: &IOHIDServiceClient,
v0: i64,
v1: i32,
v2: i64,
) -> Option<NonNull<IOHIDEvent>>;
pub fn IOHIDEventGetFloatValue(event: &IOHIDEvent, field: i64) -> f64;
}
pub(crate) const HID_DEVICE_PROPERTY_PRODUCT: &str = "Product";
pub(crate) const HID_DEVICE_PROPERTY_PRIMARY_USAGE: &str = "PrimaryUsage";
pub(crate) const HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE: &str = "PrimaryUsagePage";
#[allow(non_upper_case_globals)]
pub(crate) const kHIDPage_AppleVendor: i32 = 0xff00;
#[allow(non_upper_case_globals)]
pub(crate) const kHIDUsage_AppleVendor_TemperatureSensor: i32 = 0x0005;
pub(crate) fn matching(page: i32, usage: i32) -> Option<CFRetained<CFDictionary>> {
unsafe {
let keys = [
CFString::from_static_str(HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE),
CFString::from_static_str(HID_DEVICE_PROPERTY_PRIMARY_USAGE),
];
let nums = [CFNumber::new_i32(page), CFNumber::new_i32(usage)];
CFDictionaryCreate(
None,
&keys as *const _ as *mut _,
&nums as *const _ as *mut _,
2,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks,
)
}
}
}
#[cfg(all(
feature = "component",
not(feature = "apple-sandbox"),
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
))]
pub use io_service::*;

View File

@@ -0,0 +1,57 @@
// Take a look at the license at the top of the repository in the LICENSE file.
pub mod ffi;
cfg_if! {
if #[cfg(all(feature = "system", not(feature = "apple-sandbox")))] {
pub(crate) mod cpu;
pub mod system;
pub mod process;
}
if #[cfg(all(feature = "system", feature = "apple-sandbox"))] {
pub use crate::sys::app_store::process;
}
if #[cfg(any(
feature = "disk",
all(
not(feature = "apple-sandbox"),
any(
feature = "system",
all(
feature = "component",
any(target_arch = "x86", target_arch = "x86_64")
)
)
),
))]
{
pub(crate) mod utils;
}
if #[cfg(feature = "disk")] {
pub mod disk;
}
if #[cfg(feature = "apple-sandbox")] {
#[cfg(feature = "component")]
pub use crate::sys::app_store::component;
} else if #[cfg(feature = "component")] {
pub mod component;
}
}
// Make formattable by rustfmt.
#[cfg(any())]
mod component;
#[cfg(any())]
mod cpu;
#[cfg(any())]
mod disk;
#[cfg(any())]
mod process;
#[cfg(any())]
mod system;
#[cfg(any())]
mod utils;

View File

@@ -0,0 +1,799 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use std::ffi::{OsStr, OsString};
use std::mem::{self, MaybeUninit};
use std::os::unix::ffi::{OsStrExt, OsStringExt};
use std::path::{Path, PathBuf};
use std::process::ExitStatus;
use libc::{c_int, c_void, kill};
use crate::{DiskUsage, Gid, Pid, Process, ProcessRefreshKind, ProcessStatus, Signal, Uid};
use crate::sys::process::ThreadStatus;
use crate::sys::system::Wrap;
use crate::unix::utils::cstr_to_rust_with_size;
pub(crate) struct ProcessInner {
pub(crate) name: OsString,
pub(crate) cmd: Vec<OsString>,
pub(crate) exe: Option<PathBuf>,
pid: Pid,
parent: Option<Pid>,
pub(crate) environ: Vec<OsString>,
cwd: Option<PathBuf>,
pub(crate) root: Option<PathBuf>,
pub(crate) memory: u64,
pub(crate) virtual_memory: u64,
old_utime: u64,
old_stime: u64,
start_time: u64,
run_time: u64,
pub(crate) updated: bool,
cpu_usage: f32,
user_id: Option<Uid>,
effective_user_id: Option<Uid>,
group_id: Option<Gid>,
effective_group_id: Option<Gid>,
pub(crate) process_status: ProcessStatus,
/// Status of process (running, stopped, waiting, etc). `None` means `sysinfo` doesn't have
/// enough rights to get this information.
///
/// This is very likely this one that you want instead of `process_status`.
pub(crate) status: Option<ThreadStatus>,
pub(crate) old_read_bytes: u64,
pub(crate) old_written_bytes: u64,
pub(crate) read_bytes: u64,
pub(crate) written_bytes: u64,
accumulated_cpu_time: u64,
exists: bool,
}
impl ProcessInner {
pub(crate) fn new_empty(pid: Pid) -> Self {
Self {
name: OsString::new(),
pid,
parent: None,
cmd: Vec::new(),
environ: Vec::new(),
exe: None,
cwd: None,
root: None,
memory: 0,
virtual_memory: 0,
cpu_usage: 0.,
old_utime: 0,
old_stime: 0,
updated: true,
start_time: 0,
run_time: 0,
user_id: None,
effective_user_id: None,
group_id: None,
effective_group_id: None,
process_status: ProcessStatus::Unknown(0),
status: None,
old_read_bytes: 0,
old_written_bytes: 0,
read_bytes: 0,
written_bytes: 0,
accumulated_cpu_time: 0,
exists: true,
}
}
pub(crate) fn new(pid: Pid, parent: Option<Pid>, start_time: u64, run_time: u64) -> Self {
Self {
name: OsString::new(),
pid,
parent,
cmd: Vec::new(),
environ: Vec::new(),
exe: None,
cwd: None,
root: None,
memory: 0,
virtual_memory: 0,
cpu_usage: 0.,
old_utime: 0,
old_stime: 0,
updated: true,
start_time,
run_time,
user_id: None,
effective_user_id: None,
group_id: None,
effective_group_id: None,
process_status: ProcessStatus::Unknown(0),
status: None,
old_read_bytes: 0,
old_written_bytes: 0,
read_bytes: 0,
written_bytes: 0,
accumulated_cpu_time: 0,
exists: true,
}
}
pub(crate) fn kill_with(&self, signal: Signal) -> Option<bool> {
let c_signal = crate::sys::system::convert_signal(signal)?;
unsafe { Some(kill(self.pid.0, c_signal) == 0) }
}
pub(crate) fn name(&self) -> &OsStr {
&self.name
}
pub(crate) fn cmd(&self) -> &[OsString] {
&self.cmd
}
pub(crate) fn exe(&self) -> Option<&Path> {
self.exe.as_deref()
}
pub(crate) fn pid(&self) -> Pid {
self.pid
}
pub(crate) fn environ(&self) -> &[OsString] {
&self.environ
}
pub(crate) fn cwd(&self) -> Option<&Path> {
self.cwd.as_deref()
}
pub(crate) fn root(&self) -> Option<&Path> {
self.root.as_deref()
}
pub(crate) fn memory(&self) -> u64 {
self.memory
}
pub(crate) fn virtual_memory(&self) -> u64 {
self.virtual_memory
}
pub(crate) fn parent(&self) -> Option<Pid> {
self.parent
}
pub(crate) fn status(&self) -> ProcessStatus {
// If the status is `Run`, then it's very likely wrong so we instead
// return a `ProcessStatus` converted from the `ThreadStatus`.
if self.process_status == ProcessStatus::Run {
if let Some(thread_status) = self.status {
return ProcessStatus::from(thread_status);
}
}
self.process_status
}
pub(crate) fn start_time(&self) -> u64 {
self.start_time
}
pub(crate) fn run_time(&self) -> u64 {
self.run_time
}
pub(crate) fn cpu_usage(&self) -> f32 {
self.cpu_usage
}
pub(crate) fn accumulated_cpu_time(&self) -> u64 {
self.accumulated_cpu_time
}
pub(crate) fn disk_usage(&self) -> DiskUsage {
DiskUsage {
read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes),
total_read_bytes: self.read_bytes,
written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
total_written_bytes: self.written_bytes,
}
}
pub(crate) fn user_id(&self) -> Option<&Uid> {
self.user_id.as_ref()
}
pub(crate) fn effective_user_id(&self) -> Option<&Uid> {
self.effective_user_id.as_ref()
}
pub(crate) fn group_id(&self) -> Option<Gid> {
self.group_id
}
pub(crate) fn effective_group_id(&self) -> Option<Gid> {
self.effective_group_id
}
pub(crate) fn wait(&self) -> Option<ExitStatus> {
crate::unix::utils::wait_process(self.pid)
}
pub(crate) fn session_id(&self) -> Option<Pid> {
unsafe {
let session_id = libc::getsid(self.pid.0);
if session_id < 0 {
None
} else {
Some(Pid(session_id))
}
}
}
pub(crate) fn switch_updated(&mut self) -> bool {
std::mem::replace(&mut self.updated, false)
}
pub(crate) fn set_nonexistent(&mut self) {
self.exists = false;
}
pub(crate) fn exists(&self) -> bool {
self.exists
}
}
#[allow(deprecated)] // Because of libc::mach_absolute_time.
pub(crate) fn compute_cpu_usage(
p: &mut ProcessInner,
task_info: libc::proc_taskinfo,
system_time: u64,
user_time: u64,
time_interval: Option<f64>,
) {
if let Some(time_interval) = time_interval {
let total_existing_time = p.old_stime.saturating_add(p.old_utime);
let mut updated_cpu_usage = false;
if time_interval > 0.000001 && total_existing_time > 0 {
let total_current_time = task_info
.pti_total_system
.saturating_add(task_info.pti_total_user);
let total_time_diff = total_current_time.saturating_sub(total_existing_time);
if total_time_diff > 0 {
p.cpu_usage = (total_time_diff as f64 / time_interval * 100.) as f32;
updated_cpu_usage = true;
}
}
if !updated_cpu_usage {
p.cpu_usage = 0.;
}
p.old_stime = task_info.pti_total_system;
p.old_utime = task_info.pti_total_user;
} else {
unsafe {
// This is the "backup way" of CPU computation.
let time = libc::mach_absolute_time();
let task_time = user_time
.saturating_add(system_time)
.saturating_add(task_info.pti_total_user)
.saturating_add(task_info.pti_total_system);
let system_time_delta = if task_time < p.old_utime {
task_time
} else {
task_time.saturating_sub(p.old_utime)
};
let time_delta = if time < p.old_stime {
time
} else {
time.saturating_sub(p.old_stime)
};
p.old_utime = task_time;
p.old_stime = time;
p.cpu_usage = if time_delta == 0 {
0f32
} else {
(system_time_delta as f64 * 100f64 / time_delta as f64) as f32
};
}
}
}
unsafe fn get_task_info(pid: Pid) -> libc::proc_taskinfo {
let mut task_info = mem::zeroed::<libc::proc_taskinfo>();
// If it doesn't work, we just don't have memory information for this process
// so it's "fine".
libc::proc_pidinfo(
pid.0,
libc::PROC_PIDTASKINFO,
0,
&mut task_info as *mut libc::proc_taskinfo as *mut c_void,
mem::size_of::<libc::proc_taskinfo>() as _,
);
task_info
}
#[inline]
fn check_if_pid_is_alive(pid: Pid, check_if_alive: bool) -> bool {
// In case we are iterating all pids we got from `proc_listallpids`, then
// there is no point checking if the process is alive since it was returned
// from this function.
if !check_if_alive {
return true;
}
unsafe {
if kill(pid.0, 0) == 0 {
return true;
}
// `kill` failed but it might not be because the process is dead.
let errno = crate::unix::libc_errno();
// If errno is equal to ESCHR, it means the process is dead.
!errno.is_null() && *errno != libc::ESRCH
}
}
unsafe fn get_bsd_info(pid: Pid) -> Option<libc::proc_bsdinfo> {
let mut info = mem::zeroed::<libc::proc_bsdinfo>();
if libc::proc_pidinfo(
pid.0,
libc::PROC_PIDTBSDINFO,
0,
&mut info as *mut _ as *mut _,
mem::size_of::<libc::proc_bsdinfo>() as _,
) != mem::size_of::<libc::proc_bsdinfo>() as c_int
{
None
} else {
Some(info)
}
}
fn get_parent(info: &libc::proc_bsdinfo) -> Option<Pid> {
match info.pbi_ppid as i32 {
0 => None,
p => Some(Pid(p)),
}
}
unsafe fn create_new_process(
pid: Pid,
now: u64,
refresh_kind: ProcessRefreshKind,
info: Option<libc::proc_bsdinfo>,
timebase_to_ms: f64,
) -> Result<Option<Process>, ()> {
let info = match info {
Some(info) => info,
None => {
let mut p = ProcessInner::new_empty(pid);
if get_exe_and_name_backup(&mut p, refresh_kind, false) {
get_cwd_root(&mut p, refresh_kind);
return Ok(Some(Process { inner: p }));
}
// If we can't even have the name, no point in keeping it.
return Err(());
}
};
let parent = get_parent(&info);
let start_time = info.pbi_start_tvsec;
let run_time = now.saturating_sub(start_time);
let mut p = ProcessInner::new(pid, parent, start_time, run_time);
if !get_process_infos(&mut p, refresh_kind)
&& !get_exe_and_name_backup(&mut p, refresh_kind, false)
{
// If we can't even have the name, no point in keeping it.
return Err(());
}
get_cwd_root(&mut p, refresh_kind);
if refresh_kind.cpu() || refresh_kind.memory() {
let task_info = get_task_info(pid);
if refresh_kind.cpu() {
p.accumulated_cpu_time = (task_info
.pti_total_user
.saturating_add(task_info.pti_total_system)
as f64
* timebase_to_ms) as u64;
}
if refresh_kind.memory() {
p.memory = task_info.pti_resident_size;
p.virtual_memory = task_info.pti_virtual_size;
}
}
p.user_id = Some(Uid(info.pbi_ruid));
p.effective_user_id = Some(Uid(info.pbi_uid));
p.group_id = Some(Gid(info.pbi_rgid));
p.effective_group_id = Some(Gid(info.pbi_gid));
p.process_status = ProcessStatus::from(info.pbi_status);
if refresh_kind.disk_usage() {
update_proc_disk_activity(&mut p);
}
Ok(Some(Process { inner: p }))
}
/// Less efficient way to retrieve `exe` and `name`.
unsafe fn get_exe_and_name_backup(
process: &mut ProcessInner,
refresh_kind: ProcessRefreshKind,
force_check: bool,
) -> bool {
let exe_needs_update = refresh_kind.exe().needs_update(|| process.exe.is_none());
if !process.name.is_empty() && !exe_needs_update && !force_check {
return true;
}
let mut buffer: Vec<u8> = Vec::with_capacity(libc::PROC_PIDPATHINFO_MAXSIZE as _);
match libc::proc_pidpath(
process.pid.0,
buffer.as_mut_ptr() as *mut _,
libc::PROC_PIDPATHINFO_MAXSIZE as _,
) {
x if x > 0 => {
buffer.set_len(x as _);
let tmp = OsString::from_vec(buffer);
let exe = PathBuf::from(tmp);
if process.name.is_empty() {
exe.file_name()
.unwrap_or_default()
.clone_into(&mut process.name);
}
if exe_needs_update {
process.exe = Some(exe);
}
true
}
_ => false,
}
}
unsafe fn convert_node_path_info(node: &libc::vnode_info_path) -> Option<PathBuf> {
if node.vip_vi.vi_stat.vst_dev == 0 {
return None;
}
cstr_to_rust_with_size(
node.vip_path.as_ptr() as _,
Some(mem::size_of_val(&node.vip_path)),
)
.map(PathBuf::from)
}
unsafe fn get_cwd_root(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) {
let cwd_needs_update = refresh_kind.cwd().needs_update(|| process.cwd.is_none());
let root_needs_update = refresh_kind.root().needs_update(|| process.root.is_none());
if !cwd_needs_update && !root_needs_update {
return;
}
let mut vnodepathinfo = mem::zeroed::<libc::proc_vnodepathinfo>();
let result = libc::proc_pidinfo(
process.pid.0,
libc::PROC_PIDVNODEPATHINFO,
0,
&mut vnodepathinfo as *mut _ as *mut _,
mem::size_of::<libc::proc_vnodepathinfo>() as _,
);
if result < 1 {
sysinfo_debug!("Failed to retrieve cwd and root for {}", process.pid.0);
return;
}
if cwd_needs_update {
process.cwd = convert_node_path_info(&vnodepathinfo.pvi_cdir);
}
if root_needs_update {
process.root = convert_node_path_info(&vnodepathinfo.pvi_rdir);
}
}
unsafe fn get_process_infos(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) -> bool {
/*
* /---------------\ 0x00000000
* | ::::::::::::: |
* |---------------| <-- Beginning of data returned by sysctl() is here.
* | argc |
* |---------------|
* | exec_path |
* |---------------|
* | 0 |
* |---------------|
* | arg[0] |
* |---------------|
* | 0 |
* |---------------|
* | arg[n] |
* |---------------|
* | 0 |
* |---------------|
* | env[0] |
* |---------------|
* | 0 |
* |---------------|
* | env[n] |
* |---------------|
* | ::::::::::::: |
* |---------------| <-- Top of stack.
* : :
* : :
* \---------------/ 0xffffffff
*/
let mut mib: [libc::c_int; 3] = [libc::CTL_KERN, libc::KERN_PROCARGS2, process.pid.0 as _];
let mut arg_max = 0;
// First we retrieve the size we will need for our data (in `arg_max`).
if libc::sysctl(
mib.as_mut_ptr(),
mib.len() as _,
std::ptr::null_mut(),
&mut arg_max,
std::ptr::null_mut(),
0,
) == -1
{
sysinfo_debug!(
"couldn't get arguments and environment size for PID {}",
process.pid.0
);
return false; // not enough rights I assume?
}
let mut proc_args: Vec<u8> = Vec::with_capacity(arg_max as _);
if libc::sysctl(
mib.as_mut_ptr(),
mib.len() as _,
proc_args.as_mut_slice().as_mut_ptr() as *mut _,
&mut arg_max,
std::ptr::null_mut(),
0,
) == -1
{
sysinfo_debug!(
"couldn't get arguments and environment for PID {}",
process.pid.0
);
return false; // What changed since the previous call? Dark magic!
}
proc_args.set_len(arg_max);
if proc_args.is_empty() {
return false;
}
// We copy the number of arguments (`argc`) to `n_args`.
let mut n_args: c_int = 0;
libc::memcpy(
&mut n_args as *mut _ as *mut _,
proc_args.as_slice().as_ptr() as *const _,
mem::size_of::<c_int>(),
);
// We skip `argc`.
let proc_args = &proc_args[mem::size_of::<c_int>()..];
let (exe, proc_args) = get_exe(proc_args);
if process.name.is_empty() {
exe.file_name()
.unwrap_or_default()
.clone_into(&mut process.name);
}
if refresh_kind.exe().needs_update(|| process.exe.is_none()) {
process.exe = Some(exe.to_owned());
}
let environ_needs_update = refresh_kind
.environ()
.needs_update(|| process.environ.is_empty());
let cmd_needs_update = refresh_kind.cmd().needs_update(|| process.cmd.is_empty());
if !environ_needs_update && !cmd_needs_update {
// Nothing else to be done!
return true;
}
let proc_args = get_arguments(&mut process.cmd, proc_args, n_args, cmd_needs_update);
if environ_needs_update {
get_environ(&mut process.environ, proc_args);
}
true
}
fn get_exe(data: &[u8]) -> (&Path, &[u8]) {
let pos = data.iter().position(|c| *c == 0).unwrap_or(data.len());
let (exe, proc_args) = data.split_at(pos);
(Path::new(OsStr::from_bytes(exe)), proc_args)
}
fn get_arguments<'a>(
cmd: &mut Vec<OsString>,
mut data: &'a [u8],
mut n_args: c_int,
refresh_cmd: bool,
) -> &'a [u8] {
if refresh_cmd {
cmd.clear();
}
if n_args < 1 {
return data;
}
while data.first() == Some(&0) {
data = &data[1..];
}
while n_args > 0 && !data.is_empty() {
let pos = data.iter().position(|c| *c == 0).unwrap_or(data.len());
let arg = &data[..pos];
if !arg.is_empty() && refresh_cmd {
cmd.push(OsStr::from_bytes(arg).to_os_string());
}
data = &data[pos..];
while data.first() == Some(&0) {
data = &data[1..];
}
n_args -= 1;
}
data
}
fn get_environ(environ: &mut Vec<OsString>, mut data: &[u8]) {
environ.clear();
while data.first() == Some(&0) {
data = &data[1..];
}
while !data.is_empty() {
let pos = data.iter().position(|c| *c == 0).unwrap_or(data.len());
let arg = &data[..pos];
if arg.is_empty() {
return;
}
environ.push(OsStr::from_bytes(arg).to_os_string());
data = &data[pos..];
while data.first() == Some(&0) {
data = &data[1..];
}
}
}
pub(crate) fn update_process(
wrap: &Wrap,
pid: Pid,
time_interval: Option<f64>,
now: u64,
refresh_kind: ProcessRefreshKind,
check_if_alive: bool,
timebase_to_ms: f64,
) -> Result<Option<Process>, ()> {
unsafe {
if let Some(ref mut p) = (*wrap.0.get()).get_mut(&pid) {
let p = &mut p.inner;
let mut extra_checked = false;
if let Some(info) = get_bsd_info(pid) {
if info.pbi_start_tvsec != p.start_time {
// We don't want it to be removed, just replaced.
p.updated = true;
// To ensure the name and exe path will be updated.
p.name.clear();
p.exe = None;
// The owner of this PID changed.
return create_new_process(pid, now, refresh_kind, Some(info), timebase_to_ms);
}
let parent = get_parent(&info);
// Update the parent if it changed.
if p.parent != parent {
p.parent = parent;
}
} else {
// Weird that we can't get this information. Sometimes, mac can list PIDs that do
// not exist anymore. So let's ensure that the process is actually still alive.
if !get_exe_and_name_backup(p, refresh_kind, true) {
// So it's not actually alive, then let's un-update it so it will be removed.
p.updated = false;
return Ok(None);
}
extra_checked = true;
}
if !get_process_infos(p, refresh_kind) && !extra_checked {
get_exe_and_name_backup(p, refresh_kind, false);
}
get_cwd_root(p, refresh_kind);
if refresh_kind.disk_usage() {
update_proc_disk_activity(p);
}
let mut thread_info = mem::zeroed::<libc::proc_threadinfo>();
let (user_time, system_time, thread_status) = if libc::proc_pidinfo(
pid.0,
libc::PROC_PIDTHREADINFO,
0,
&mut thread_info as *mut libc::proc_threadinfo as *mut c_void,
mem::size_of::<libc::proc_threadinfo>() as _,
) != 0
{
(
thread_info.pth_user_time,
thread_info.pth_system_time,
Some(ThreadStatus::from(thread_info.pth_run_state)),
)
} else {
// It very likely means that the process is dead...
if check_if_pid_is_alive(pid, check_if_alive) {
(0, 0, Some(ThreadStatus::Running))
} else {
return Err(());
}
};
p.status = thread_status;
p.run_time = now.saturating_sub(p.start_time);
if refresh_kind.cpu() || refresh_kind.memory() {
let task_info = get_task_info(pid);
if refresh_kind.cpu() {
compute_cpu_usage(p, task_info, system_time, user_time, time_interval);
p.accumulated_cpu_time = (task_info
.pti_total_user
.saturating_add(task_info.pti_total_system)
as f64
* timebase_to_ms) as u64;
}
if refresh_kind.memory() {
p.memory = task_info.pti_resident_size;
p.virtual_memory = task_info.pti_virtual_size;
}
}
p.updated = true;
Ok(None)
} else {
create_new_process(pid, now, refresh_kind, get_bsd_info(pid), timebase_to_ms)
}
}
}
fn update_proc_disk_activity(p: &mut ProcessInner) {
p.old_read_bytes = p.read_bytes;
p.old_written_bytes = p.written_bytes;
let mut pidrusage = MaybeUninit::<libc::rusage_info_v2>::uninit();
unsafe {
let retval = libc::proc_pid_rusage(
p.pid().0 as _,
libc::RUSAGE_INFO_V2,
pidrusage.as_mut_ptr() as _,
);
if retval < 0 {
sysinfo_debug!("proc_pid_rusage failed: {:?}", retval);
} else {
let pidrusage = pidrusage.assume_init();
p.read_bytes = pidrusage.ri_diskio_bytesread;
p.written_bytes = pidrusage.ri_diskio_byteswritten;
}
}
}
#[allow(clippy::uninit_vec)]
pub(crate) fn get_proc_list() -> Option<Vec<Pid>> {
unsafe {
let count = libc::proc_listallpids(::std::ptr::null_mut(), 0);
if count < 1 {
return None;
}
let mut pids: Vec<Pid> = Vec::with_capacity(count as usize);
pids.set_len(count as usize);
let count = count * mem::size_of::<Pid>() as i32;
let x = libc::proc_listallpids(pids.as_mut_ptr() as *mut c_void, count);
if x < 1 || x as usize >= pids.len() {
None
} else {
pids.set_len(x as usize);
Some(pids)
}
}
}

View File

@@ -0,0 +1,182 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[allow(deprecated)]
use libc::{mach_timebase_info, mach_timebase_info_data_t};
use libc::{
host_processor_info, mach_port_t, munmap, natural_t, processor_cpu_load_info,
processor_cpu_load_info_t, sysconf, vm_page_size, PROCESSOR_CPU_LOAD_INFO, _SC_CLK_TCK,
};
use std::ptr::null_mut;
use std::time::Instant;
struct ProcessorCpuLoadInfo {
cpu_load: processor_cpu_load_info_t,
cpu_count: natural_t,
}
impl ProcessorCpuLoadInfo {
fn new(port: mach_port_t) -> Option<Self> {
let mut info_size = std::mem::size_of::<processor_cpu_load_info_t>() as _;
let mut cpu_count = 0;
let mut cpu_load: processor_cpu_load_info_t = null_mut();
unsafe {
if host_processor_info(
port,
PROCESSOR_CPU_LOAD_INFO,
&mut cpu_count,
&mut cpu_load as *mut _ as *mut _,
&mut info_size,
) != 0
{
sysinfo_debug!("host_processor_info failed, not updating CPU ticks usage...");
None
} else if cpu_count < 1 || cpu_load.is_null() {
None
} else {
Some(Self {
cpu_load,
cpu_count,
})
}
}
}
}
impl Drop for ProcessorCpuLoadInfo {
fn drop(&mut self) {
unsafe {
munmap(self.cpu_load as _, vm_page_size);
}
}
}
pub(crate) struct SystemTimeInfo {
timebase_to_ns: f64,
pub(crate) timebase_to_ms: f64,
clock_per_sec: f64,
old_cpu_info: ProcessorCpuLoadInfo,
last_update: Option<Instant>,
prev_time_interval: f64,
}
unsafe impl Send for SystemTimeInfo {}
unsafe impl Sync for SystemTimeInfo {}
impl SystemTimeInfo {
#[allow(deprecated)] // Everything related to mach_timebase_info_data_t
pub fn new(port: mach_port_t) -> Option<Self> {
unsafe {
let clock_ticks_per_sec = sysconf(_SC_CLK_TCK);
// FIXME: Maybe check errno here? Problem is that if errno is not 0 before this call,
// we will get an error which isn't related...
// if let Some(er) = std::io::Error::last_os_error().raw_os_error() {
// if err != 0 {
// println!("==> {:?}", er);
// sysinfo_debug!("Failed to get _SC_CLK_TCK value, using old CPU tick measure system");
// return None;
// }
// }
let mut info = mach_timebase_info_data_t { numer: 0, denom: 0 };
if mach_timebase_info(&mut info) != libc::KERN_SUCCESS {
sysinfo_debug!("mach_timebase_info failed, using default value of 1");
info.numer = 1;
info.denom = 1;
}
let old_cpu_info = match ProcessorCpuLoadInfo::new(port) {
Some(cpu_info) => cpu_info,
None => {
sysinfo_debug!("host_processor_info failed, using old CPU tick measure system");
return None;
}
};
let nano_per_seconds = 1_000_000_000.;
let timebase_to_ns = info.numer as f64 / info.denom as f64;
sysinfo_debug!("");
Some(Self {
timebase_to_ns,
// We convert from nano (10^-9) to ms (10^3).
timebase_to_ms: timebase_to_ns / 1_000_000.,
clock_per_sec: nano_per_seconds / clock_ticks_per_sec as f64,
old_cpu_info,
last_update: None,
prev_time_interval: 0.,
})
}
}
pub fn get_time_interval(&mut self, port: mach_port_t) -> f64 {
let need_cpu_usage_update = self
.last_update
.map(|last_update| last_update.elapsed() > crate::MINIMUM_CPU_UPDATE_INTERVAL)
.unwrap_or(true);
if need_cpu_usage_update {
let mut total = 0;
let new_cpu_info = match ProcessorCpuLoadInfo::new(port) {
Some(cpu_info) => cpu_info,
None => return 0.,
};
let cpu_count = std::cmp::min(self.old_cpu_info.cpu_count, new_cpu_info.cpu_count);
unsafe {
for i in 0..cpu_count {
let new_load: &processor_cpu_load_info = &*new_cpu_info.cpu_load.offset(i as _);
let old_load: &processor_cpu_load_info =
&*self.old_cpu_info.cpu_load.offset(i as _);
for (new, old) in new_load.cpu_ticks.iter().zip(old_load.cpu_ticks.iter()) {
if new > old {
total += new.saturating_sub(*old);
}
}
}
}
self.old_cpu_info = new_cpu_info;
self.last_update = Some(Instant::now());
// Now we convert the ticks to nanoseconds (if the interval is less than
// `MINIMUM_CPU_UPDATE_INTERVAL`, we replace it with it instead):
let base_interval = total as f64 / cpu_count as f64 * self.clock_per_sec;
let smallest = crate::MINIMUM_CPU_UPDATE_INTERVAL.as_secs_f64() * 1_000_000_000.0;
self.prev_time_interval = if base_interval < smallest {
smallest
} else {
base_interval / self.timebase_to_ns
};
self.prev_time_interval
} else {
self.prev_time_interval
}
}
}
#[cfg(test)]
mod test {
use super::*;
/// Regression test for <https://github.com/GuillaumeGomez/sysinfo/issues/956>.
#[test]
fn test_getting_time_interval() {
if !crate::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
return;
}
#[allow(deprecated)]
let port = unsafe { libc::mach_host_self() };
let mut info = SystemTimeInfo::new(port).unwrap();
info.get_time_interval(port);
std::thread::sleep(crate::MINIMUM_CPU_UPDATE_INTERVAL.saturating_mul(5));
let val = info.get_time_interval(port);
assert_ne!(
val,
crate::MINIMUM_CPU_UPDATE_INTERVAL.as_secs_f64() * 1_000_000_000.0
);
}
}

View File

@@ -0,0 +1,31 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use std::num::NonZeroU32;
type IoObject = NonZeroU32;
pub(crate) struct IOReleaser(IoObject);
impl IOReleaser {
pub(crate) fn new(obj: u32) -> Option<Self> {
IoObject::new(obj).map(Self)
}
#[cfg(feature = "disk")]
pub(crate) unsafe fn new_unchecked(obj: u32) -> Self {
// Chance at catching in-development mistakes
debug_assert_ne!(obj, 0);
Self(IoObject::new_unchecked(obj))
}
#[inline]
pub(crate) fn inner(&self) -> u32 {
self.0.get()
}
}
impl Drop for IOReleaser {
fn drop(&mut self) {
unsafe { super::ffi::IOObjectRelease(self.0.get() as _) };
}
}

81
vendor/sysinfo/src/unix/apple/mod.rs vendored Normal file
View File

@@ -0,0 +1,81 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
pub(crate) mod app_store;
mod ffi;
mod utils;
cfg_if! {
if #[cfg(all(target_os = "macos", any(feature = "disk", feature = "system", feature = "component")))] {
pub(crate) mod macos;
pub(crate) use self::macos as inner;
} else if #[cfg(all(target_os = "ios", any(feature = "system", feature = "component")))] {
pub(crate) mod ios;
pub(crate) use self::ios as inner;
}
}
cfg_if! {
if #[cfg(feature = "system")] {
pub mod cpu;
pub mod process;
pub mod system;
pub(crate) use self::cpu::CpuInner;
pub(crate) use self::process::ProcessInner;
pub(crate) use self::system::SystemInner;
pub use self::system::{MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS};
}
if #[cfg(feature = "disk")] {
pub mod disk;
pub(crate) use self::disk::DiskInner;
pub(crate) use crate::unix::DisksInner;
}
if #[cfg(feature = "component")] {
pub mod component;
pub(crate) use self::component::{ComponentInner, ComponentsInner};
}
if #[cfg(feature = "network")] {
pub mod network;
pub(crate) use self::network::{NetworkDataInner, NetworksInner};
}
if #[cfg(feature = "user")] {
pub mod groups;
pub mod users;
pub(crate) use crate::unix::groups::get_groups;
pub(crate) use crate::unix::users::{get_users, UserInner};
}
}
#[doc = include_str!("../../../md_doc/is_supported.md")]
pub const IS_SUPPORTED_SYSTEM: bool = true;
// Make formattable by rustfmt.
#[cfg(any())]
mod component;
#[cfg(any())]
mod cpu;
#[cfg(any())]
mod disk;
#[cfg(any())]
mod groups;
#[cfg(any())]
mod ios;
#[cfg(any())]
mod macos;
#[cfg(any())]
mod network;
#[cfg(any())]
mod process;
#[cfg(any())]
mod system;
#[cfg(any())]
mod users;

338
vendor/sysinfo/src/unix/apple/network.rs vendored Normal file
View File

@@ -0,0 +1,338 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use libc::{
self, c_char, c_int, c_uint, if_data64, if_msghdr2, sysctl, CTL_NET, IFNAMSIZ, NET_RT_IFLIST2,
PF_ROUTE, RTM_IFINFO2,
};
use std::collections::{hash_map, HashMap};
use std::mem::{size_of, MaybeUninit};
use std::ptr::null_mut;
use crate::network::refresh_networks_addresses;
use crate::{IpNetwork, MacAddr, NetworkData};
// FIXME: To be removed once https://github.com/rust-lang/libc/pull/4022 is merged and released.
#[repr(C)]
struct ifmibdata {
ifmd_name: [c_char; IFNAMSIZ],
ifmd_pcount: c_uint,
ifmd_flags: c_uint,
ifmd_snd_len: c_uint,
ifmd_snd_maxlen: c_uint,
ifmd_snd_drops: c_uint,
ifmd_filler: [c_uint; 4],
ifmd_data: if_data64,
}
// FIXME: To be removed once https://github.com/rust-lang/libc/pull/4022 is merged and released.
pub const IFDATA_GENERAL: c_int = 1;
// FIXME: To be removed once https://github.com/rust-lang/libc/pull/4022 is merged and released.
pub const IFMIB_IFDATA: c_int = 2;
// FIXME: To be removed once https://github.com/rust-lang/libc/pull/4022 is merged and released.
pub const NETLINK_GENERIC: c_int = 0;
#[inline]
fn update_field(old_field: &mut u64, new_field: &mut u64, value: u64) {
*old_field = *new_field;
*new_field = value;
}
fn update_network_data(inner: &mut NetworkDataInner, data: &if_data64) {
update_field(&mut inner.old_out, &mut inner.current_out, data.ifi_obytes);
update_field(&mut inner.old_in, &mut inner.current_in, data.ifi_ibytes);
update_field(
&mut inner.old_packets_out,
&mut inner.packets_out,
data.ifi_opackets,
);
update_field(
&mut inner.old_packets_in,
&mut inner.packets_in,
data.ifi_ipackets,
);
update_field(
&mut inner.old_errors_in,
&mut inner.errors_in,
data.ifi_ierrors,
);
update_field(
&mut inner.old_errors_out,
&mut inner.errors_out,
data.ifi_oerrors,
);
}
pub(crate) struct NetworksInner {
pub(crate) interfaces: HashMap<String, NetworkData>,
}
impl NetworksInner {
pub(crate) fn new() -> Self {
Self {
interfaces: HashMap::new(),
}
}
pub(crate) fn list(&self) -> &HashMap<String, NetworkData> {
&self.interfaces
}
pub(crate) fn refresh(&mut self, remove_not_listed_interfaces: bool) {
self.update_networks();
if remove_not_listed_interfaces {
self.interfaces.retain(|_, i| {
if !i.inner.updated {
return false;
}
i.inner.updated = false;
true
});
}
refresh_networks_addresses(&mut self.interfaces);
}
#[allow(clippy::cast_ptr_alignment)]
#[allow(clippy::uninit_vec)]
fn update_networks(&mut self) {
let mib = &mut [CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0];
let mib2 = &mut [
CTL_NET,
libc::PF_LINK,
NETLINK_GENERIC,
IFMIB_IFDATA,
0,
IFDATA_GENERAL,
];
let mut len = 0;
unsafe {
if sysctl(
mib.as_mut_ptr(),
mib.len() as _,
null_mut(),
&mut len,
null_mut(),
0,
) < 0
{
// TODO: might be nice to put an error in here...
return;
}
let mut buf = Vec::with_capacity(len);
buf.set_len(len);
if sysctl(
mib.as_mut_ptr(),
mib.len() as _,
buf.as_mut_ptr(),
&mut len,
null_mut(),
0,
) < 0
{
// TODO: might be nice to put an error in here...
return;
}
let buf = buf.as_ptr() as *const c_char;
let lim = buf.add(len);
let mut next = buf;
while next < lim {
let ifm = next as *const libc::if_msghdr;
next = next.offset((*ifm).ifm_msglen as isize);
if (*ifm).ifm_type == RTM_IFINFO2 as u8 {
// The interface (line description) name stored at ifname will be returned in
// the default coded character set identifier (CCSID) currently in effect for
// the job. If this is not a single byte CCSID, then storage greater than
// IFNAMSIZ (16) bytes may be needed. 22 bytes is large enough for all CCSIDs.
let mut name = vec![0u8; libc::IFNAMSIZ + 6];
let if2m: *const if_msghdr2 = ifm as *const if_msghdr2;
let pname =
libc::if_indextoname((*if2m).ifm_index as _, name.as_mut_ptr() as _);
if pname.is_null() {
continue;
}
name.set_len(libc::strlen(pname));
let name = String::from_utf8_unchecked(name);
let mtu = (*if2m).ifm_data.ifi_mtu as u64;
// Because data size is capped at 32 bits with the previous sysctl call for some
// reasons, we need to make another sysctl call to get the actual values
// we originally got into `ifm.ifm_data`...
//
// Issue: https://github.com/GuillaumeGomez/sysinfo/issues/1378
let mut mib_data: MaybeUninit<ifmibdata> = MaybeUninit::uninit();
mib2[4] = (*if2m).ifm_index as _;
let ret = sysctl(
mib2.as_mut_ptr(),
mib2.len() as _,
mib_data.as_mut_ptr() as *mut _,
&mut size_of::<ifmibdata>(),
null_mut(),
0,
);
match self.interfaces.entry(name) {
hash_map::Entry::Occupied(mut e) => {
let interface = e.get_mut();
let interface = &mut interface.inner;
if ret < 0 {
sysinfo_debug!(
"Cannot get network interface data usage: sysctl failed: {ret}"
);
} else {
let data = mib_data.assume_init();
update_network_data(interface, &data.ifmd_data);
}
if interface.mtu != mtu {
interface.mtu = mtu
}
interface.updated = true;
}
hash_map::Entry::Vacant(e) => {
let current_in;
let current_out;
let packets_in;
let packets_out;
let errors_in;
let errors_out;
if ret < 0 {
sysinfo_debug!(
"Cannot get network interface data usage: sysctl failed: {ret}"
);
current_in = 0;
current_out = 0;
packets_in = 0;
packets_out = 0;
errors_in = 0;
errors_out = 0;
} else {
let data = mib_data.assume_init();
let data = data.ifmd_data;
current_in = data.ifi_ibytes;
current_out = data.ifi_obytes;
packets_in = data.ifi_ipackets;
packets_out = data.ifi_opackets;
errors_in = data.ifi_ierrors;
errors_out = data.ifi_oerrors;
}
e.insert(NetworkData {
inner: NetworkDataInner {
current_in,
old_in: current_in,
current_out,
old_out: current_out,
packets_in,
old_packets_in: packets_in,
packets_out,
old_packets_out: packets_out,
errors_in,
old_errors_in: errors_in,
errors_out,
old_errors_out: errors_out,
updated: true,
mac_addr: MacAddr::UNSPECIFIED,
ip_networks: vec![],
mtu,
},
});
}
}
}
}
}
}
}
#[derive(PartialEq, Eq)]
pub(crate) struct NetworkDataInner {
current_in: u64,
old_in: u64,
current_out: u64,
old_out: u64,
packets_in: u64,
old_packets_in: u64,
packets_out: u64,
old_packets_out: u64,
errors_in: u64,
old_errors_in: u64,
errors_out: u64,
old_errors_out: u64,
updated: bool,
/// MAC address
pub(crate) mac_addr: MacAddr,
/// IP networks
pub(crate) ip_networks: Vec<IpNetwork>,
/// Interface Maximum Transfer Unit (MTU)
mtu: u64,
}
impl NetworkDataInner {
pub(crate) fn received(&self) -> u64 {
self.current_in.saturating_sub(self.old_in)
}
pub(crate) fn total_received(&self) -> u64 {
self.current_in
}
pub(crate) fn transmitted(&self) -> u64 {
self.current_out.saturating_sub(self.old_out)
}
pub(crate) fn total_transmitted(&self) -> u64 {
self.current_out
}
pub(crate) fn packets_received(&self) -> u64 {
self.packets_in.saturating_sub(self.old_packets_in)
}
pub(crate) fn total_packets_received(&self) -> u64 {
self.packets_in
}
pub(crate) fn packets_transmitted(&self) -> u64 {
self.packets_out.saturating_sub(self.old_packets_out)
}
pub(crate) fn total_packets_transmitted(&self) -> u64 {
self.packets_out
}
pub(crate) fn errors_on_received(&self) -> u64 {
self.errors_in.saturating_sub(self.old_errors_in)
}
pub(crate) fn total_errors_on_received(&self) -> u64 {
self.errors_in
}
pub(crate) fn errors_on_transmitted(&self) -> u64 {
self.errors_out.saturating_sub(self.old_errors_out)
}
pub(crate) fn total_errors_on_transmitted(&self) -> u64 {
self.errors_out
}
pub(crate) fn mac_address(&self) -> MacAddr {
self.mac_addr
}
pub(crate) fn ip_networks(&self) -> &[IpNetwork] {
&self.ip_networks
}
pub(crate) fn mtu(&self) -> u64 {
self.mtu
}
}

110
vendor/sysinfo/src/unix/apple/process.rs vendored Normal file
View File

@@ -0,0 +1,110 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use std::fmt;
pub(crate) use crate::sys::inner::process::*;
use crate::ProcessStatus;
#[doc(hidden)]
impl From<u32> for ProcessStatus {
fn from(status: u32) -> ProcessStatus {
match status {
libc::SIDL => ProcessStatus::Idle,
libc::SRUN => ProcessStatus::Run,
libc::SSLEEP => ProcessStatus::Sleep,
libc::SSTOP => ProcessStatus::Stop,
libc::SZOMB => ProcessStatus::Zombie,
x => ProcessStatus::Unknown(x),
}
}
}
#[doc(hidden)]
impl From<ThreadStatus> for ProcessStatus {
fn from(status: ThreadStatus) -> ProcessStatus {
match status {
ThreadStatus::Running => ProcessStatus::Run,
ThreadStatus::Stopped => ProcessStatus::Stop,
ThreadStatus::Waiting => ProcessStatus::Sleep,
ThreadStatus::Uninterruptible => ProcessStatus::Dead,
ThreadStatus::Halted => ProcessStatus::Parked,
ThreadStatus::Unknown(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",
_ => "Unknown",
})
}
}
/// Enum describing the different status of a thread.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ThreadStatus {
/// Thread is running normally.
Running,
/// Thread is stopped.
Stopped,
/// Thread is waiting normally.
Waiting,
/// Thread is in an uninterruptible wait
Uninterruptible,
/// Thread is halted at a clean point.
Halted,
/// Unknown.
Unknown(i32),
}
impl From<i32> for ThreadStatus {
fn from(status: i32) -> ThreadStatus {
match status {
libc::TH_STATE_RUNNING => ThreadStatus::Running,
libc::TH_STATE_STOPPED => ThreadStatus::Stopped,
libc::TH_STATE_WAITING => ThreadStatus::Waiting,
libc::TH_STATE_UNINTERRUPTIBLE => ThreadStatus::Uninterruptible,
libc::TH_STATE_HALTED => ThreadStatus::Halted,
x => ThreadStatus::Unknown(x),
}
}
}
impl ProcessInner {
pub(crate) fn open_files(&self) -> Option<u32> {
let buffer_size_bytes = unsafe {
libc::proc_pidinfo(
self.pid().0,
libc::PROC_PIDLISTFDS,
0,
std::ptr::null_mut(),
0,
)
};
if buffer_size_bytes < 0 {
sysinfo_debug!("proc_pidinfo failed");
None
} else {
Some(buffer_size_bytes as u32 / std::mem::size_of::<libc::proc_fdinfo>() as u32)
}
}
// FIXME: Should query the value, because it can be changed with setrlimit and other means.
// For now, cannot find where to get this information sadly...
#[cfg(target_os = "ios")]
pub(crate) fn open_files_limit(&self) -> Option<u32> {
Some(256)
}
#[cfg(not(target_os = "ios"))]
pub(crate) fn open_files_limit(&self) -> Option<u32> {
Some(10_240)
}
}

578
vendor/sysinfo/src/unix/apple/system.rs vendored Normal file
View File

@@ -0,0 +1,578 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::sys::cpu::*;
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
use crate::sys::process::*;
use crate::sys::utils::{get_sys_value, get_sys_value_by_name};
use crate::{
Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind,
ProcessesToUpdate,
};
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::ffi::CStr;
use std::mem;
use std::time::Duration;
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
use std::time::SystemTime;
use libc::{
c_int, c_void, host_statistics64, mach_port_t, sysconf, sysctl, timeval, vm_statistics64,
_SC_PAGESIZE,
};
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
declare_signals! {
libc::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,
// SIGPOLL doesn't exist on apple targets but since it's an equivalent of SIGIO on unix,
// we simply use the SIGIO constant.
Signal::Poll => libc::SIGIO,
Signal::Sys => libc::SIGSYS,
_ => None,
}
#[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
declare_signals! {
libc::c_int,
_ => None,
}
#[doc = include_str!("../../../md_doc/supported_signals.md")]
pub const SUPPORTED_SIGNALS: &[crate::Signal] = supported_signals();
#[doc = include_str!("../../../md_doc/minimum_cpu_update_interval.md")]
pub const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(200);
pub(crate) struct SystemInner {
process_list: HashMap<Pid, Process>,
mem_total: u64,
mem_free: u64,
mem_used: u64,
mem_available: u64,
swap_total: u64,
swap_free: u64,
page_size_b: u64,
port: mach_port_t,
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
clock_info: Option<crate::sys::macos::system::SystemTimeInfo>,
cpus: CpusWrapper,
}
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
pub(crate) struct Wrap<'a>(pub UnsafeCell<&'a mut HashMap<Pid, Process>>);
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
unsafe impl Send for Wrap<'_> {}
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
unsafe impl Sync for Wrap<'_> {}
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 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 _
}
}
}
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
fn get_now() -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|n| n.as_secs())
.unwrap_or(0)
}
impl SystemInner {
pub(crate) fn new() -> Self {
unsafe {
#[allow(deprecated)]
let port = libc::mach_host_self();
Self {
process_list: HashMap::with_capacity(200),
mem_total: 0,
mem_free: 0,
mem_available: 0,
mem_used: 0,
swap_total: 0,
swap_free: 0,
page_size_b: sysconf(_SC_PAGESIZE) as _,
port,
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
clock_info: crate::sys::macos::system::SystemTimeInfo::new(port),
cpus: CpusWrapper::new(),
}
}
}
pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) {
let mut mib = [libc::CTL_VM as _, libc::VM_SWAPUSAGE as _];
unsafe {
if refresh_kind.swap() {
// get system values
// get swap info
let mut xs: libc::xsw_usage = mem::zeroed::<libc::xsw_usage>();
if get_sys_value(
mem::size_of::<libc::xsw_usage>(),
&mut xs as *mut _ as *mut c_void,
&mut mib,
) {
self.swap_total = xs.xsu_total;
self.swap_free = xs.xsu_avail;
}
}
if refresh_kind.ram() {
mib[0] = libc::CTL_HW as _;
mib[1] = libc::HW_MEMSIZE as _;
// get ram info
if self.mem_total < 1 {
get_sys_value(
mem::size_of::<u64>(),
&mut self.mem_total as *mut u64 as *mut c_void,
&mut mib,
);
}
let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _;
let mut stat = mem::zeroed::<vm_statistics64>();
if host_statistics64(
self.port,
libc::HOST_VM_INFO64,
&mut stat as *mut vm_statistics64 as *mut _,
&mut count,
) == libc::KERN_SUCCESS
{
// From the apple documentation:
//
// /*
// * NB: speculative pages are already accounted for in "free_count",
// * so "speculative_count" is the number of "free" pages that are
// * used to hold data that was read speculatively from disk but
// * haven't actually been used by anyone so far.
// */
self.mem_available = u64::from(stat.free_count)
.saturating_add(u64::from(stat.inactive_count))
.saturating_add(u64::from(stat.purgeable_count))
.saturating_sub(u64::from(stat.compressor_page_count))
.saturating_mul(self.page_size_b);
self.mem_used = u64::from(stat.active_count)
.saturating_add(u64::from(stat.wire_count))
.saturating_add(u64::from(stat.compressor_page_count))
.saturating_add(u64::from(stat.speculative_count))
.saturating_mul(self.page_size_b);
self.mem_free = u64::from(stat.free_count)
.saturating_sub(u64::from(stat.speculative_count))
.saturating_mul(self.page_size_b);
}
}
}
}
pub(crate) fn cgroup_limits(&self) -> Option<crate::CGroupLimits> {
None
}
pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) {
self.cpus.refresh(refresh_kind, self.port);
}
pub(crate) fn refresh_cpu_list(&mut self, refresh_kind: CpuRefreshKind) {
self.cpus = CpusWrapper::new();
self.cpus.refresh(refresh_kind, self.port);
}
#[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
pub(crate) fn refresh_processes_specifics(
&mut self,
_processes_to_update: ProcessesToUpdate<'_>,
_refresh_kind: ProcessRefreshKind,
) -> usize {
0
}
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
pub(crate) fn refresh_processes_specifics(
&mut self,
processes_to_update: ProcessesToUpdate<'_>,
refresh_kind: ProcessRefreshKind,
) -> usize {
use crate::utils::into_iter;
use std::sync::atomic::{AtomicUsize, Ordering};
unsafe {
let count = libc::proc_listallpids(::std::ptr::null_mut(), 0);
if count < 1 {
return 0;
}
}
if let Some(pids) = get_proc_list() {
#[inline(always)]
fn real_filter(e: Pid, filter: &[Pid]) -> bool {
filter.contains(&e)
}
#[inline(always)]
fn empty_filter(_e: Pid, _filter: &[Pid]) -> bool {
true
}
#[allow(clippy::type_complexity)]
let (filter, filter_callback): (
&[Pid],
&(dyn Fn(Pid, &[Pid]) -> bool + Sync + Send),
) = match processes_to_update {
ProcessesToUpdate::All => (&[], &empty_filter),
ProcessesToUpdate::Some(pids_to_refresh) => {
if pids_to_refresh.is_empty() {
return 0;
}
(pids_to_refresh, &real_filter)
}
};
let nb_updated = AtomicUsize::new(0);
let now = get_now();
let port = self.port;
let time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port));
let timebase_to_ms = self
.clock_info
.as_ref()
.map(|c| c.timebase_to_ms)
.unwrap_or_default();
let entries: Vec<Process> = {
let wrap = &Wrap(UnsafeCell::new(&mut self.process_list));
#[cfg(feature = "multithread")]
use rayon::iter::ParallelIterator;
into_iter(pids)
.flat_map(|pid| {
if !filter_callback(pid, filter) {
return None;
}
nb_updated.fetch_add(1, Ordering::Relaxed);
update_process(
wrap,
pid,
time_interval,
now,
refresh_kind,
false,
timebase_to_ms,
)
.unwrap_or_default()
})
.collect()
};
entries.into_iter().for_each(|entry| {
self.process_list.insert(entry.pid(), entry);
});
nb_updated.into_inner()
} else {
0
}
}
// COMMON PART
//
// Need to be moved into a "common" file to avoid duplication.
pub(crate) fn processes(&self) -> &HashMap<Pid, Process> {
&self.process_list
}
pub(crate) fn processes_mut(&mut self) -> &mut HashMap<Pid, Process> {
&mut self.process_list
}
pub(crate) fn process(&self, pid: Pid) -> Option<&Process> {
self.process_list.get(&pid)
}
pub(crate) fn global_cpu_usage(&self) -> f32 {
self.cpus.global_cpu.percent()
}
pub(crate) fn cpus(&self) -> &[Cpu] {
&self.cpus.cpus
}
pub(crate) fn total_memory(&self) -> u64 {
self.mem_total
}
pub(crate) fn free_memory(&self) -> u64 {
self.mem_free
}
pub(crate) fn available_memory(&self) -> u64 {
self.mem_available
}
pub(crate) fn used_memory(&self) -> u64 {
self.mem_used
}
pub(crate) fn total_swap(&self) -> u64 {
self.swap_total
}
pub(crate) fn free_swap(&self) -> u64 {
self.swap_free
}
// TODO: need to be checked
pub(crate) fn used_swap(&self) -> u64 {
self.swap_total - self.swap_free
}
pub(crate) fn uptime() -> u64 {
unsafe {
let csec = libc::time(::std::ptr::null_mut());
libc::difftime(csec, Self::boot_time() as _) as _
}
}
pub(crate) fn load_average() -> 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],
}
}
}
pub(crate) fn boot_time() -> u64 {
boot_time()
}
pub(crate) fn name() -> Option<String> {
get_system_info(libc::KERN_OSTYPE, Some("Darwin"))
}
pub(crate) fn long_os_version() -> Option<String> {
#[cfg(target_os = "macos")]
{
let Some(os_version) = Self::os_version() else {
return Some("macOS".to_owned());
};
// https://en.wikipedia.org/wiki/MacOS_version_history
for (version_prefix, macos_spelling, friendly_name) in [
("15", "macOS", "Sequoia"),
("14", "macOS", "Sonoma"),
("13", "macOS", "Ventura"),
("12", "macOS", "Monterey"),
("11", "macOS", "Big Sur"),
// Big Sur identifies itself as 10.16 in some situations.
// https://en.wikipedia.org/wiki/MacOS_Big_Sur#Development_history
("10.16", "macOS", "Big Sur"),
("10.15", "macOS", "Catalina"),
("10.14", "macOS", "Mojave"),
("10.13", "macOS", "High Sierra"),
("10.12", "macOS", "Sierra"),
("10.11", "OS X", "El Capitan"),
("10.10", "OS X", "Yosemite"),
("10.9", "OS X", "Mavericks"),
("10.8", "OS X", "Mountain Lion"),
("10.7", "Mac OS X", "Lion"),
("10.6", "Mac OS X", "Snow Leopard"),
("10.5", "Mac OS X", "Leopard"),
("10.4", "Mac OS X", "Tiger"),
("10.3", "Mac OS X", "Panther"),
("10.2", "Mac OS X", "Jaguar"),
("10.1", "Mac OS X", "Puma"),
("10.0", "Mac OS X", "Cheetah"),
] {
if os_version.starts_with(version_prefix) {
return Some(format!("{macos_spelling} {os_version} {friendly_name}"));
}
}
Some(format!("macOS {os_version}"))
}
#[cfg(target_os = "ios")]
{
let mut long_name = "iOS".to_owned();
if let Some(os_version) = Self::os_version() {
long_name.push(' ');
long_name.push_str(&os_version);
}
Some(long_name)
}
}
pub(crate) fn host_name() -> Option<String> {
get_system_info(libc::KERN_HOSTNAME, None)
}
pub(crate) fn kernel_version() -> Option<String> {
get_system_info(libc::KERN_OSRELEASE, None)
}
pub(crate) fn os_version() -> Option<String> {
unsafe {
// get the size for the buffer first
let mut size = 0;
if get_sys_value_by_name(b"kern.osproductversion\0", &mut size, std::ptr::null_mut())
&& size > 0
{
// now create a buffer with the size and get the real value
let mut buf = vec![0_u8; size as _];
if get_sys_value_by_name(
b"kern.osproductversion\0",
&mut size,
buf.as_mut_ptr() as *mut c_void,
) {
if let Some(pos) = buf.iter().position(|x| *x == 0) {
// Shrink buffer to terminate the null bytes
buf.resize(pos, 0);
}
String::from_utf8(buf).ok()
} else {
// getting the system value failed
None
}
} else {
// getting the system value failed, or did not return a buffer size
None
}
}
}
pub(crate) fn distribution_id() -> String {
std::env::consts::OS.to_owned()
}
pub(crate) fn distribution_id_like() -> Vec<String> {
Vec::new()
}
pub(crate) fn kernel_name() -> Option<&'static str> {
Some("Darwin")
}
pub(crate) fn cpu_arch() -> Option<String> {
let mut arch_str: [u8; 32] = [0; 32];
let mut mib = [libc::CTL_HW as _, libc::HW_MACHINE as _];
unsafe {
if get_sys_value(
mem::size_of::<[u8; 32]>(),
arch_str.as_mut_ptr() as *mut _,
&mut mib,
) {
CStr::from_bytes_until_nul(&arch_str)
.ok()
.and_then(|res| match res.to_str() {
Ok(arch) => Some(arch.to_string()),
Err(_) => None,
})
} else {
None
}
}
}
pub(crate) fn physical_core_count() -> Option<usize> {
physical_core_count()
}
}
fn get_system_info(value: c_int, default: Option<&str>) -> Option<String> {
let mut mib: [c_int; 2] = [libc::CTL_KERN, value];
let mut size = 0;
unsafe {
// Call first to get size
sysctl(
mib.as_mut_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![0_u8; size as _];
if sysctl(
mib.as_mut_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 {
if let Some(pos) = buf.iter().position(|x| *x == 0) {
// Shrink buffer to terminate the null bytes
buf.resize(pos, 0);
}
String::from_utf8(buf).ok()
}
}
}
}

151
vendor/sysinfo/src/unix/apple/users.rs vendored Normal file
View File

@@ -0,0 +1,151 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{
common::{Gid, Uid},
User, UserInner,
};
use libc::{c_char, endpwent, getpwent, setpwent, strlen};
use std::collections::HashMap;
fn endswith(s1: *const c_char, s2: &[u8]) -> bool {
if s1.is_null() {
return false;
}
unsafe {
let mut len = strlen(s1) as isize - 1;
let mut i = s2.len() as isize - 1;
while len >= 0 && i >= 0 && *s1.offset(len) == s2[i as usize] as _ {
i -= 1;
len -= 1;
}
i == -1
}
}
pub(crate) fn get_users(users: &mut Vec<User>) {
fn filter(shell: *const c_char, uid: u32) -> bool {
!endswith(shell, b"/false") && !endswith(shell, b"/uucico") && uid < 65536
}
users.clear();
let mut users_map = HashMap::with_capacity(10);
unsafe {
setpwent();
loop {
let pw = getpwent();
if pw.is_null() {
// The call was interrupted by a signal, retrying.
if std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted {
continue;
}
break;
}
if !filter((*pw).pw_shell, (*pw).pw_uid) {
// This is not a "real" or "local" user.
continue;
}
if let Some(name) = crate::unix::utils::cstr_to_rust((*pw).pw_name) {
if users_map.contains_key(&name) {
continue;
}
let uid = (*pw).pw_uid;
let gid = (*pw).pw_gid;
users_map.insert(name, (Uid(uid), Gid(gid)));
}
}
endpwent();
}
for (name, (uid, gid)) in users_map {
users.push(User {
inner: UserInner::new(uid, gid, name),
});
}
}
// This was the OSX-based solution. It provides enough information, but what a mess!
// pub fn get_users_list() -> Vec<User> {
// let mut users = Vec::new();
// let node_name = b"/Local/Default\0";
// unsafe {
// let node_name = ffi::CFStringCreateWithCStringNoCopy(
// std::ptr::null_mut(),
// node_name.as_ptr() as *const c_char,
// ffi::kCFStringEncodingMacRoman,
// ffi::kCFAllocatorNull as *mut c_void,
// );
// let node_ref = ffi::ODNodeCreateWithName(
// ffi::kCFAllocatorDefault,
// ffi::kODSessionDefault,
// node_name,
// std::ptr::null_mut(),
// );
// let query = ffi::ODQueryCreateWithNode(
// ffi::kCFAllocatorDefault,
// node_ref,
// ffi::kODRecordTypeUsers as _, // kODRecordTypeGroups
// std::ptr::null(),
// 0,
// std::ptr::null(),
// std::ptr::null(),
// 0,
// std::ptr::null_mut(),
// );
// if query.is_null() {
// return users;
// }
// let results = ffi::ODQueryCopyResults(
// query,
// false as _,
// std::ptr::null_mut(),
// );
// let len = ffi::CFArrayGetCount(results);
// for i in 0..len {
// let name = match get_user_name(ffi::CFArrayGetValueAtIndex(results, i)) {
// Some(n) => n,
// None => continue,
// };
// users.push(User { name });
// }
// ffi::CFRelease(results as *const c_void);
// ffi::CFRelease(query as *const c_void);
// ffi::CFRelease(node_ref as *const c_void);
// ffi::CFRelease(node_name as *const c_void);
// }
// users.sort_unstable_by(|x, y| x.name.partial_cmp(&y.name).unwrap());
// return users;
// }
// fn get_user_name(result: *const c_void) -> Option<String> {
// let user_name = ffi::ODRecordGetRecordName(result as _);
// let ptr = ffi::CFStringGetCharactersPtr(user_name);
// String::from_utf16(&if ptr.is_null() {
// let len = ffi::CFStringGetLength(user_name); // It returns the len in UTF-16 code pairs.
// if len == 0 {
// continue;
// }
// let mut v = Vec::with_capacity(len as _);
// for x in 0..len {
// v.push(ffi::CFStringGetCharacterAtIndex(user_name, x));
// }
// v
// } else {
// let mut v: Vec<u16> = Vec::new();
// let mut x = 0;
// loop {
// let letter = *ptr.offset(x);
// if letter == 0 {
// break;
// }
// v.push(letter);
// x += 1;
// }
// v
// }.ok()
// }

32
vendor/sysinfo/src/unix/apple/utils.rs vendored Normal file
View File

@@ -0,0 +1,32 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[cfg(feature = "system")]
pub(crate) unsafe fn get_sys_value(
mut len: usize,
value: *mut libc::c_void,
mib: &mut [i32],
) -> bool {
libc::sysctl(
mib.as_mut_ptr(),
mib.len() as _,
value,
&mut len as *mut _,
std::ptr::null_mut(),
0,
) == 0
}
#[cfg(feature = "system")]
pub(crate) unsafe fn get_sys_value_by_name(
name: &[u8],
len: &mut usize,
value: *mut libc::c_void,
) -> bool {
libc::sysctlbyname(
name.as_ptr() as *const _,
value,
len,
std::ptr::null_mut(),
0,
) == 0
}

View File

@@ -0,0 +1,111 @@
// 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::Component;
pub(crate) struct ComponentInner {
id: Vec<u8>,
label: String,
temperature: Option<f32>,
max: f32,
pub(crate) updated: bool,
}
impl ComponentInner {
pub(crate) fn temperature(&self) -> Option<f32> {
self.temperature
}
pub(crate) fn max(&self) -> Option<f32> {
Some(self.max)
}
pub(crate) fn critical(&self) -> Option<f32> {
None
}
pub(crate) fn label(&self) -> &str {
&self.label
}
pub(crate) fn refresh(&mut self) {
unsafe {
self.temperature = refresh_component(&self.id);
if let Some(temperature) = self.temperature {
if temperature > self.max {
self.max = 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(crate) struct ComponentsInner {
nb_cpus: usize,
pub(crate) components: Vec<Component>,
}
impl ComponentsInner {
pub(crate) fn new() -> Self {
let nb_cpus = unsafe { super::utils::get_nb_cpus() };
Self {
nb_cpus,
components: Vec::with_capacity(nb_cpus),
}
}
pub(crate) fn from_vec(components: Vec<Component>) -> Self {
Self {
nb_cpus: unsafe { super::utils::get_nb_cpus() },
components,
}
}
pub(crate) fn into_vec(self) -> Vec<Component> {
self.components
}
pub(crate) fn list(&self) -> &[Component] {
&self.components
}
pub(crate) fn list_mut(&mut self) -> &mut [Component] {
&mut self.components
}
pub(crate) fn refresh(&mut self) {
if self.components.len() != self.nb_cpus {
for core in 0..self.nb_cpus {
unsafe {
let id = format!("dev.cpu.{core}.temperature\0").as_bytes().to_vec();
if let Some(temperature) = refresh_component(&id) {
self.components.push(Component {
inner: ComponentInner {
id,
label: format!("CPU {}", core + 1),
temperature: Some(temperature),
max: temperature,
updated: true,
},
});
}
}
}
} else {
for c in self.components.iter_mut() {
c.refresh();
c.inner.updated = true;
}
}
}
}

225
vendor/sysinfo/src/unix/freebsd/cpu.rs vendored Normal file
View File

@@ -0,0 +1,225 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::sys::utils::{
get_sys_value_array, get_sys_value_by_name, get_sys_value_str_by_name, init_mib,
};
use crate::{Cpu, CpuRefreshKind};
use libc::{c_int, c_ulong};
pub(crate) struct CpusWrapper {
pub(crate) global_cpu_usage: f32,
pub(crate) cpus: Vec<Cpu>,
got_cpu_frequency: bool,
mib_cp_time: [c_int; 2],
mib_cp_times: [c_int; 2],
// For the global CPU usage.
cp_time: VecSwitcher<c_ulong>,
// For each CPU usage.
cp_times: VecSwitcher<c_ulong>,
nb_cpus: usize,
}
impl CpusWrapper {
pub(crate) fn new() -> Self {
let mut mib_cp_time = [0; 2];
let mut mib_cp_times = [0; 2];
unsafe {
let nb_cpus = super::utils::get_nb_cpus();
init_mib(b"kern.cp_time\0", &mut mib_cp_time);
init_mib(b"kern.cp_times\0", &mut mib_cp_times);
Self {
global_cpu_usage: 0.,
cpus: Vec::with_capacity(nb_cpus),
got_cpu_frequency: false,
mib_cp_time,
mib_cp_times,
cp_time: VecSwitcher::new(vec![0; libc::CPUSTATES as usize]),
cp_times: VecSwitcher::new(vec![0; nb_cpus * libc::CPUSTATES as usize]),
nb_cpus,
}
}
}
pub(crate) fn refresh(&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.nb_cpus {
if refresh_kind.frequency() {
unsafe {
frequency = get_frequency_for_cpu(pos);
}
}
self.cpus.push(Cpu {
inner: CpuInner::new(format!("cpu {pos}"), vendor_id.clone(), frequency),
});
}
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_.inner.frequency = get_frequency_for_cpu(pos);
}
}
self.got_cpu_frequency = true;
}
if refresh_kind.cpu_usage() {
self.get_cpu_usage();
}
}
fn get_cpu_usage(&mut self) {
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 compute_cpu_usage(new_cp_time: &[c_ulong], old_cp_time: &[c_ulong]) -> f32 {
let mut total_new: u64 = 0;
let mut total_old: u64 = 0;
let mut cp_diff: 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 = cp_diff.saturating_add(new_cp_time[i].saturating_sub(old_cp_time[i]));
}
total_new = total_new.saturating_add(new_cp_time[i] as _);
total_old = total_old.saturating_add(old_cp_time[i] as _);
}
let total_diff = total_new.saturating_sub(total_old);
if total_diff < 1 {
0.
} else {
cp_diff as f32 / total_diff as f32 * 100.
}
}
self.global_cpu_usage = compute_cpu_usage(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, cpu) in self.cpus.iter_mut().enumerate() {
let index = pos * libc::CPUSTATES as usize;
cpu.inner.cpu_usage = compute_cpu_usage(&new_cp_times[index..], &old_cp_times[index..]);
}
}
}
pub(crate) struct CpuInner {
pub(crate) cpu_usage: f32,
name: String,
pub(crate) vendor_id: String,
pub(crate) frequency: u64,
}
impl CpuInner {
pub(crate) fn new(name: String, vendor_id: String, frequency: u64) -> Self {
Self {
cpu_usage: 0.,
name,
vendor_id,
frequency,
}
}
pub(crate) fn cpu_usage(&self) -> f32 {
self.cpu_usage
}
pub(crate) fn name(&self) -> &str {
&self.name
}
pub(crate) fn frequency(&self) -> u64 {
self.frequency
}
pub(crate) fn vendor_id(&self) -> &str {
&self.vendor_id
}
pub(crate) fn brand(&self) -> &str {
""
}
}
pub(crate) fn physical_core_count() -> 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
}
}
}
unsafe fn get_frequency_for_cpu(cpu_nb: usize) -> u64 {
let mut frequency: c_int = 0;
// The information can be missing if it's running inside a VM.
if !get_sys_value_by_name(
format!("dev.cpu.{cpu_nb}.freq\0").as_bytes(),
&mut frequency,
) {
frequency = 0;
}
frequency as _
}
/// 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
}
}
}

533
vendor/sysinfo/src/unix/freebsd/disk.rs vendored Normal file
View File

@@ -0,0 +1,533 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
use std::marker::PhantomData;
use std::os::unix::ffi::OsStringExt;
use std::path::{Path, PathBuf};
use std::ptr::{null_mut, NonNull};
use std::sync::OnceLock;
use libc::{c_void, devstat, devstat_getversion};
use super::ffi::{
geom_stats_open, geom_stats_snapshot_free, geom_stats_snapshot_get, geom_stats_snapshot_next,
geom_stats_snapshot_reset, DEVSTAT_READ, DEVSTAT_WRITE,
};
use super::utils::{c_buf_to_utf8_str, get_sys_value_str_by_name};
use crate::{Disk, DiskKind, DiskRefreshKind, DiskUsage};
#[derive(Debug)]
pub(crate) struct DiskInner {
name: OsString,
c_mount_point: Vec<libc::c_char>,
dev_id: Option<String>,
mount_point: PathBuf,
total_space: u64,
available_space: u64,
file_system: OsString,
is_removable: bool,
is_read_only: bool,
read_bytes: u64,
old_read_bytes: u64,
written_bytes: u64,
old_written_bytes: u64,
updated: bool,
}
impl DiskInner {
pub(crate) fn kind(&self) -> DiskKind {
// Currently don't know how to retrieve this information on FreeBSD.
DiskKind::Unknown(-1)
}
pub(crate) fn name(&self) -> &OsStr {
&self.name
}
pub(crate) fn file_system(&self) -> &OsStr {
&self.file_system
}
pub(crate) fn mount_point(&self) -> &Path {
&self.mount_point
}
pub(crate) fn total_space(&self) -> u64 {
self.total_space
}
pub(crate) fn available_space(&self) -> u64 {
self.available_space
}
pub(crate) fn is_removable(&self) -> bool {
self.is_removable
}
pub(crate) fn is_read_only(&self) -> bool {
self.is_read_only
}
pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) -> bool {
refresh_disk(self, refresh_kind)
}
pub(crate) fn usage(&self) -> DiskUsage {
DiskUsage {
read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes),
total_read_bytes: self.read_bytes,
written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
total_written_bytes: self.written_bytes,
}
}
}
impl crate::DisksInner {
pub(crate) fn new() -> Self {
Self {
disks: Vec::with_capacity(2),
}
}
pub(crate) fn refresh_specifics(
&mut self,
remove_not_listed_disks: bool,
refresh_kind: DiskRefreshKind,
) {
unsafe { get_all_list(&mut self.disks, remove_not_listed_disks, refresh_kind) }
}
pub(crate) fn list(&self) -> &[Disk] {
&self.disks
}
pub(crate) fn list_mut(&mut self) -> &mut [Disk] {
&mut self.disks
}
}
trait GetValues {
fn update_old(&mut self);
fn get_read(&mut self) -> &mut u64;
fn get_written(&mut self) -> &mut u64;
fn dev_id(&self) -> Option<&String>;
}
impl GetValues for crate::Disk {
fn update_old(&mut self) {
self.inner.update_old()
}
fn get_read(&mut self) -> &mut u64 {
self.inner.get_read()
}
fn get_written(&mut self) -> &mut u64 {
self.inner.get_written()
}
fn dev_id(&self) -> Option<&String> {
self.inner.dev_id()
}
}
impl GetValues for &mut DiskInner {
fn update_old(&mut self) {
self.old_read_bytes = self.read_bytes;
self.old_written_bytes = self.written_bytes;
}
fn get_read(&mut self) -> &mut u64 {
&mut self.read_bytes
}
fn get_written(&mut self) -> &mut u64 {
&mut self.written_bytes
}
fn dev_id(&self) -> Option<&String> {
self.dev_id.as_ref()
}
}
impl GetValues for DiskInner {
fn update_old(&mut self) {
self.old_read_bytes = self.read_bytes;
self.old_written_bytes = self.written_bytes;
}
fn get_read(&mut self) -> &mut u64 {
&mut self.read_bytes
}
fn get_written(&mut self) -> &mut u64 {
&mut self.written_bytes
}
fn dev_id(&self) -> Option<&String> {
self.dev_id.as_ref()
}
}
/// Returns `(total_space, available_space, is_read_only)`.
unsafe fn get_statvfs(
c_mount_point: &[libc::c_char],
vfs: &mut libc::statvfs,
) -> Option<(u64, u64, bool)> {
if libc::statvfs(c_mount_point.as_ptr() as *const _, vfs as *mut _) < 0 {
sysinfo_debug!("statvfs failed");
None
} else {
let block_size: u64 = vfs.f_frsize as _;
Some((
vfs.f_blocks.saturating_mul(block_size),
vfs.f_favail.saturating_mul(block_size),
(vfs.f_flag & libc::ST_RDONLY) != 0,
))
}
}
fn refresh_disk(disk: &mut DiskInner, refresh_kind: DiskRefreshKind) -> bool {
if refresh_kind.storage() {
unsafe {
let mut vfs: libc::statvfs = std::mem::zeroed();
if let Some((total_space, available_space, is_read_only)) =
get_statvfs(&disk.c_mount_point, &mut vfs)
{
disk.total_space = total_space;
disk.available_space = available_space;
disk.is_read_only = is_read_only;
}
}
}
if refresh_kind.io_usage() {
unsafe {
refresh_disk_io(&mut [disk]);
}
}
true
}
unsafe fn initialize_geom() -> Result<(), ()> {
let version = devstat_getversion(null_mut());
if version != 6 {
// For now we only handle the devstat 6 version.
sysinfo_debug!("version {version} of devstat is not supported");
return Err(());
}
let r = unsafe { geom_stats_open() };
if r != 0 {
sysinfo_debug!("`geom_stats_open` failed: {r}");
Err(())
} else {
Ok(())
}
}
unsafe fn refresh_disk_io<T: GetValues>(disks: &mut [T]) {
static GEOM_STATS: OnceLock<Result<(), ()>> = OnceLock::new();
if GEOM_STATS
.get_or_init(|| unsafe { initialize_geom() })
.is_err()
{
return;
}
let Some(mut snap) = GeomSnapshot::new() else {
return;
};
for device in snap.iter() {
let device = device.devstat.as_ref();
let Some(device_name) = c_buf_to_utf8_str(&device.device_name) else {
continue;
};
let dev_stat_name = format!("{device_name}{}", device.unit_number);
for disk in disks
.iter_mut()
.filter(|d| d.dev_id().is_some_and(|id| *id == dev_stat_name))
{
disk.update_old();
*disk.get_read() = device.bytes[DEVSTAT_READ];
*disk.get_written() = device.bytes[DEVSTAT_WRITE];
}
}
// thread_local! {
// static DEV_INFO: RefCell<DevInfoWrapper> = RefCell::new(DevInfoWrapper::new());
// }
// DEV_INFO.with_borrow_mut(|dev_info| {
// let Some(stat_info) = dev_info.get_devs() else { return };
// let dinfo = (*stat_info).dinfo;
// let numdevs = (*dinfo).numdevs;
// if numdevs < 0 {
// return;
// }
// let devices: &mut [devstat] = std::slice::from_raw_parts_mut((*dinfo).devices, numdevs as _);
// for device in devices {
// let Some(device_name) = c_buf_to_utf8_str(&device.device_name) else { continue };
// let dev_stat_name = format!("{device_name}{}", device.unit_number);
// for disk in disks.iter_mut().filter(|d| d.dev_id().is_some_and(|id| *id == dev_stat_name)) {
// disk.update_old();
// let mut read = 0u64;
// // This code cannot work because `devstat_compute_statistics` expects a
// // `long double` as 3rd argument, making it impossible for rust to call it...
// devstat_compute_statistics(
// device,
// null_mut(),
// 0,
// DSM_TOTAL_BYTES_READ,
// &mut read,
// DSM_TOTAL_BYTES_WRITE,
// disk.get_written(),
// DSM_NONE,
// );
// *disk.get_read() = read;
// }
// }
// });
}
fn get_disks_mapping() -> HashMap<String, String> {
let mut disk_mapping = HashMap::new();
let Some(mapping) = get_sys_value_str_by_name(b"kern.geom.conftxt\0") else {
return disk_mapping;
};
let mut last_id = String::new();
for line in mapping.lines() {
let mut parts = line.split_whitespace();
let Some(kind) = parts.next() else { continue };
if kind == "0" {
if let Some("DISK") = parts.next() {
if let Some(id) = parts.next() {
last_id.clear();
last_id.push_str(id);
}
}
} else if kind == "2" && !last_id.is_empty() {
if let Some("LABEL") = parts.next() {
if let Some(path) = parts.next() {
disk_mapping.insert(format!("/dev/{path}"), last_id.clone());
}
}
}
}
disk_mapping
}
pub unsafe fn get_all_list(
container: &mut Vec<Disk>,
remove_not_listed_disks: bool,
refresh_kind: DiskRefreshKind,
) {
let mut fs_infos: *mut libc::statfs = null_mut();
let count = libc::getmntinfo(&mut fs_infos, libc::MNT_WAIT);
if count < 1 {
return;
}
let disk_mapping = get_disks_mapping();
let fs_infos: &[libc::statfs] = std::slice::from_raw_parts(fs_infos as _, count as _);
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: Vec<u8> = {
let len = fs_info
.f_fstypename
.iter()
.position(|x| *x == 0)
.unwrap_or(fs_info.f_fstypename.len());
fs_info.f_fstypename[..len]
.iter()
.map(|c| *c as u8)
.collect()
};
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_utf8_str(&fs_info.f_fstypename).unwrap(),
);
continue;
}
_ => {}
}
let mount_point = match c_buf_to_utf8_str(&fs_info.f_mntonname) {
Some(m) => m,
None => {
sysinfo_debug!("Cannot get disk mount point, ignoring it.");
continue;
}
};
if mount_point == "/boot/efi" {
continue;
}
let name = if mount_point == "/" {
OsString::from("root")
} else {
OsString::from(mount_point)
};
if let Some(disk) = container.iter_mut().find(|d| {
d.inner.name == name
&& d.inner
.file_system
.as_encoded_bytes()
.iter()
.zip(fs_type.iter())
.all(|(a, b)| a == b)
}) {
// I/O usage is updated for all disks at once at the end.
refresh_disk(&mut disk.inner, refresh_kind.without_io_usage());
disk.inner.updated = true;
} else {
let dev_mount_point = c_buf_to_utf8_str(&fs_info.f_mntfromname).unwrap_or("");
// USB keys and CDs are removable.
let is_removable = if refresh_kind.storage() {
[b"USB", b"usb"].iter().any(|b| *b == &fs_type[..])
|| fs_type.starts_with(b"/dev/cd")
} else {
false
};
let mut disk = DiskInner {
name,
c_mount_point: fs_info.f_mntonname.to_vec(),
mount_point: PathBuf::from(mount_point),
dev_id: disk_mapping.get(dev_mount_point).map(ToString::to_string),
total_space: 0,
available_space: 0,
file_system: OsString::from_vec(fs_type),
is_removable,
is_read_only: false,
read_bytes: 0,
old_read_bytes: 0,
written_bytes: 0,
old_written_bytes: 0,
updated: true,
};
// I/O usage is updated for all disks at once at the end.
refresh_disk(&mut disk, refresh_kind.without_io_usage());
container.push(Disk { inner: disk });
}
}
if remove_not_listed_disks {
container.retain_mut(|disk| {
if !disk.inner.updated {
return false;
}
disk.inner.updated = false;
true
});
} else {
for c in container.iter_mut() {
c.inner.updated = false;
}
}
if refresh_kind.io_usage() {
refresh_disk_io(container.as_mut_slice());
}
}
// struct DevInfoWrapper {
// info: statinfo,
// }
// impl DevInfoWrapper {
// fn new() -> Self {
// Self {
// info: unsafe { std::mem::zeroed() },
// }
// }
// unsafe fn get_devs(&mut self) -> Option<&statinfo> {
// let version = devstat_getversion(null_mut());
// if version != 6 {
// // For now we only handle the devstat 6 version.
// sysinfo_debug!("version {version} of devstat is not supported");
// return None;
// }
// if self.info.dinfo.is_null() {
// self.info.dinfo = libc::calloc(1, std::mem::size_of::<devinfo>()) as *mut _;
// if self.info.dinfo.is_null() {
// return None;
// }
// }
// if devstat_getdevs(null_mut(), &mut self.info as *mut _) != -1 {
// Some(&self.info)
// } else {
// None
// }
// }
// }
// impl Drop for DevInfoWrapper {
// fn drop(&mut self) {
// if !self.info.dinfo.is_null() {
// unsafe { libc::free(self.info.dinfo as *mut _); }
// }
// }
// }
// Most of this code was adapted from `gstat-rs` (https://github.com/asomers/gstat-rs).
struct GeomSnapshot(NonNull<c_void>);
impl GeomSnapshot {
unsafe fn new() -> Option<Self> {
match NonNull::new(geom_stats_snapshot_get()) {
Some(n) => Some(Self(n)),
None => {
sysinfo_debug!("geom_stats_snapshot_get failed");
None
}
}
}
fn iter(&mut self) -> GeomSnapshotIter {
GeomSnapshotIter(self)
}
fn reset(&mut self) {
unsafe { geom_stats_snapshot_reset(self.0.as_mut()) }
}
}
impl Drop for GeomSnapshot {
fn drop(&mut self) {
unsafe { geom_stats_snapshot_free(self.0.as_mut()) };
}
}
#[repr(transparent)]
struct Devstat<'a> {
devstat: NonNull<devstat>,
phantom: PhantomData<&'a devstat>,
}
struct GeomSnapshotIter<'a>(&'a mut GeomSnapshot);
impl<'a> Iterator for GeomSnapshotIter<'a> {
type Item = Devstat<'a>;
fn next(&mut self) -> Option<Self::Item> {
let raw = unsafe { geom_stats_snapshot_next(self.0 .0.as_mut()) };
NonNull::new(raw).map(|devstat| Devstat {
devstat,
phantom: PhantomData,
})
}
}
impl Drop for GeomSnapshotIter<'_> {
fn drop(&mut self) {
self.0.reset();
}
}

83
vendor/sysinfo/src/unix/freebsd/ffi.rs vendored Normal file
View File

@@ -0,0 +1,83 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#![allow(non_camel_case_types, dead_code)]
use libc::{c_char, c_int, c_uint, c_ulong, c_void, uintptr_t};
// definitions come from:
// https://github.com/freebsd/freebsd-src/blob/main/lib/libdevstat/devstat.h
// https://github.com/freebsd/freebsd-src/blob/main/sys/sys/devicestat.h
// #[repr(C)]
// pub(crate) struct statinfo {
// pub(crate) cp_time: [c_long; CPUSTATES as usize],
// pub(crate) tk_nin: c_long,
// pub(crate) tk_nout: c_long,
// pub(crate) dinfo: *mut devinfo,
// pub(crate) snap_time: c_long_double,
// }
// FIXME: can be removed once https://github.com/rust-lang/libc/pull/4327 is merged
#[repr(C)]
pub(crate) struct filedesc {
pub fd_files: *mut fdescenttbl,
pub fd_map: *mut c_ulong,
pub fd_freefile: c_int,
pub fd_refcnt: c_int,
pub fd_holdcnt: c_int,
fd_sx: sx,
fd_kqlist: kqlist,
pub fd_holdleaderscount: c_int,
pub fd_holdleaderswakeup: c_int,
}
// FIXME: can be removed once https://github.com/rust-lang/libc/pull/4327 is merged
#[repr(C)]
pub(crate) struct fdescenttbl {
pub fdt_nfiles: c_int,
fdt_ofiles: [*mut c_void; 0],
}
// FIXME: can be removed once https://github.com/rust-lang/libc/pull/4327 is merged
#[repr(C)]
pub(crate) struct sx {
lock_object: lock_object,
sx_lock: uintptr_t,
}
// FIXME: can be removed once https://github.com/rust-lang/libc/pull/4327 is merged
#[repr(C)]
pub(crate) struct lock_object {
lo_name: *const c_char,
lo_flags: c_uint,
lo_data: c_uint,
// This is normally `struct witness`.
lo_witness: *mut c_void,
}
// FIXME: can be removed once https://github.com/rust-lang/libc/pull/4327 is merged
#[repr(C)]
pub(crate) struct kqlist {
tqh_first: *mut c_void,
tqh_last: *mut *mut c_void,
}
pub(crate) const DEVSTAT_READ: usize = 0x01;
pub(crate) const DEVSTAT_WRITE: usize = 0x02;
// pub(crate) const DSM_NONE: c_int = 0;
// pub(crate) const DSM_TOTAL_BYTES_READ: c_int = 2;
// pub(crate) const DSM_TOTAL_BYTES_WRITE: c_int = 3;
extern "C" {
// pub(crate) fn devstat_compute_statistics(current: *mut devstat, previous: *mut devstat, etime: c_long_double, ...) -> c_int;
}
#[link(name = "geom")]
extern "C" {
pub(crate) fn geom_stats_open() -> c_int;
pub(crate) fn geom_stats_snapshot_get() -> *mut c_void;
pub(crate) fn geom_stats_snapshot_next(arg: *mut c_void) -> *mut libc::devstat;
pub(crate) fn geom_stats_snapshot_reset(arg: *mut c_void);
pub(crate) fn geom_stats_snapshot_free(arg: *mut c_void);
}

62
vendor/sysinfo/src/unix/freebsd/mod.rs vendored Normal file
View File

@@ -0,0 +1,62 @@
// Take a look at the license at the top of the repository in the LICENSE file.
pub(crate) mod utils;
cfg_if! {
if #[cfg(feature = "system")] {
pub mod cpu;
pub mod process;
pub mod system;
pub(crate) use self::cpu::CpuInner;
pub(crate) use self::process::ProcessInner;
pub(crate) use self::system::SystemInner;
pub use self::system::{MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS};
}
if #[cfg(feature = "disk")] {
pub mod disk;
pub(crate) use self::disk::DiskInner;
pub(crate) use crate::unix::DisksInner;
}
if #[cfg(any(feature = "disk", feature = "system"))] {
pub mod ffi;
}
if #[cfg(feature = "component")] {
pub mod component;
pub(crate) use self::component::{ComponentInner, ComponentsInner};
}
if #[cfg(feature = "network")] {
pub mod network;
pub(crate) use self::network::{NetworkDataInner, NetworksInner};
}
if #[cfg(feature = "user")] {
pub(crate) use crate::unix::groups::get_groups;
pub(crate) use crate::unix::users::{get_users, UserInner};
}
}
#[doc = include_str!("../../../md_doc/is_supported.md")]
pub const IS_SUPPORTED_SYSTEM: bool = true;
// Make formattable by rustfmt.
#[cfg(any())]
mod component;
#[cfg(any())]
mod cpu;
#[cfg(any())]
mod disk;
#[cfg(any())]
mod ffi;
#[cfg(any())]
mod network;
#[cfg(any())]
mod process;
#[cfg(any())]
mod system;

View File

@@ -0,0 +1,225 @@
// 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::network::refresh_networks_addresses;
use crate::{IpNetwork, MacAddr, NetworkData};
macro_rules! old_and_new {
($ty_:expr, $name:ident, $old:ident, $data:expr) => {{
$ty_.$old = $ty_.$name;
$ty_.$name = $data.$name;
}};
}
pub(crate) struct NetworksInner {
pub(crate) interfaces: HashMap<String, NetworkData>,
}
impl NetworksInner {
pub(crate) fn new() -> Self {
Self {
interfaces: HashMap::new(),
}
}
pub(crate) fn list(&self) -> &HashMap<String, NetworkData> {
&self.interfaces
}
pub(crate) fn refresh(&mut self, remove_not_listed_interfaces: bool) {
unsafe {
self.refresh_interfaces(true);
}
if remove_not_listed_interfaces {
// Remove interfaces which are gone.
self.interfaces.retain(|_, i| {
if !i.inner.updated {
return false;
}
i.inner.updated = false;
true
});
}
refresh_networks_addresses(&mut self.interfaces);
}
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.inner.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_utf8_string(&data.ifmd_name) {
let data = &data.ifmd_data;
let mtu = data.ifi_mtu as u64;
match self.interfaces.entry(name) {
hash_map::Entry::Occupied(mut e) => {
let interface = e.get_mut();
let interface = &mut interface.inner;
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);
if interface.mtu != mtu {
interface.mtu = mtu;
}
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 {
inner: NetworkDataInner {
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,
mac_addr: MacAddr::UNSPECIFIED,
ip_networks: vec![],
mtu,
},
});
}
}
}
}
}
}
pub(crate) struct NetworkDataInner {
/// 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,
/// MAC address
pub(crate) mac_addr: MacAddr,
/// IP networks
pub(crate) ip_networks: Vec<IpNetwork>,
/// Interface Maximum Transfer Unit (MTU)
mtu: u64,
}
impl NetworkDataInner {
pub(crate) fn received(&self) -> u64 {
self.ifi_ibytes.saturating_sub(self.old_ifi_ibytes)
}
pub(crate) fn total_received(&self) -> u64 {
self.ifi_ibytes
}
pub(crate) fn transmitted(&self) -> u64 {
self.ifi_obytes.saturating_sub(self.old_ifi_obytes)
}
pub(crate) fn total_transmitted(&self) -> u64 {
self.ifi_obytes
}
pub(crate) fn packets_received(&self) -> u64 {
self.ifi_ipackets.saturating_sub(self.old_ifi_ipackets)
}
pub(crate) fn total_packets_received(&self) -> u64 {
self.ifi_ipackets
}
pub(crate) fn packets_transmitted(&self) -> u64 {
self.ifi_opackets.saturating_sub(self.old_ifi_opackets)
}
pub(crate) fn total_packets_transmitted(&self) -> u64 {
self.ifi_opackets
}
pub(crate) fn errors_on_received(&self) -> u64 {
self.ifi_ierrors.saturating_sub(self.old_ifi_ierrors)
}
pub(crate) fn total_errors_on_received(&self) -> u64 {
self.ifi_ierrors
}
pub(crate) fn errors_on_transmitted(&self) -> u64 {
self.ifi_oerrors.saturating_sub(self.old_ifi_oerrors)
}
pub(crate) fn total_errors_on_transmitted(&self) -> u64 {
self.ifi_oerrors
}
pub(crate) fn mac_address(&self) -> MacAddr {
self.mac_addr
}
pub(crate) fn ip_networks(&self) -> &[IpNetwork] {
&self.ip_networks
}
pub(crate) fn mtu(&self) -> u64 {
self.mtu
}
}

View File

@@ -0,0 +1,397 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{DiskUsage, Gid, Pid, Process, ProcessRefreshKind, ProcessStatus, Signal, Uid};
use std::ffi::{OsStr, OsString};
use std::fmt;
use std::path::{Path, PathBuf};
use std::process::ExitStatus;
use super::ffi::filedesc;
use super::utils::{get_sys_value_by_name, 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",
})
}
}
pub(crate) struct ProcessInner {
pub(crate) name: OsString,
pub(crate) cmd: Vec<OsString>,
pub(crate) exe: Option<PathBuf>,
pub(crate) pid: Pid,
parent: Option<Pid>,
pub(crate) environ: Vec<OsString>,
pub(crate) cwd: Option<PathBuf>,
pub(crate) root: Option<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,
effective_user_id: Uid,
group_id: Gid,
effective_group_id: Gid,
read_bytes: u64,
old_read_bytes: u64,
written_bytes: u64,
old_written_bytes: u64,
accumulated_cpu_time: u64,
exists: bool,
// On FreeBSD, we can only get this information from `kinfo_proc`, so instead of going through
// all open processes again, better store the value...
open_files: Option<u32>,
}
impl ProcessInner {
pub(crate) fn kill_with(&self, signal: Signal) -> Option<bool> {
let c_signal = crate::sys::system::convert_signal(signal)?;
unsafe { Some(libc::kill(self.pid.0, c_signal) == 0) }
}
pub(crate) fn name(&self) -> &OsStr {
&self.name
}
pub(crate) fn cmd(&self) -> &[OsString] {
&self.cmd
}
pub(crate) fn exe(&self) -> Option<&Path> {
self.exe.as_deref()
}
pub(crate) fn pid(&self) -> Pid {
self.pid
}
pub(crate) fn environ(&self) -> &[OsString] {
&self.environ
}
pub(crate) fn cwd(&self) -> Option<&Path> {
self.cwd.as_deref()
}
pub(crate) fn root(&self) -> Option<&Path> {
self.root.as_deref()
}
pub(crate) fn memory(&self) -> u64 {
self.memory
}
pub(crate) fn virtual_memory(&self) -> u64 {
self.virtual_memory
}
pub(crate) fn parent(&self) -> Option<Pid> {
self.parent
}
pub(crate) fn status(&self) -> ProcessStatus {
self.status
}
pub(crate) fn start_time(&self) -> u64 {
self.start_time
}
pub(crate) fn run_time(&self) -> u64 {
self.run_time
}
pub(crate) fn cpu_usage(&self) -> f32 {
self.cpu_usage
}
pub(crate) fn accumulated_cpu_time(&self) -> u64 {
self.accumulated_cpu_time
}
pub(crate) 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,
}
}
pub(crate) fn user_id(&self) -> Option<&Uid> {
Some(&self.user_id)
}
pub(crate) fn effective_user_id(&self) -> Option<&Uid> {
Some(&self.effective_user_id)
}
pub(crate) fn group_id(&self) -> Option<Gid> {
Some(self.group_id)
}
pub(crate) fn effective_group_id(&self) -> Option<Gid> {
Some(self.effective_group_id)
}
pub(crate) fn wait(&self) -> Option<ExitStatus> {
crate::unix::utils::wait_process(self.pid)
}
pub(crate) fn session_id(&self) -> Option<Pid> {
unsafe {
let session_id = libc::getsid(self.pid.0);
if session_id < 0 {
None
} else {
Some(Pid(session_id))
}
}
}
pub(crate) fn switch_updated(&mut self) -> bool {
std::mem::replace(&mut self.updated, false)
}
pub(crate) fn set_nonexistent(&mut self) {
self.exists = false;
}
pub(crate) fn exists(&self) -> bool {
self.exists
}
pub(crate) fn open_files(&self) -> Option<u32> {
self.open_files
}
pub(crate) fn open_files_limit(&self) -> Option<u32> {
let mut value = 0u32;
unsafe {
if get_sys_value_by_name(b"kern.maxfilesperproc\0", &mut value) {
Some(value)
} else {
None
}
}
}
}
#[inline]
fn get_accumulated_cpu_time(kproc: &libc::kinfo_proc) -> u64 {
// from FreeBSD source /bin/ps/print.c
kproc.ki_runtime / 1_000
}
pub(crate) unsafe fn get_process_data(
kd: super::system::PtrWrap<()>,
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, memory) = if refresh_kind.memory() {
(
kproc.ki_size as _,
(kproc.ki_rssize as u64).saturating_mul(page_size as _),
)
} else {
(0, 0)
};
// FIXME: This is to get the "real" run time (in micro-seconds).
// let run_time = (kproc.ki_runtime + 5_000) / 10_000;
let start_time = kproc.ki_start.tv_sec as u64;
let mut open_files = None;
let kd = kd.0 as *mut libc::kvm_t;
if !kd.is_null() && !kproc.ki_fd.is_null() {
let mut ki_fd = std::mem::MaybeUninit::<filedesc>::uninit();
let size = std::mem::size_of::<filedesc>();
// `ki_fd` pointer is not actually a pointer to accessible but to kernel memory.
// So to retrieve the value, we need to get the memory from the kernel using `kvm_read2`.
let write_size = libc::kvm_read2(
kd,
kproc.ki_fd as _,
ki_fd.as_mut_ptr() as *mut _,
size as _,
);
if write_size == size as _ {
let ki_fd = ki_fd.assume_init();
if !ki_fd.fd_files.is_null() {
let mut fd_files = std::mem::MaybeUninit::<super::ffi::fdescenttbl>::uninit();
let size = std::mem::size_of::<super::ffi::fdescenttbl>();
// Kernel memory here as well...
let write_size = libc::kvm_read2(
kd,
ki_fd.fd_files as _,
fd_files.as_mut_ptr() as *mut _,
size as _,
);
if write_size == size as _ {
let fd_files = fd_files.assume_init();
open_files = Some(fd_files.fdt_nfiles as _);
}
}
}
}
if let Some(proc_) = (*wrap.0.get()).get_mut(&Pid(kproc.ki_pid)) {
let proc_ = &mut proc_.inner;
proc_.updated = true;
// If the `start_time` we just got is different from the one stored, it means it's not the
// same process.
if proc_.start_time == start_time {
proc_.cpu_usage = cpu_usage;
proc_.parent = parent;
proc_.status = status;
if refresh_kind.memory() {
proc_.virtual_memory = virtual_memory;
proc_.memory = memory;
}
proc_.run_time = now.saturating_sub(proc_.start_time);
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 _;
}
if refresh_kind.cpu() {
proc_.accumulated_cpu_time = get_accumulated_cpu_time(kproc);
}
proc_.open_files = open_files;
return Ok(None);
}
}
// This is a new process, we need to get more information!
// 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);
Ok(Some(Process {
inner: ProcessInner {
pid: Pid(kproc.ki_pid),
parent,
user_id: Uid(kproc.ki_ruid),
effective_user_id: Uid(kproc.ki_uid),
group_id: Gid(kproc.ki_rgid),
effective_group_id: Gid(kproc.ki_svgid),
start_time,
run_time: now.saturating_sub(start_time),
cpu_usage,
virtual_memory,
memory,
// procstat_getfiles
cwd: None,
exe: None,
// kvm_getargv isn't thread-safe so we get it in the main thread.
name: OsString::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: None,
// 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,
accumulated_cpu_time: if refresh_kind.cpu() {
get_accumulated_cpu_time(kproc)
} else {
0
},
updated: true,
exists: true,
open_files,
},
}))
}
pub(crate) unsafe fn get_exe(
exe: &mut Option<PathBuf>,
pid: crate::Pid,
refresh_kind: ProcessRefreshKind,
) {
if refresh_kind.exe().needs_update(|| exe.is_none()) {
let mut buffer = [0; libc::PATH_MAX as usize + 1];
*exe = get_sys_value_str(
&[
libc::CTL_KERN,
libc::KERN_PROC,
libc::KERN_PROC_PATHNAME,
pid.0,
],
&mut buffer,
)
.map(PathBuf::from);
}
}

View File

@@ -0,0 +1,763 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{
Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessInner,
ProcessRefreshKind, ProcessesToUpdate,
};
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 std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use std::sync::OnceLock;
use std::time::{Duration, SystemTime};
use crate::sys::cpu::{physical_core_count, CpusWrapper};
use crate::sys::process::get_exe;
use crate::sys::utils::{
self, boot_time, c_buf_to_os_string, c_buf_to_utf8_string, from_cstr_array, get_sys_value,
get_sys_value_by_name, 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/supported_signals.md")]
pub const SUPPORTED_SIGNALS: &[crate::Signal] = supported_signals();
#[doc = include_str!("../../../md_doc/minimum_cpu_update_interval.md")]
pub const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(100);
pub(crate) struct SystemInner {
process_list: HashMap<Pid, Process>,
mem_total: u64,
mem_free: u64,
mem_used: u64,
swap_total: u64,
swap_used: u64,
system_info: SystemInfo,
cpus: CpusWrapper,
}
impl SystemInner {
pub(crate) fn new() -> Self {
Self {
process_list: HashMap::with_capacity(200),
mem_total: 0,
mem_free: 0,
mem_used: 0,
swap_total: 0,
swap_used: 0,
system_info: SystemInfo::new(),
cpus: CpusWrapper::new(),
}
}
pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) {
if refresh_kind.ram() {
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();
}
if refresh_kind.swap() {
let (swap_used, swap_total) = self.system_info.get_swap_info();
self.swap_total = swap_total;
self.swap_used = swap_used;
}
}
pub(crate) fn cgroup_limits(&self) -> Option<crate::CGroupLimits> {
None
}
pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) {
self.cpus.refresh(refresh_kind)
}
pub(crate) fn refresh_cpu_list(&mut self, refresh_kind: CpuRefreshKind) {
self.cpus = CpusWrapper::new();
self.cpus.refresh(refresh_kind);
}
pub(crate) fn refresh_processes_specifics(
&mut self,
processes_to_update: ProcessesToUpdate<'_>,
refresh_kind: ProcessRefreshKind,
) -> usize {
unsafe { self.refresh_procs(processes_to_update, refresh_kind) }
}
// COMMON PART
//
// Need to be moved into a "common" file to avoid duplication.
pub(crate) fn processes(&self) -> &HashMap<Pid, Process> {
&self.process_list
}
pub(crate) fn processes_mut(&mut self) -> &mut HashMap<Pid, Process> {
&mut self.process_list
}
pub(crate) fn process(&self, pid: Pid) -> Option<&Process> {
self.process_list.get(&pid)
}
pub(crate) fn global_cpu_usage(&self) -> f32 {
self.cpus.global_cpu_usage
}
pub(crate) fn cpus(&self) -> &[Cpu] {
&self.cpus.cpus
}
pub(crate) fn total_memory(&self) -> u64 {
self.mem_total
}
pub(crate) fn free_memory(&self) -> u64 {
self.mem_free
}
pub(crate) fn available_memory(&self) -> u64 {
self.mem_free
}
pub(crate) fn used_memory(&self) -> u64 {
self.mem_used
}
pub(crate) fn total_swap(&self) -> u64 {
self.swap_total
}
pub(crate) fn free_swap(&self) -> u64 {
self.swap_total - self.swap_used
}
// TODO: need to be checked
pub(crate) fn used_swap(&self) -> u64 {
self.swap_used
}
pub(crate) fn uptime() -> u64 {
unsafe {
let csec = libc::time(std::ptr::null_mut());
libc::difftime(csec, Self::boot_time() as _) as u64
}
}
pub(crate) fn boot_time() -> u64 {
boot_time()
}
pub(crate) fn load_average() -> 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],
}
}
}
pub(crate) fn name() -> Option<String> {
let mut os_type: [c_int; 2] = [0; 2];
unsafe {
init_mib(b"kern.ostype\0", &mut os_type);
get_system_info(&os_type, Some("FreeBSD"))
}
}
pub(crate) fn os_version() -> Option<String> {
let mut os_release: [c_int; 2] = [0; 2];
unsafe {
init_mib(b"kern.osrelease\0", &mut os_release);
// It returns something like "13.0-RELEASE". We want to keep everything until the "-".
get_system_info(&os_release, None)
.and_then(|s| s.split('-').next().map(|s| s.to_owned()))
}
}
pub(crate) fn long_os_version() -> Option<String> {
let mut os_release: [c_int; 2] = [0; 2];
unsafe {
init_mib(b"kern.version\0", &mut os_release);
get_system_info(&os_release, None)
}
}
pub(crate) fn host_name() -> Option<String> {
let mut hostname: [c_int; 2] = [0; 2];
unsafe {
init_mib(b"kern.hostname\0", &mut hostname);
get_system_info(&hostname, None)
}
}
pub(crate) fn kernel_version() -> Option<String> {
unsafe {
let mut kern_version: libc::c_int = 0;
if get_sys_value_by_name(b"kern.osrevision\0", &mut kern_version) {
Some(kern_version.to_string())
} else {
None
}
}
}
pub(crate) fn distribution_id() -> String {
std::env::consts::OS.to_owned()
}
pub(crate) fn distribution_id_like() -> Vec<String> {
Vec::new()
}
pub(crate) fn kernel_name() -> Option<&'static str> {
Some("FreeBSD")
}
pub(crate) fn cpu_arch() -> Option<String> {
let mut arch_str: [u8; 32] = [0; 32];
let mib = [libc::CTL_HW as _, libc::HW_MACHINE as _];
unsafe {
if get_sys_value(&mib, &mut arch_str) {
CStr::from_bytes_until_nul(&arch_str)
.ok()
.and_then(|res| match res.to_str() {
Ok(arch) => Some(arch.to_string()),
Err(_) => None,
})
} else {
None
}
}
}
pub(crate) fn physical_core_count() -> Option<usize> {
physical_core_count()
}
}
#[derive(Clone, Copy)]
pub(crate) struct PtrWrap<T: Copy>(pub *mut T);
unsafe impl<T: Copy> Send for PtrWrap<T> {}
unsafe impl<T: Copy> Sync for PtrWrap<T> {}
impl SystemInner {
unsafe fn refresh_procs(
&mut self,
processes_to_update: ProcessesToUpdate<'_>,
refresh_kind: ProcessRefreshKind,
) -> usize {
let (op, arg) = match processes_to_update {
ProcessesToUpdate::Some(&[]) => return 0,
ProcessesToUpdate::Some(&[pid]) => (libc::KERN_PROC_PID, pid.as_u32() as c_int),
_ => (libc::KERN_PROC_PROC, 0),
};
let mut count = 0;
let kvm_procs = libc::kvm_getprocs(self.system_info.kd.as_ptr(), op, arg, &mut count);
if count < 1 {
sysinfo_debug!("kvm_getprocs returned nothing...");
return 0;
}
#[inline(always)]
fn real_filter(e: &libc::kinfo_proc, filter: &[Pid]) -> bool {
filter.contains(&Pid(e.ki_pid))
}
#[inline(always)]
fn empty_filter(_e: &libc::kinfo_proc, _filter: &[Pid]) -> bool {
true
}
#[allow(clippy::type_complexity)]
let (filter, filter_callback): (
&[Pid],
&(dyn Fn(&libc::kinfo_proc, &[Pid]) -> bool + Sync + Send),
) = match processes_to_update {
ProcessesToUpdate::All => (&[], &empty_filter),
ProcessesToUpdate::Some(pids) => {
if pids.is_empty() {
return 0;
}
(pids, &real_filter)
}
};
let nb_updated = AtomicUsize::new(0);
let new_processes = {
#[cfg(feature = "multithread")]
use rayon::iter::{ParallelIterator, ParallelIterator as IterTrait};
#[cfg(not(feature = "multithread"))]
use std::iter::Iterator as IterTrait;
let kvm_procs: &mut [utils::KInfoProc] =
std::slice::from_raw_parts_mut(kvm_procs as _, count as _);
let fscale = self.system_info.fscale;
let page_size = self.system_info.page_size as isize;
let now = get_now();
let proc_list = utils::WrapMap(UnsafeCell::new(&mut self.process_list));
// We cast into `*mut ()` because `c_void` isn't `Copy`...
//
// And we need `PtrWrap` because pointers aren't `Send` and `Sync`.
let kd = PtrWrap(kd() as *mut ());
IterTrait::filter_map(crate::utils::into_iter(kvm_procs), |kproc| {
if !filter_callback(kproc, filter) {
return None;
}
let ret = super::process::get_process_data(
kd,
kproc,
&proc_list,
page_size,
fscale,
now,
refresh_kind,
)
.ok()?;
nb_updated.fetch_add(1, Ordering::Relaxed);
ret
})
.collect::<Vec<_>>()
};
for process in new_processes {
self.process_list.insert(process.inner.pid, process);
}
let kvm_procs: &mut [utils::KInfoProc] =
std::slice::from_raw_parts_mut(kvm_procs as _, count as _);
for kproc in kvm_procs {
if let Some(process) = self.process_list.get_mut(&Pid(kproc.ki_pid)) {
add_missing_proc_info(&mut self.system_info, kproc, process, refresh_kind);
}
}
nb_updated.into_inner()
}
}
unsafe fn add_missing_proc_info(
system_info: &mut SystemInfo,
kproc: &libc::kinfo_proc,
proc_: &mut Process,
refresh_kind: ProcessRefreshKind,
) {
{
let kd = system_info.kd.as_ptr();
let proc_inner = &mut proc_.inner;
let cmd_needs_update = refresh_kind
.cmd()
.needs_update(|| proc_inner.cmd.is_empty());
if proc_inner.name.is_empty() || cmd_needs_update {
let cmd = from_cstr_array(libc::kvm_getargv(kd, kproc, 0) as _);
if !cmd.is_empty() {
// First, we try to retrieve the name from the command line.
let p = Path::new(&cmd[0]);
if let Some(name) = p.file_name() {
name.clone_into(&mut proc_inner.name);
}
if cmd_needs_update {
proc_inner.cmd = cmd;
}
}
}
get_exe(&mut proc_inner.exe, proc_inner.pid, refresh_kind);
system_info.get_proc_missing_info(kproc, proc_inner, refresh_kind);
if proc_inner.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_inner.name = c_buf_to_os_string(&kproc.ki_comm);
}
if refresh_kind
.environ()
.needs_update(|| proc_inner.environ.is_empty())
{
proc_inner.environ = from_cstr_array(libc::kvm_getenvv(kd, kproc, 0) as _);
}
}
}
#[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
}
}
}
fn kd() -> *mut libc::kvm_t {
static KD: OnceLock<AtomicPtr<libc::kvm_t>> = OnceLock::new();
KD.get_or_init(|| unsafe {
AtomicPtr::new(libc::kvm_open(
std::ptr::null(),
std::ptr::null(),
std::ptr::null(),
libc::O_RDONLY,
std::ptr::null_mut(),
))
})
.load(Ordering::Relaxed)
}
/// 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],
buf_space: [c_int; 2],
kd: NonNull<libc::kvm_t>,
/// 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 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(),
kd,
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);
si
}
}
/// 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 _),
total.saturating_mul(self.page_size as _),
)
}
}
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 _);
}
// 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
}
}
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 = mem_wire.saturating_sub(arc_size);
}
mem_active
.saturating_mul(self.page_size as _)
.saturating_add(mem_wire)
}
}
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...
buffers_mem
.saturating_add(inactive_mem.saturating_mul(self.page_size as _))
.saturating_add(cached_mem.saturating_mul(self.page_size as _))
.saturating_add(free_mem.saturating_mul(self.page_size as _))
}
}
#[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 ProcessInner,
refresh_kind: ProcessRefreshKind,
) {
let mut done = 0;
let cwd_needs_update = refresh_kind.cwd().needs_update(|| proc_.cwd().is_none());
let root_needs_update = refresh_kind.root().needs_update(|| proc_.root().is_none());
if cwd_needs_update {
done += 1;
}
if root_needs_update {
done += 1;
}
if done == 0 {
return;
}
if self.procstat.is_null() {
self.procstat = libc::procstat_open_sysctl();
if self.procstat.is_null() {
sysinfo_debug!("procstat_open_sysctl failed");
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;
while !entry.is_null() && done > 0 {
{
let tmp = &*entry;
if tmp.fs_uflags & libc::PS_FST_UFLAG_CDIR != 0 {
if cwd_needs_update && !tmp.fs_path.is_null() {
if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() {
proc_.cwd = Some(PathBuf::from(p));
done -= 1;
}
}
} else if tmp.fs_uflags & libc::PS_FST_UFLAG_RDIR != 0 {
if root_needs_update && !tmp.fs_path.is_null() {
if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() {
proc_.root = Some(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);
}
}
}
}
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 _];
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_utf8_string(&buf)
}
}
}
}
fn get_now() -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|n| n.as_secs())
.unwrap_or(0)
}

246
vendor/sysinfo/src/unix/freebsd/utils.rs vendored Normal file
View File

@@ -0,0 +1,246 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[cfg(feature = "system")]
use std::ffi::{CStr, OsStr, OsString};
#[cfg(feature = "system")]
use std::os::unix::ffi::OsStrExt;
#[cfg(feature = "system")]
#[inline]
pub unsafe fn init_mib(name: &[u8], mib: &mut [libc::c_int]) {
let mut len = mib.len();
libc::sysctlnametomib(name.as_ptr() as _, mib.as_mut_ptr(), &mut len);
}
#[cfg(feature = "system")]
pub(crate) fn boot_time() -> u64 {
let mut boot_time = libc::timeval {
tv_sec: 0,
tv_usec: 0,
};
let mut len = std::mem::size_of::<libc::timeval>();
let mut mib: [libc::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 libc::timeval as *mut _,
&mut len,
std::ptr::null_mut(),
0,
) < 0
{
0
} else {
boot_time.tv_sec as _
}
}
}
#[cfg(any(feature = "system", feature = "network"))]
pub(crate) unsafe fn get_sys_value<T: Sized>(mib: &[libc::c_int], value: &mut T) -> bool {
let mut len = std::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
}
#[cfg(feature = "system")]
pub(crate) unsafe fn get_sys_value_array<T: Sized>(mib: &[libc::c_int], value: &mut [T]) -> bool {
let mut len = std::mem::size_of_val(value) 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
}
#[cfg(any(feature = "disk", feature = "system", feature = "network"))]
pub(crate) fn c_buf_to_utf8_str(buf: &[libc::c_char]) -> Option<&str> {
unsafe {
let buf: &[u8] = std::slice::from_raw_parts(buf.as_ptr() as _, buf.len());
std::str::from_utf8(if let Some(pos) = buf.iter().position(|x| *x == 0) {
// Shrink buffer to terminate the null bytes
&buf[..pos]
} else {
buf
})
.ok()
}
}
#[cfg(any(feature = "disk", feature = "system", feature = "network"))]
pub(crate) fn c_buf_to_utf8_string(buf: &[libc::c_char]) -> Option<String> {
c_buf_to_utf8_str(buf).map(|s| s.to_owned())
}
#[cfg(feature = "system")]
pub(crate) fn c_buf_to_os_str(buf: &[libc::c_char]) -> &OsStr {
unsafe {
let buf: &[u8] = std::slice::from_raw_parts(buf.as_ptr() as _, buf.len());
OsStr::from_bytes(if let Some(pos) = buf.iter().position(|x| *x == 0) {
// Shrink buffer to terminate the null bytes
&buf[..pos]
} else {
buf
})
}
}
#[cfg(feature = "system")]
pub(crate) fn c_buf_to_os_string(buf: &[libc::c_char]) -> OsString {
c_buf_to_os_str(buf).to_owned()
}
#[cfg(feature = "system")]
pub(crate) unsafe fn get_sys_value_str(
mib: &[libc::c_int],
buf: &mut [libc::c_char],
) -> Option<OsString> {
let mut len = std::mem::size_of_val(buf) 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;
}
Some(c_buf_to_os_string(
&buf[..len / std::mem::size_of::<libc::c_char>()],
))
}
#[cfg(any(feature = "system", feature = "component"))]
pub(crate) unsafe fn get_sys_value_by_name<T: Sized>(name: &[u8], value: &mut T) -> bool {
let mut len = std::mem::size_of::<T>() as libc::size_t;
let original = len;
libc::sysctlbyname(
name.as_ptr() as *const libc::c_char,
value as *mut _ as *mut _,
&mut len,
std::ptr::null_mut(),
0,
) == 0
&& original == len
}
#[cfg(any(feature = "system", feature = "disk"))]
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 libc::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 _];
if libc::sysctlbyname(
name.as_ptr() as *const libc::c_char,
buf.as_mut_ptr() as *mut _,
&mut size,
std::ptr::null_mut(),
0,
) == 0
&& size > 0
{
c_buf_to_utf8_string(&buf)
} else {
// getting the system value failed
None
}
} else {
None
}
}
}
#[cfg(feature = "system")]
pub(crate) unsafe fn from_cstr_array(ptr: *const *const libc::c_char) -> Vec<OsString> {
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);
ret.push(OsStr::from_bytes(CStr::from_ptr(*p).to_bytes()).to_os_string());
}
ret
}
#[cfg(any(feature = "system", feature = "component"))]
pub(crate) unsafe fn get_nb_cpus() -> usize {
let mut smp: libc::c_int = 0;
let mut nb_cpus: libc::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;
}
}
nb_cpus as usize
}
// All this is needed because `kinfo_proc` doesn't implement `Send` (because it contains pointers).
#[cfg(feature = "system")]
pub(crate) struct WrapMap<'a>(
pub std::cell::UnsafeCell<&'a mut std::collections::HashMap<crate::Pid, crate::Process>>,
);
#[cfg(feature = "system")]
unsafe impl Send for WrapMap<'_> {}
#[cfg(feature = "system")]
unsafe impl Sync for WrapMap<'_> {}
#[cfg(feature = "system")]
#[repr(transparent)]
pub(crate) struct KInfoProc(libc::kinfo_proc);
#[cfg(feature = "system")]
unsafe impl Send for KInfoProc {}
#[cfg(feature = "system")]
unsafe impl Sync for KInfoProc {}
#[cfg(feature = "system")]
impl std::ops::Deref for KInfoProc {
type Target = libc::kinfo_proc;
fn deref(&self) -> &Self::Target {
&self.0
}
}

53
vendor/sysinfo/src/unix/groups.rs vendored Normal file
View File

@@ -0,0 +1,53 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
use crate::Group;
impl crate::GroupInner {
pub(crate) fn new(id: crate::Gid, name: String) -> Self {
Self { id, name }
}
pub(crate) fn id(&self) -> &crate::Gid {
&self.id
}
pub(crate) fn name(&self) -> &str {
&self.name
}
}
// Not used by mac.
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
pub(crate) fn get_groups(groups: &mut Vec<Group>) {
use crate::{Gid, GroupInner};
use std::fs::File;
use std::io::Read;
#[inline]
fn parse_id(id: &str) -> Option<u32> {
id.parse::<u32>().ok()
}
groups.clear();
let mut s = String::new();
let _ = File::open("/etc/group").and_then(|mut f| f.read_to_string(&mut s));
for line in s.lines() {
let mut parts = line.split(':');
if let Some(name) = parts.next() {
let mut parts = parts.skip(1);
// Skip the user if the uid cannot be parsed correctly
if let Some(gid) = parts.next().and_then(parse_id) {
groups.push(Group {
inner: GroupInner::new(Gid(gid), name.to_owned()),
});
}
}
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub(crate) use crate::unix::apple::groups::get_groups;

View File

@@ -0,0 +1,439 @@
// Take a look at the license at the top of the repository in the LICENSE file.
// Information about values readable from `hwmon` sysfs.
//
// Values in /sys/class/hwmonN are `c_long` or `c_ulong`
// transposed to rust we only read `u32` or `i32` values.
use crate::Component;
use std::collections::HashMap;
use std::fs::{read_dir, File};
use std::io::Read;
use std::path::{Path, PathBuf};
#[derive(Default)]
pub(crate) struct ComponentInner {
/// Optional associated device of a `Component`.
device_model: Option<String>,
/// The chip name.
///
/// Kernel documentation extract:
///
/// ```txt
/// This should be a short, lowercase string, not containing
/// whitespace, dashes, or the wildcard character '*'.
/// This attribute represents the chip name. It is the only
/// mandatory attribute.
/// I2C devices get this attribute created automatically.
/// ```
name: String,
/// Temperature current value
/// - Read in: `temp[1-*]_input`.
/// - Unit: read as millidegree Celsius converted to Celsius.
temperature: Option<f32>,
/// Maximum value computed by `sysinfo`.
max: Option<f32>,
// /// Max threshold provided by the chip/kernel
// /// - Read in:`temp[1-*]_max`
// /// - Unit: read as millidegree Celsius converted to Celsius.
// threshold_max: Option<f32>,
// /// Min threshold provided by the chip/kernel.
// /// - Read in:`temp[1-*]_min`
// /// - Unit: read as millidegree Celsius converted to Celsius.
// threshold_min: Option<f32>,
/// Critical threshold provided by the chip/kernel previous user write.
/// Read in `temp[1-*]_crit`:
/// Typically greater than corresponding temp_max values.
/// - Unit: read as millidegree Celsius converted to Celsius.
threshold_critical: Option<f32>,
/// Sensor type, not common but can exist!
///
/// Read in: `temp[1-*]_type` Sensor type selection.
/// Values integer:
///
/// - 1: CPU embedded diode
/// - 2: 3904 transistor
/// - 3: thermal diode
/// - 4: thermistor
/// - 5: AMD AMDSI
/// - 6: Intel PECI
///
/// Not all types are supported by all chips.
sensor_type: Option<ThermalSensorType>,
/// Component Label
///
/// ## Linux implementation details
///
/// read n: `temp[1-*]_label` Suggested temperature channel label.
/// Value: Text string
///
/// Should only be created if the driver has hints about what
/// this temperature channel is being used for, and user-space
/// doesn't. In all other cases, the label is provided by user-space.
label: String,
// Historical minimum temperature
// - Read in:`temp[1-*]_lowest
// - Unit: millidegree Celsius
//
// Temperature critical min value, typically lower than
// corresponding temp_min values.
// - Read in:`temp[1-*]_lcrit`
// - Unit: millidegree Celsius
//
// Temperature emergency max value, for chips supporting more than
// two upper temperature limits. Must be equal or greater than
// corresponding temp_crit values.
// - temp[1-*]_emergency
// - Unit: millidegree Celsius
/// File to read current temperature shall be `temp[1-*]_input`
/// It may be absent but we don't continue if absent.
input_file: Option<PathBuf>,
/// `temp[1-*]_highest file` to read if available highest value.
highest_file: Option<PathBuf>,
pub(crate) updated: bool,
}
impl ComponentInner {
fn update_from(
&mut self,
Component {
inner:
ComponentInner {
temperature,
max,
input_file,
highest_file,
..
},
}: Component,
) {
if let Some(temp) = temperature {
self.temperature = Some(temp);
}
match (max, self.max) {
(Some(new_max), Some(old_max)) => self.max = Some(new_max.max(old_max)),
(Some(max), None) => self.max = Some(max),
_ => {}
}
if input_file.is_some() && input_file != self.input_file {
self.input_file = input_file;
}
if highest_file.is_some() && highest_file != self.highest_file {
self.highest_file = highest_file;
}
self.updated = true;
}
}
// Read arbitrary data from sysfs.
fn get_file_line(file: &Path, capacity: usize) -> Option<String> {
let mut reader = String::with_capacity(capacity);
let mut f = File::open(file).ok()?;
f.read_to_string(&mut reader).ok()?;
reader.truncate(reader.trim_end().len());
Some(reader)
}
/// Designed at first for reading an `i32` or `u32` aka `c_long`
/// from a `/sys/class/hwmon` sysfs file.
fn read_number_from_file<N>(file: &Path) -> Option<N>
where
N: std::str::FromStr,
{
let mut reader = [0u8; 32];
let mut f = File::open(file).ok()?;
let n = f.read(&mut reader).ok()?;
// parse and trim would complain about `\0`.
let number = &reader[..n];
let number = std::str::from_utf8(number).ok()?;
let number = number.trim();
// Assert that we cleaned a little bit that string.
if cfg!(feature = "debug") {
assert!(!number.contains('\n') && !number.contains('\0'));
}
number.parse().ok()
}
// Read a temperature from a `tempN_item` sensor form the sysfs.
// number returned will be in mili-celsius.
//
// Don't call it on `label`, `name` or `type` file.
#[inline]
fn get_temperature_from_file(file: &Path) -> Option<f32> {
let temp = read_number_from_file(file);
convert_temp_celsius(temp)
}
/// Takes a raw temperature in mili-celsius and convert it to celsius.
#[inline]
fn convert_temp_celsius(temp: Option<i32>) -> Option<f32> {
temp.map(|n| (n as f32) / 1000f32)
}
/// Information about thermal sensor. It may be unavailable as it's
/// kernel module and chip dependent.
enum ThermalSensorType {
/// 1: CPU embedded diode
CPUEmbeddedDiode,
/// 2: 3904 transistor
Transistor3904,
/// 3: thermal diode
ThermalDiode,
/// 4: thermistor
Thermistor,
/// 5: AMD AMDSI
AMDAMDSI,
/// 6: Intel PECI
IntelPECI,
/// Not all types are supported by all chips so we keep space for unknown sensors.
#[allow(dead_code)]
Unknown(u8),
}
impl From<u8> for ThermalSensorType {
fn from(input: u8) -> Self {
match input {
0 => Self::CPUEmbeddedDiode,
1 => Self::Transistor3904,
3 => Self::ThermalDiode,
4 => Self::Thermistor,
5 => Self::AMDAMDSI,
6 => Self::IntelPECI,
n => Self::Unknown(n),
}
}
}
/// Check given `item` dispatch to read the right `file` with the right parsing and store data in
/// given `component`. `id` is provided for `label` creation.
fn fill_component(component: &mut ComponentInner, item: &str, folder: &Path, file: &str) {
let hwmon_file = folder.join(file);
match item {
"type" => {
component.sensor_type =
read_number_from_file::<u8>(&hwmon_file).map(ThermalSensorType::from)
}
"input" => {
let temperature = get_temperature_from_file(&hwmon_file);
component.input_file = Some(hwmon_file);
component.temperature = temperature;
// Maximum know try to get it from `highest` if not available
// use current temperature
if component.max.is_none() {
component.max = temperature;
}
}
"label" => component.label = get_file_line(&hwmon_file, 10).unwrap_or_default(),
"highest" => {
component.max = get_temperature_from_file(&hwmon_file).or(component.temperature);
component.highest_file = Some(hwmon_file);
}
// "max" => component.threshold_max = get_temperature_from_file(&hwmon_file),
// "min" => component.threshold_min = get_temperature_from_file(&hwmon_file),
"crit" => component.threshold_critical = get_temperature_from_file(&hwmon_file),
_ => {
sysinfo_debug!(
"This hwmon-temp file is still not supported! Contributions are appreciated.;) {:?}",
hwmon_file,
);
}
}
}
impl ComponentInner {
/// Read out `hwmon` info (hardware monitor) from `folder`
/// to get values' path to be used on refresh as well as files containing `max`,
/// `critical value` and `label`. Then we store everything into `components`.
///
/// Note that a thermal [Component] must have a way to read its temperature.
/// If not, it will be ignored and not added into `components`.
///
/// ## What is read:
///
/// - Mandatory: `name` the name of the `hwmon`.
/// - Mandatory: `tempN_input` Drop [Component] if missing
/// - Optional: sensor `label`, in the general case content of `tempN_label`
/// see below for special cases
/// - Optional: `label`
/// - Optional: `/device/model`
/// - Optional: highest historic value in `tempN_highest`.
/// - Optional: max threshold value defined in `tempN_max`
/// - Optional: critical threshold value defined in `tempN_crit`
///
/// Where `N` is a `u32` associated to a sensor like `temp1_max`, `temp1_input`.
///
/// ## Doc to Linux kernel API.
///
/// Kernel hwmon API: https://www.kernel.org/doc/html/latest/hwmon/hwmon-kernel-api.html
/// DriveTemp kernel API: https://docs.kernel.org/gpu/amdgpu/thermal.html#hwmon-interfaces
/// Amdgpu hwmon interface: https://www.kernel.org/doc/html/latest/hwmon/drivetemp.html
fn from_hwmon(components: &mut Vec<Component>, folder: &Path) -> Option<()> {
let dir = read_dir(folder).ok()?;
let mut matchings: HashMap<u32, Component> = HashMap::with_capacity(10);
for entry in dir.flatten() {
if !entry.file_type().is_ok_and(|file_type| !file_type.is_dir()) {
continue;
}
let entry = entry.path();
let filename = entry.file_name().and_then(|x| x.to_str()).unwrap_or("");
let Some((id, item)) = filename
.strip_prefix("temp")
.and_then(|f| f.split_once('_'))
.and_then(|(id, item)| Some((id.parse::<u32>().ok()?, item)))
else {
continue;
};
let component = matchings.entry(id).or_insert_with(|| Component {
inner: ComponentInner::default(),
});
let component = &mut component.inner;
let name = get_file_line(&folder.join("name"), 16);
component.name = name.unwrap_or_default();
let device_model = get_file_line(&folder.join("device/model"), 16);
component.device_model = device_model;
fill_component(component, item, folder, filename);
}
for (id, mut new_comp) in matchings
.into_iter()
// Remove components without `tempN_input` file termal. `Component` doesn't support this
// kind of sensors yet
.filter(|(_, c)| c.inner.input_file.is_some())
{
if new_comp.inner.label.is_empty() {
// sysinfo expose a generic interface with a `label`.
// Problem: a lot of sensors don't have a label or a device model! ¯\_(ツ)_/¯
// So let's pretend we have a unique label!
// See the table in `Component::label` documentation for the table detail.
new_comp.inner.label = new_comp.inner.format_label("temp", id);
}
if let Some(comp) = components
.iter_mut()
.find(|comp| comp.inner.label == new_comp.inner.label)
{
comp.inner.update_from(new_comp);
} else {
new_comp.inner.updated = true;
components.push(new_comp);
}
}
Some(())
}
/// Compute a label out of available information.
/// See the table in `Component::label`'s documentation.
fn format_label(&self, class: &str, id: u32) -> String {
let ComponentInner {
device_model,
name,
label,
..
} = self;
let has_label = !label.is_empty();
match (has_label, device_model) {
(true, Some(device_model)) => {
format!("{name} {label} {device_model} {class}{id}")
}
(true, None) => format!("{name} {label}"),
(false, Some(device_model)) => format!("{name} {device_model}"),
(false, None) => format!("{name} {class}{id}"),
}
}
pub(crate) fn temperature(&self) -> Option<f32> {
self.temperature
}
pub(crate) fn max(&self) -> Option<f32> {
self.max
}
pub(crate) fn critical(&self) -> Option<f32> {
self.threshold_critical
}
pub(crate) fn label(&self) -> &str {
&self.label
}
pub(crate) fn refresh(&mut self) {
let current = self
.input_file
.as_ref()
.and_then(|file| get_temperature_from_file(file.as_path()));
// tries to read out kernel highest if not compute something from temperature.
let max = self
.highest_file
.as_ref()
.and_then(|file| get_temperature_from_file(file.as_path()))
.or_else(|| {
let last = self.temperature?;
let current = current?;
Some(last.max(current))
});
self.max = max;
self.temperature = current;
}
}
pub(crate) struct ComponentsInner {
pub(crate) components: Vec<Component>,
}
impl ComponentsInner {
pub(crate) fn new() -> Self {
Self {
components: Vec::with_capacity(4),
}
}
pub(crate) fn from_vec(components: Vec<Component>) -> Self {
Self { components }
}
pub(crate) fn into_vec(self) -> Vec<Component> {
self.components
}
pub(crate) fn list(&self) -> &[Component] {
&self.components
}
pub(crate) fn list_mut(&mut self) -> &mut [Component] {
&mut self.components
}
pub(crate) fn refresh(&mut self) {
if let Ok(dir) = read_dir(Path::new("/sys/class/hwmon/")) {
for entry in dir.flatten() {
let Ok(file_type) = entry.file_type() else {
continue;
};
let entry = entry.path();
if !file_type.is_file()
&& entry
.file_name()
.and_then(|x| x.to_str())
.unwrap_or("")
.starts_with("hwmon")
{
ComponentInner::from_hwmon(&mut self.components, &entry);
}
}
}
if self.components.is_empty() {
// Specfic to raspberry pi.
let thermal_path = Path::new("/sys/class/thermal/thermal_zone0/");
if thermal_path.join("temp").exists() {
let mut component = ComponentInner::default();
fill_component(&mut component, "input", thermal_path, "temp");
let name = get_file_line(&thermal_path.join("type"), 16);
component.name = name.unwrap_or_default();
self.components.push(Component { inner: component });
}
}
}
}

837
vendor/sysinfo/src/unix/linux/cpu.rs vendored Normal file
View File

@@ -0,0 +1,837 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#![allow(clippy::too_many_arguments)]
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use std::time::Instant;
use crate::sys::utils::to_u64;
use crate::{Cpu, CpuRefreshKind};
macro_rules! to_str {
($e:expr) => {
unsafe { std::str::from_utf8_unchecked($e) }
};
}
pub(crate) struct CpusWrapper {
pub(crate) global_cpu: CpuUsage,
pub(crate) cpus: Vec<Cpu>,
got_cpu_frequency: bool,
/// This field is needed to prevent updating when not enough time passed since last update.
last_update: Option<Instant>,
}
impl CpusWrapper {
pub(crate) fn new() -> Self {
Self {
global_cpu: CpuUsage::default(),
cpus: Vec::with_capacity(4),
got_cpu_frequency: false,
last_update: None,
}
}
pub(crate) fn refresh_if_needed(
&mut self,
only_update_global_cpu: bool,
refresh_kind: CpuRefreshKind,
) {
self.refresh(only_update_global_cpu, refresh_kind);
}
pub(crate) fn refresh(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) {
let need_cpu_usage_update = self
.last_update
.map(|last_update| last_update.elapsed() > crate::MINIMUM_CPU_UPDATE_INTERVAL)
.unwrap_or(true);
let first = self.cpus.is_empty();
let mut vendors_brands = if first {
get_vendor_id_and_brand()
} else {
HashMap::new()
};
// If the last CPU usage update is too close (less than `MINIMUM_CPU_UPDATE_INTERVAL`),
// we don't want to update CPUs times.
if need_cpu_usage_update {
self.last_update = Some(Instant::now());
let f = match File::open("/proc/stat") {
Ok(f) => f,
Err(_e) => {
sysinfo_debug!("failed to retrieve CPU information: {:?}", _e);
return;
}
};
let buf = BufReader::new(f);
let mut i: usize = 0;
let mut it = buf.split(b'\n');
if first || refresh_kind.cpu_usage() {
if let Some(Ok(line)) = it.next() {
if &line[..4] != b"cpu " {
return;
}
let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()).skip(1);
self.global_cpu.set(
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
);
}
if first || !only_update_global_cpu {
while let Some(Ok(line)) = it.next() {
if &line[..3] != b"cpu" {
break;
}
let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty());
if first {
let (vendor_id, brand) = match vendors_brands.remove(&i) {
Some((vendor_id, brand)) => (vendor_id, brand),
None => (String::new(), String::new()),
};
self.cpus.push(Cpu {
inner: CpuInner::new_with_values(
to_str!(parts.next().unwrap_or(&[])),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
0,
vendor_id,
brand,
),
});
} else {
parts.next(); // we don't want the name again
self.cpus[i].inner.set(
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
parts.next().map(to_u64).unwrap_or(0),
);
}
i += 1;
}
}
}
}
if refresh_kind.frequency() {
#[cfg(feature = "multithread")]
use rayon::iter::{
IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator,
};
#[cfg(feature = "multithread")]
// This function is voluntarily made generic in case we want to generalize it.
fn iter_mut<'a, T>(
val: &'a mut T,
) -> <&'a mut T as rayon::iter::IntoParallelIterator>::Iter
where
&'a mut T: rayon::iter::IntoParallelIterator,
{
val.par_iter_mut()
}
#[cfg(not(feature = "multithread"))]
fn iter_mut(val: &mut [Cpu]) -> std::slice::IterMut<'_, Cpu> {
val.iter_mut()
}
// `get_cpu_frequency` is very slow, so better run it in parallel.
iter_mut(&mut self.cpus)
.enumerate()
.for_each(|(pos, proc_)| proc_.inner.frequency = get_cpu_frequency(pos));
self.got_cpu_frequency = true;
}
}
pub(crate) fn get_global_raw_times(&self) -> (u64, u64) {
(self.global_cpu.total_time, self.global_cpu.old_total_time)
}
pub(crate) fn len(&self) -> usize {
self.cpus.len()
}
pub(crate) fn is_empty(&self) -> bool {
self.cpus.is_empty()
}
}
/// Struct containing values to compute a CPU usage.
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct CpuValues {
user: u64,
nice: u64,
system: u64,
idle: u64,
iowait: u64,
irq: u64,
softirq: u64,
steal: u64,
guest: u64,
guest_nice: u64,
}
impl CpuValues {
/// Sets the given argument to the corresponding fields.
pub fn set(
&mut self,
user: u64,
nice: u64,
system: u64,
idle: u64,
iowait: u64,
irq: u64,
softirq: u64,
steal: u64,
guest: u64,
guest_nice: u64,
) {
// `guest` is already accounted in `user`.
self.user = user.saturating_sub(guest);
// `guest_nice` is already accounted in `nice`.
self.nice = nice.saturating_sub(guest_nice);
self.system = system;
self.idle = idle;
self.iowait = iowait;
self.irq = irq;
self.softirq = softirq;
self.steal = steal;
self.guest = guest;
self.guest_nice = guest_nice;
}
/// Returns work time.
pub fn work_time(&self) -> u64 {
self.user
.saturating_add(self.nice)
.saturating_add(self.system)
.saturating_add(self.irq)
.saturating_add(self.softirq)
}
/// Returns total time.
pub fn total_time(&self) -> u64 {
self.work_time()
.saturating_add(self.idle)
.saturating_add(self.iowait)
// `steal`, `guest` and `guest_nice` are only used if we want to account the "guest"
// into the computation.
.saturating_add(self.guest)
.saturating_add(self.guest_nice)
.saturating_add(self.steal)
}
}
#[derive(Default)]
pub(crate) struct CpuUsage {
percent: f32,
old_values: CpuValues,
new_values: CpuValues,
total_time: u64,
old_total_time: u64,
}
impl CpuUsage {
pub(crate) fn new_with_values(
user: u64,
nice: u64,
system: u64,
idle: u64,
iowait: u64,
irq: u64,
softirq: u64,
steal: u64,
guest: u64,
guest_nice: u64,
) -> Self {
let mut new_values = CpuValues::default();
new_values.set(
user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
);
Self {
old_values: CpuValues::default(),
new_values,
percent: 0f32,
total_time: 0,
old_total_time: 0,
}
}
pub(crate) fn set(
&mut self,
user: u64,
nice: u64,
system: u64,
idle: u64,
iowait: u64,
irq: u64,
softirq: u64,
steal: u64,
guest: u64,
guest_nice: u64,
) {
macro_rules! min {
($a:expr, $b:expr, $def:expr) => {
if $a > $b {
($a - $b) as f32
} else {
$def
}
};
}
self.old_values = self.new_values;
self.new_values.set(
user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
);
self.total_time = self.new_values.total_time();
self.old_total_time = self.old_values.total_time();
self.percent = min!(self.new_values.work_time(), self.old_values.work_time(), 0.)
/ min!(self.total_time, self.old_total_time, 1.)
* 100.;
if self.percent > 100. {
self.percent = 100.; // to prevent the percentage to go above 100%
}
}
pub(crate) fn usage(&self) -> f32 {
self.percent
}
}
pub(crate) struct CpuInner {
usage: CpuUsage,
pub(crate) name: String,
pub(crate) frequency: u64,
pub(crate) vendor_id: String,
pub(crate) brand: String,
}
impl CpuInner {
pub(crate) fn new_with_values(
name: &str,
user: u64,
nice: u64,
system: u64,
idle: u64,
iowait: u64,
irq: u64,
softirq: u64,
steal: u64,
guest: u64,
guest_nice: u64,
frequency: u64,
vendor_id: String,
brand: String,
) -> Self {
Self {
usage: CpuUsage::new_with_values(
user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
),
name: name.to_owned(),
frequency,
vendor_id,
brand,
}
}
pub(crate) fn set(
&mut self,
user: u64,
nice: u64,
system: u64,
idle: u64,
iowait: u64,
irq: u64,
softirq: u64,
steal: u64,
guest: u64,
guest_nice: u64,
) {
self.usage.set(
user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
);
}
pub(crate) fn cpu_usage(&self) -> f32 {
self.usage.percent
}
pub(crate) fn name(&self) -> &str {
&self.name
}
/// Returns the CPU frequency in MHz.
pub(crate) fn frequency(&self) -> u64 {
self.frequency
}
pub(crate) fn vendor_id(&self) -> &str {
&self.vendor_id
}
pub(crate) fn brand(&self) -> &str {
&self.brand
}
}
pub(crate) fn get_cpu_frequency(cpu_core_index: usize) -> u64 {
let mut s = String::new();
if File::open(format!(
"/sys/devices/system/cpu/cpu{cpu_core_index}/cpufreq/scaling_cur_freq",
))
.and_then(|mut f| f.read_to_string(&mut s))
.is_ok()
{
let freq_option = s.trim().split('\n').next();
if let Some(freq_string) = freq_option {
if let Ok(freq) = freq_string.parse::<u64>() {
return freq / 1000;
}
}
}
s.clear();
if File::open("/proc/cpuinfo")
.and_then(|mut f| f.read_to_string(&mut s))
.is_err()
{
return 0;
}
let find_cpu_mhz = s.split('\n').find(|line| {
line.starts_with("cpu MHz\t")
|| line.starts_with("BogoMIPS")
|| line.starts_with("clock\t")
|| line.starts_with("bogomips per cpu")
});
find_cpu_mhz
.and_then(|line| line.split(':').next_back())
.and_then(|val| val.replace("MHz", "").trim().parse::<f64>().ok())
.map(|speed| speed as u64)
.unwrap_or_default()
}
#[allow(unused_assignments)]
pub(crate) fn get_physical_core_count() -> Option<usize> {
let mut s = String::new();
if let Err(_e) = File::open("/proc/cpuinfo").and_then(|mut f| f.read_to_string(&mut s)) {
sysinfo_debug!("Cannot read `/proc/cpuinfo` file: {:?}", _e);
return None;
}
macro_rules! add_core {
($core_ids_and_physical_ids:ident, $core_id:ident, $physical_id:ident, $cpu:ident) => {{
if !$core_id.is_empty() && !$physical_id.is_empty() {
$core_ids_and_physical_ids.insert(format!("{} {}", $core_id, $physical_id));
} else if !$cpu.is_empty() {
// On systems with only physical cores like raspberry, there is no "core id" or
// "physical id" fields. So if one of them is missing, we simply use the "CPU"
// info and count it as a physical core.
$core_ids_and_physical_ids.insert($cpu.to_owned());
}
$core_id = "";
$physical_id = "";
$cpu = "";
}};
}
let mut core_ids_and_physical_ids: HashSet<String> = HashSet::new();
let mut core_id = "";
let mut physical_id = "";
let mut cpu = "";
for line in s.lines() {
if line.is_empty() {
add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu);
} else if line.starts_with("processor") {
cpu = line
.splitn(2, ':')
.last()
.map(|x| x.trim())
.unwrap_or_default();
} else if line.starts_with("core id") {
core_id = line
.splitn(2, ':')
.last()
.map(|x| x.trim())
.unwrap_or_default();
} else if line.starts_with("physical id") {
physical_id = line
.splitn(2, ':')
.last()
.map(|x| x.trim())
.unwrap_or_default();
}
}
add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu);
Some(core_ids_and_physical_ids.len())
}
/// Obtain the implementer of this CPU core.
///
/// This has been obtained from util-linux's lscpu implementation, see
/// https://github.com/util-linux/util-linux/blob/7076703b529d255600631306419cca1b48ab850a/sys-utils/lscpu-arm.c#L240
///
/// This list will have to be updated every time a new vendor appears, please keep it synchronized
/// with util-linux and update the link above with the commit you have used.
fn get_arm_implementer(implementer: u32) -> Option<&'static str> {
Some(match implementer {
0x41 => "ARM",
0x42 => "Broadcom",
0x43 => "Cavium",
0x44 => "DEC",
0x46 => "FUJITSU",
0x48 => "HiSilicon",
0x49 => "Infineon",
0x4d => "Motorola/Freescale",
0x4e => "NVIDIA",
0x50 => "APM",
0x51 => "Qualcomm",
0x53 => "Samsung",
0x56 => "Marvell",
0x61 => "Apple",
0x66 => "Faraday",
0x69 => "Intel",
0x70 => "Phytium",
0xc0 => "Ampere",
_ => return None,
})
}
/// Obtain the part of this CPU core.
///
/// This has been obtained from util-linux's lscpu implementation, see
/// https://github.com/util-linux/util-linux/blob/eb788e20b82d0e1001a30867c71c8bfb2bb86819/sys-utils/lscpu-arm.c#L25
///
/// This list will have to be updated every time a new core appears, please keep it synchronized
/// with util-linux and update the link above with the commit you have used.
fn get_arm_part(implementer: u32, part: u32) -> Option<&'static str> {
Some(match (implementer, part) {
// ARM
(0x41, 0x810) => "ARM810",
(0x41, 0x920) => "ARM920",
(0x41, 0x922) => "ARM922",
(0x41, 0x926) => "ARM926",
(0x41, 0x940) => "ARM940",
(0x41, 0x946) => "ARM946",
(0x41, 0x966) => "ARM966",
(0x41, 0xa20) => "ARM1020",
(0x41, 0xa22) => "ARM1022",
(0x41, 0xa26) => "ARM1026",
(0x41, 0xb02) => "ARM11 MPCore",
(0x41, 0xb36) => "ARM1136",
(0x41, 0xb56) => "ARM1156",
(0x41, 0xb76) => "ARM1176",
(0x41, 0xc05) => "Cortex-A5",
(0x41, 0xc07) => "Cortex-A7",
(0x41, 0xc08) => "Cortex-A8",
(0x41, 0xc09) => "Cortex-A9",
(0x41, 0xc0d) => "Cortex-A17", // Originally A12
(0x41, 0xc0f) => "Cortex-A15",
(0x41, 0xc0e) => "Cortex-A17",
(0x41, 0xc14) => "Cortex-R4",
(0x41, 0xc15) => "Cortex-R5",
(0x41, 0xc17) => "Cortex-R7",
(0x41, 0xc18) => "Cortex-R8",
(0x41, 0xc20) => "Cortex-M0",
(0x41, 0xc21) => "Cortex-M1",
(0x41, 0xc23) => "Cortex-M3",
(0x41, 0xc24) => "Cortex-M4",
(0x41, 0xc27) => "Cortex-M7",
(0x41, 0xc60) => "Cortex-M0+",
(0x41, 0xd01) => "Cortex-A32",
(0x41, 0xd02) => "Cortex-A34",
(0x41, 0xd03) => "Cortex-A53",
(0x41, 0xd04) => "Cortex-A35",
(0x41, 0xd05) => "Cortex-A55",
(0x41, 0xd06) => "Cortex-A65",
(0x41, 0xd07) => "Cortex-A57",
(0x41, 0xd08) => "Cortex-A72",
(0x41, 0xd09) => "Cortex-A73",
(0x41, 0xd0a) => "Cortex-A75",
(0x41, 0xd0b) => "Cortex-A76",
(0x41, 0xd0c) => "Neoverse-N1",
(0x41, 0xd0d) => "Cortex-A77",
(0x41, 0xd0e) => "Cortex-A76AE",
(0x41, 0xd13) => "Cortex-R52",
(0x41, 0xd15) => "Cortex-R82",
(0x41, 0xd16) => "Cortex-R52+",
(0x41, 0xd20) => "Cortex-M23",
(0x41, 0xd21) => "Cortex-M33",
(0x41, 0xd22) => "Cortex-R55",
(0x41, 0xd23) => "Cortex-R85",
(0x41, 0xd40) => "Neoverse-V1",
(0x41, 0xd41) => "Cortex-A78",
(0x41, 0xd42) => "Cortex-A78AE",
(0x41, 0xd43) => "Cortex-A65AE",
(0x41, 0xd44) => "Cortex-X1",
(0x41, 0xd46) => "Cortex-A510",
(0x41, 0xd47) => "Cortex-A710",
(0x41, 0xd48) => "Cortex-X2",
(0x41, 0xd49) => "Neoverse-N2",
(0x41, 0xd4a) => "Neoverse-E1",
(0x41, 0xd4b) => "Cortex-A78C",
(0x41, 0xd4c) => "Cortex-X1C",
(0x41, 0xd4d) => "Cortex-A715",
(0x41, 0xd4e) => "Cortex-X3",
(0x41, 0xd4f) => "Neoverse-V2",
(0x41, 0xd80) => "Cortex-A520",
(0x41, 0xd81) => "Cortex-A720",
(0x41, 0xd82) => "Cortex-X4",
(0x41, 0xd84) => "Neoverse-V3",
(0x41, 0xd85) => "Cortex-X925",
(0x41, 0xd87) => "Cortex-A725",
(0x41, 0xd8e) => "Neoverse-N3",
// Broadcom
(0x42, 0x00f) => "Brahma-B15",
(0x42, 0x100) => "Brahma-B53",
(0x42, 0x516) => "ThunderX2",
// Cavium
(0x43, 0x0a0) => "ThunderX",
(0x43, 0x0a1) => "ThunderX-88XX",
(0x43, 0x0a2) => "ThunderX-81XX",
(0x43, 0x0a3) => "ThunderX-83XX",
(0x43, 0x0af) => "ThunderX2-99xx",
// DEC
(0x44, 0xa10) => "SA110",
(0x44, 0xa11) => "SA1100",
// Fujitsu
(0x46, 0x001) => "A64FX",
// HiSilicon
(0x48, 0xd01) => "Kunpeng-920", // aka tsv110
// NVIDIA
(0x4e, 0x000) => "Denver",
(0x4e, 0x003) => "Denver 2",
(0x4e, 0x004) => "Carmel",
// APM
(0x50, 0x000) => "X-Gene",
// Qualcomm
(0x51, 0x00f) => "Scorpion",
(0x51, 0x02d) => "Scorpion",
(0x51, 0x04d) => "Krait",
(0x51, 0x06f) => "Krait",
(0x51, 0x201) => "Kryo",
(0x51, 0x205) => "Kryo",
(0x51, 0x211) => "Kryo",
(0x51, 0x800) => "Falkor-V1/Kryo",
(0x51, 0x801) => "Kryo-V2",
(0x51, 0x802) => "Kryo-3XX-Gold",
(0x51, 0x803) => "Kryo-3XX-Silver",
(0x51, 0x804) => "Kryo-4XX-Gold",
(0x51, 0x805) => "Kryo-4XX-Silver",
(0x51, 0xc00) => "Falkor",
(0x51, 0xc01) => "Saphira",
// Samsung
(0x53, 0x001) => "exynos-m1",
// Marvell
(0x56, 0x131) => "Feroceon-88FR131",
(0x56, 0x581) => "PJ4/PJ4b",
(0x56, 0x584) => "PJ4B-MP",
// Apple
(0x61, 0x020) => "Icestorm-A14",
(0x61, 0x021) => "Firestorm-A14",
(0x61, 0x022) => "Icestorm-M1",
(0x61, 0x023) => "Firestorm-M1",
(0x61, 0x024) => "Icestorm-M1-Pro",
(0x61, 0x025) => "Firestorm-M1-Pro",
(0x61, 0x028) => "Icestorm-M1-Max",
(0x61, 0x029) => "Firestorm-M1-Max",
(0x61, 0x030) => "Blizzard-A15",
(0x61, 0x031) => "Avalanche-A15",
(0x61, 0x032) => "Blizzard-M2",
(0x61, 0x033) => "Avalanche-M2",
// Faraday
(0x66, 0x526) => "FA526",
(0x66, 0x626) => "FA626",
// Intel
(0x69, 0x200) => "i80200",
(0x69, 0x210) => "PXA250A",
(0x69, 0x212) => "PXA210A",
(0x69, 0x242) => "i80321-400",
(0x69, 0x243) => "i80321-600",
(0x69, 0x290) => "PXA250B/PXA26x",
(0x69, 0x292) => "PXA210B",
(0x69, 0x2c2) => "i80321-400-B0",
(0x69, 0x2c3) => "i80321-600-B0",
(0x69, 0x2d0) => "PXA250C/PXA255/PXA26x",
(0x69, 0x2d2) => "PXA210C",
(0x69, 0x411) => "PXA27x",
(0x69, 0x41c) => "IPX425-533",
(0x69, 0x41d) => "IPX425-400",
(0x69, 0x41f) => "IPX425-266",
(0x69, 0x682) => "PXA32x",
(0x69, 0x683) => "PXA930/PXA935",
(0x69, 0x688) => "PXA30x",
(0x69, 0x689) => "PXA31x",
(0x69, 0xb11) => "SA1110",
(0x69, 0xc12) => "IPX1200",
// Phytium
(0x70, 0x660) => "FTC660",
(0x70, 0x661) => "FTC661",
(0x70, 0x662) => "FTC662",
(0x70, 0x663) => "FTC663",
_ => return None,
})
}
/// Returns the brand/vendor string for the first CPU (which should be the same for all CPUs).
pub(crate) fn get_vendor_id_and_brand() -> HashMap<usize, (String, String)> {
let mut s = String::new();
if File::open("/proc/cpuinfo")
.and_then(|mut f| f.read_to_string(&mut s))
.is_err()
{
return HashMap::new();
}
fn get_value(s: &str) -> String {
s.split(':')
.next_back()
.map(|x| x.trim().to_owned())
.unwrap_or_default()
}
fn get_hex_value(s: &str) -> u32 {
s.split(':')
.next_back()
.map(|x| x.trim())
.filter(|x| x.starts_with("0x"))
.map(|x| u32::from_str_radix(&x[2..], 16).unwrap())
.unwrap_or_default()
}
#[inline]
fn is_new_processor(line: &str) -> bool {
line.starts_with("processor\t")
}
#[derive(Default)]
struct CpuInfo {
index: usize,
vendor_id: Option<String>,
brand: Option<String>,
implementer: Option<u32>,
part: Option<u32>,
}
impl CpuInfo {
fn has_all_info(&self) -> bool {
(self.brand.is_some() && self.vendor_id.is_some())
|| (self.implementer.is_some() && self.part.is_some())
}
fn convert(mut self) -> (usize, String, String) {
let (vendor_id, brand) = if let (Some(implementer), Some(part)) =
(self.implementer.take(), self.part.take())
{
let vendor_id = get_arm_implementer(implementer).map(String::from);
// It's possible to "model name" even with an ARM CPU, so just in case we can't retrieve
// the brand from "CPU part", we will then use the value from "model name".
//
// Example from raspberry pi 3B+:
//
// ```
// model name : ARMv7 Processor rev 4 (v7l)
// CPU implementer : 0x41
// CPU part : 0xd03
// ```
let brand = get_arm_part(implementer, part)
.map(String::from)
.or_else(|| self.brand.take());
(vendor_id, brand)
} else {
(self.vendor_id.take(), self.brand.take())
};
(
self.index,
vendor_id.unwrap_or_default(),
brand.unwrap_or_default(),
)
}
}
let mut cpus: HashMap<usize, (String, String)> = HashMap::new();
let mut lines = s.split('\n');
while let Some(line) = lines.next() {
if is_new_processor(line) {
let index = match line
.split(':')
.nth(1)
.and_then(|i| i.trim().parse::<usize>().ok())
{
Some(index) => index,
None => {
sysinfo_debug!("Couldn't get processor ID from {line:?}, ignoring this core");
continue;
}
};
let mut info = CpuInfo {
index,
..Default::default()
};
#[allow(clippy::while_let_on_iterator)]
while let Some(line) = lines.next() {
if line.starts_with("vendor_id\t") {
info.vendor_id = Some(get_value(line));
} else if line.starts_with("model name\t") {
info.brand = Some(get_value(line));
} else if line.starts_with("CPU implementer\t") {
info.implementer = Some(get_hex_value(line));
} else if line.starts_with("CPU part\t") {
info.part = Some(get_hex_value(line));
} else if info.has_all_info() || is_new_processor(line) {
break;
}
}
let (index, vendor_id, brand) = info.convert();
cpus.insert(index, (vendor_id, brand));
}
}
cpus
}

615
vendor/sysinfo/src/unix/linux/disk.rs vendored Normal file
View File

@@ -0,0 +1,615 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::sys::utils::{get_all_utf8_data, to_cpath};
use crate::{Disk, DiskKind, DiskRefreshKind, DiskUsage};
use libc::statvfs;
use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::mem::MaybeUninit;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::str::FromStr;
/// Copied from [`psutil`]:
///
/// "man iostat" states that sectors are equivalent with blocks and have
/// a size of 512 bytes. Despite this value can be queried at runtime
/// via /sys/block/{DISK}/queue/hw_sector_size and results may vary
/// between 1k, 2k, or 4k... 512 appears to be a magic constant used
/// throughout Linux source code:
/// * <https://stackoverflow.com/a/38136179/376587>
/// * <https://lists.gt.net/linux/kernel/2241060>
/// * <https://github.com/giampaolo/psutil/issues/1305>
/// * <https://github.com/torvalds/linux/blob/4f671fe2f9523a1ea206f63fe60a7c7b3a56d5c7/include/linux/bio.h#L99>
/// * <https://lkml.org/lkml/2015/8/17/234>
///
/// [`psutil`]: <https://github.com/giampaolo/psutil/blob/master/psutil/_pslinux.py#L103>
const SECTOR_SIZE: u64 = 512;
macro_rules! cast {
($x:expr) => {
u64::from($x)
};
}
pub(crate) struct DiskInner {
type_: DiskKind,
device_name: OsString,
actual_device_name: Option<String>,
file_system: OsString,
mount_point: PathBuf,
total_space: u64,
available_space: u64,
is_removable: bool,
is_read_only: bool,
old_written_bytes: u64,
old_read_bytes: u64,
written_bytes: u64,
read_bytes: u64,
updated: bool,
}
impl DiskInner {
pub(crate) fn kind(&self) -> DiskKind {
self.type_
}
pub(crate) fn name(&self) -> &OsStr {
&self.device_name
}
pub(crate) fn file_system(&self) -> &OsStr {
&self.file_system
}
pub(crate) fn mount_point(&self) -> &Path {
&self.mount_point
}
pub(crate) fn total_space(&self) -> u64 {
self.total_space
}
pub(crate) fn available_space(&self) -> u64 {
self.available_space
}
pub(crate) fn is_removable(&self) -> bool {
self.is_removable
}
pub(crate) fn is_read_only(&self) -> bool {
self.is_read_only
}
pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) -> bool {
self.efficient_refresh(refresh_kind, &disk_stats(&refresh_kind), false)
}
fn efficient_refresh(
&mut self,
refresh_kind: DiskRefreshKind,
procfs_disk_stats: &HashMap<String, DiskStat>,
first: bool,
) -> bool {
if refresh_kind.io_usage() {
if self.actual_device_name.is_none() {
self.actual_device_name = Some(get_actual_device_name(&self.device_name));
}
if let Some(stat) = self
.actual_device_name
.as_ref()
.and_then(|actual_device_name| procfs_disk_stats.get(actual_device_name))
{
self.old_read_bytes = self.read_bytes;
self.old_written_bytes = self.written_bytes;
self.read_bytes = stat.sectors_read * SECTOR_SIZE;
self.written_bytes = stat.sectors_written * SECTOR_SIZE;
} else {
sysinfo_debug!("Failed to update disk i/o stats");
}
}
if refresh_kind.kind() && self.type_ == DiskKind::Unknown(-1) {
self.type_ = find_type_for_device_name(&self.device_name);
}
if refresh_kind.storage() {
if let Some((total_space, available_space, is_read_only)) =
unsafe { load_statvfs_values(&self.mount_point) }
{
self.total_space = total_space;
self.available_space = available_space;
if first {
self.is_read_only = is_read_only;
}
}
}
true
}
pub(crate) fn usage(&self) -> DiskUsage {
DiskUsage {
read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes),
total_read_bytes: self.read_bytes,
written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
total_written_bytes: self.written_bytes,
}
}
}
impl crate::DisksInner {
pub(crate) fn new() -> Self {
Self {
disks: Vec::with_capacity(2),
}
}
pub(crate) fn refresh_specifics(
&mut self,
remove_not_listed_disks: bool,
refresh_kind: DiskRefreshKind,
) {
get_all_list(
&mut self.disks,
&get_all_utf8_data("/proc/mounts", 16_385).unwrap_or_default(),
refresh_kind,
);
if remove_not_listed_disks {
self.disks.retain_mut(|disk| {
if !disk.inner.updated {
return false;
}
disk.inner.updated = false;
true
});
} else {
for c in self.disks.iter_mut() {
c.inner.updated = false;
}
}
}
pub(crate) fn list(&self) -> &[Disk] {
&self.disks
}
pub(crate) fn list_mut(&mut self) -> &mut [Disk] {
&mut self.disks
}
}
/// Resolves the actual device name for a specified `device` from `/proc/mounts`
///
/// This function is inspired by the [`bottom`] crate implementation and essentially does the following:
/// 1. Canonicalizes the specified device path to its absolute form
/// 2. Strips the "/dev" prefix from the canonicalized path
///
/// [`bottom`]: <https://github.com/ClementTsang/bottom/blob/main/src/data_collection/disks/unix/linux/partition.rs#L44>
fn get_actual_device_name(device: &OsStr) -> String {
let device_path = PathBuf::from(device);
std::fs::canonicalize(&device_path)
.ok()
.and_then(|path| path.strip_prefix("/dev").ok().map(Path::to_path_buf))
.unwrap_or(device_path)
.to_str()
.map(str::to_owned)
.unwrap_or_default()
}
unsafe fn load_statvfs_values(mount_point: &Path) -> Option<(u64, u64, bool)> {
let mount_point_cpath = to_cpath(mount_point);
let mut stat: MaybeUninit<statvfs> = MaybeUninit::uninit();
if retry_eintr!(statvfs(
mount_point_cpath.as_ptr() as *const _,
stat.as_mut_ptr()
)) == 0
{
let stat = stat.assume_init();
let bsize = cast!(stat.f_bsize);
let blocks = cast!(stat.f_blocks);
let bavail = cast!(stat.f_bavail);
let total = bsize.saturating_mul(blocks);
if total == 0 {
return None;
}
let available = bsize.saturating_mul(bavail);
let is_read_only = (stat.f_flag & libc::ST_RDONLY) != 0;
Some((total, available, is_read_only))
} else {
None
}
}
fn new_disk(
device_name: &OsStr,
mount_point: &Path,
file_system: &OsStr,
removable_entries: &[PathBuf],
procfs_disk_stats: &HashMap<String, DiskStat>,
refresh_kind: DiskRefreshKind,
) -> Disk {
let is_removable = removable_entries
.iter()
.any(|e| e.as_os_str() == device_name);
let mut disk = Disk {
inner: DiskInner {
type_: DiskKind::Unknown(-1),
device_name: device_name.to_owned(),
actual_device_name: None,
file_system: file_system.to_owned(),
mount_point: mount_point.to_owned(),
total_space: 0,
available_space: 0,
is_removable,
is_read_only: false,
old_read_bytes: 0,
old_written_bytes: 0,
read_bytes: 0,
written_bytes: 0,
updated: true,
},
};
disk.inner
.efficient_refresh(refresh_kind, procfs_disk_stats, true);
disk
}
#[allow(clippy::manual_range_contains)]
fn find_type_for_device_name(device_name: &OsStr) -> DiskKind {
// The format of devices are as follows:
// - device_name is symbolic link in the case of /dev/mapper/
// and /dev/root, and the target is corresponding device under
// /sys/block/
// - In the case of /dev/sd, the format is /dev/sd[a-z][1-9],
// corresponding to /sys/block/sd[a-z]
// - In the case of /dev/nvme, the format is /dev/nvme[0-9]n[0-9]p[0-9],
// corresponding to /sys/block/nvme[0-9]n[0-9]
// - In the case of /dev/mmcblk, the format is /dev/mmcblk[0-9]p[0-9],
// corresponding to /sys/block/mmcblk[0-9]
let device_name_path = device_name.to_str().unwrap_or_default();
let real_path = fs::canonicalize(device_name).unwrap_or_else(|_| PathBuf::from(device_name));
let mut real_path = real_path.to_str().unwrap_or_default();
if device_name_path.starts_with("/dev/mapper/") {
// Recursively solve, for example /dev/dm-0
if real_path != device_name_path {
return find_type_for_device_name(OsStr::new(&real_path));
}
} else if device_name_path.starts_with("/dev/sd") || device_name_path.starts_with("/dev/vd") {
// Turn "sda1" into "sda" or "vda1" into "vda"
real_path = real_path.trim_start_matches("/dev/");
real_path = real_path.trim_end_matches(|c| c >= '0' && c <= '9');
} else if device_name_path.starts_with("/dev/nvme") {
// Turn "nvme0n1p1" into "nvme0n1"
real_path = match real_path.find('p') {
Some(idx) => &real_path["/dev/".len()..idx],
None => &real_path["/dev/".len()..],
};
} else if device_name_path.starts_with("/dev/root") {
// Recursively solve, for example /dev/mmcblk0p1
if real_path != device_name_path {
return find_type_for_device_name(OsStr::new(&real_path));
}
} else if device_name_path.starts_with("/dev/mmcblk") {
// Turn "mmcblk0p1" into "mmcblk0"
real_path = match real_path.find('p') {
Some(idx) => &real_path["/dev/".len()..idx],
None => &real_path["/dev/".len()..],
};
} else {
// Default case: remove /dev/ and expects the name presents under /sys/block/
// For example, /dev/dm-0 to dm-0
real_path = real_path.trim_start_matches("/dev/");
}
let trimmed: &OsStr = OsStrExt::from_bytes(real_path.as_bytes());
let path = Path::new("/sys/block/")
.to_owned()
.join(trimmed)
.join("queue/rotational");
// Normally, this file only contains '0' or '1' but just in case, we get 8 bytes...
match get_all_utf8_data(path, 8)
.unwrap_or_default()
.trim()
.parse()
.ok()
{
// The disk is marked as rotational so it's a HDD.
Some(1) => DiskKind::HDD,
// The disk is marked as non-rotational so it's very likely a SSD.
Some(0) => DiskKind::SSD,
// Normally it shouldn't happen but welcome to the wonderful world of IT! :D
Some(x) => DiskKind::Unknown(x),
// The information isn't available...
None => DiskKind::Unknown(-1),
}
}
fn get_all_list(container: &mut Vec<Disk>, content: &str, refresh_kind: DiskRefreshKind) {
// The goal of this array is to list all removable devices (the ones whose name starts with
// "usb-").
let removable_entries = match fs::read_dir("/dev/disk/by-id/") {
Ok(r) => r
.filter_map(|res| Some(res.ok()?.path()))
.filter_map(|e| {
if e.file_name()
.and_then(|x| Some(x.to_str()?.starts_with("usb-")))
.unwrap_or_default()
{
e.canonicalize().ok()
} else {
None
}
})
.collect::<Vec<PathBuf>>(),
_ => Vec::new(),
};
let procfs_disk_stats = disk_stats(&refresh_kind);
for (fs_spec, fs_file, fs_vfstype) in content
.lines()
.map(|line| {
let line = line.trim_start();
// mounts format
// http://man7.org/linux/man-pages/man5/fstab.5.html
// fs_spec<tab>fs_file<tab>fs_vfstype<tab>other fields
let mut fields = line.split_whitespace();
let fs_spec = fields.next().unwrap_or("");
let fs_file = fields
.next()
.unwrap_or("")
.replace("\\134", "\\")
.replace("\\040", " ")
.replace("\\011", "\t")
.replace("\\012", "\n");
let fs_vfstype = fields.next().unwrap_or("");
(fs_spec, fs_file, fs_vfstype)
})
.filter(|(fs_spec, fs_file, fs_vfstype)| {
// Check if fs_vfstype is one of our 'ignored' file systems.
let filtered = match *fs_vfstype {
"rootfs" | // https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt
"sysfs" | // pseudo file system for kernel objects
"proc" | // another pseudo file system
"devtmpfs" |
"cgroup" |
"cgroup2" |
"pstore" | // https://www.kernel.org/doc/Documentation/ABI/testing/pstore
"squashfs" | // squashfs is a compressed read-only file system (for snaps)
"rpc_pipefs" | // The pipefs pseudo file system service
"iso9660" | // optical media
"devpts" | // https://www.kernel.org/doc/Documentation/filesystems/devpts.txt
"hugetlbfs" | // https://www.kernel.org/doc/Documentation/vm/hugetlbfs_reserv.txt
"mqueue" // https://man7.org/linux/man-pages/man7/mq_overview.7.html
=> true,
"tmpfs" => !cfg!(feature = "linux-tmpfs"),
// calling statvfs on a mounted CIFS or NFS may hang, when they are mounted with option: hard
"cifs" | "nfs" | "nfs4" => !cfg!(feature = "linux-netdevs"),
_ => false,
};
!(filtered ||
fs_file.starts_with("/sys") || // check if fs_file is an 'ignored' mount point
fs_file.starts_with("/proc") ||
(fs_file.starts_with("/run") && !fs_file.starts_with("/run/media")) ||
fs_spec.starts_with("sunrpc"))
})
{
let mount_point = Path::new(&fs_file);
if let Some(disk) = container.iter_mut().find(|d| {
d.inner.mount_point == mount_point
&& d.inner.device_name == fs_spec
&& d.inner.file_system == fs_vfstype
}) {
disk.inner
.efficient_refresh(refresh_kind, &procfs_disk_stats, false);
disk.inner.updated = true;
continue;
}
container.push(new_disk(
fs_spec.as_ref(),
mount_point,
fs_vfstype.as_ref(),
&removable_entries,
&procfs_disk_stats,
refresh_kind,
));
}
}
/// Disk IO stat information from `/proc/diskstats` file.
///
/// To fully understand these fields, please see the
/// [iostats.txt](https://www.kernel.org/doc/Documentation/iostats.txt) kernel documentation.
///
/// This type only contains the value `sysinfo` is interested into.
///
/// The fields of this file are:
/// 1. major number
/// 2. minor number
/// 3. device name
/// 4. reads completed successfully
/// 5. reads merged
/// 6. sectors read
/// 7. time spent reading (ms)
/// 8. writes completed
/// 9. writes merged
/// 10. sectors written
/// 11. time spent writing (ms)
/// 12. I/Os currently in progress
/// 13. time spent doing I/Os (ms)
/// 14. weighted time spent doing I/Os (ms)
///
/// Doc reference: https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
///
/// Doc reference: https://www.kernel.org/doc/Documentation/iostats.txt
#[derive(Debug, PartialEq)]
struct DiskStat {
sectors_read: u64,
sectors_written: u64,
}
impl DiskStat {
/// Returns the name and the values we're interested into.
fn new_from_line(line: &str) -> Option<(String, Self)> {
let mut iter = line.split_whitespace();
// 3rd field
let name = iter.nth(2).map(ToString::to_string)?;
// 6th field
let sectors_read = iter.nth(2).and_then(|v| u64::from_str(v).ok()).unwrap_or(0);
// 10th field
let sectors_written = iter.nth(3).and_then(|v| u64::from_str(v).ok()).unwrap_or(0);
Some((
name,
Self {
sectors_read,
sectors_written,
},
))
}
}
fn disk_stats(refresh_kind: &DiskRefreshKind) -> HashMap<String, DiskStat> {
if refresh_kind.io_usage() {
let path = "/proc/diskstats";
match fs::read_to_string(path) {
Ok(content) => disk_stats_inner(&content),
Err(_error) => {
sysinfo_debug!("failed to read {path:?}: {_error:?}");
HashMap::new()
}
}
} else {
Default::default()
}
}
// We split this function out to make it possible to test it.
fn disk_stats_inner(content: &str) -> HashMap<String, DiskStat> {
let mut data = HashMap::new();
for line in content.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
if let Some((name, stats)) = DiskStat::new_from_line(line) {
data.insert(name, stats);
}
}
data
}
#[cfg(test)]
mod test {
use super::{disk_stats_inner, DiskStat};
use std::collections::HashMap;
#[test]
fn test_disk_stat_parsing() {
// Content of a (very nicely formatted) `/proc/diskstats` file.
let file_content = "\
259 0 nvme0n1 571695 101559 38943220 165643 9824246 1076193 462375378 4140037 0 1038904 4740493 254020 0 1436922320 68519 306875 366293
259 1 nvme0n1p1 240 2360 15468 48 2 0 2 0 0 21 50 8 0 2373552 2 0 0
259 2 nvme0n1p2 243 10 11626 26 63 39 616 125 0 84 163 44 0 1075280 11 0 0
259 3 nvme0n1p3 571069 99189 38910302 165547 9824180 1076154 462374760 4139911 0 1084855 4373964 253968 0 1433473488 68505 0 0
253 0 dm-0 670206 0 38909056 259490 10900330 0 462374760 12906518 0 1177098 13195902 253968 0 1433473488 29894 0 0
252 0 zram0 2382 0 20984 11 260261 0 2082088 2063 0 1964 2074 0 0 0 0 0 0
1 2 bla 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
";
let data = disk_stats_inner(file_content);
let expected_data: HashMap<String, DiskStat> = HashMap::from([
(
"nvme0n1".to_string(),
DiskStat {
sectors_read: 38943220,
sectors_written: 462375378,
},
),
(
"nvme0n1p1".to_string(),
DiskStat {
sectors_read: 15468,
sectors_written: 2,
},
),
(
"nvme0n1p2".to_string(),
DiskStat {
sectors_read: 11626,
sectors_written: 616,
},
),
(
"nvme0n1p3".to_string(),
DiskStat {
sectors_read: 38910302,
sectors_written: 462374760,
},
),
(
"dm-0".to_string(),
DiskStat {
sectors_read: 38909056,
sectors_written: 462374760,
},
),
(
"zram0".to_string(),
DiskStat {
sectors_read: 20984,
sectors_written: 2082088,
},
),
// This one ensures that we read the correct fields.
(
"bla".to_string(),
DiskStat {
sectors_read: 6,
sectors_written: 10,
},
),
]);
assert_eq!(data, expected_data);
}
#[test]
fn disk_entry_with_less_information() {
let file_content = "\
systemd-1 /efi autofs rw,relatime,fd=181,pgrp=1,timeout=120,minproto=5,maxproto=5,direct,pipe_ino=8311 0 0
/dev/nvme0n1p1 /efi vfat rw,nosuid,nodev,noexec,relatime,nosymfollow,fmask=0077,dmask=0077 0 0
";
let data = disk_stats_inner(file_content);
let expected_data: HashMap<String, DiskStat> = HashMap::from([
(
"autofs".to_string(),
DiskStat {
sectors_read: 0,
sectors_written: 0,
},
),
(
"vfat".to_string(),
DiskStat {
sectors_read: 0,
sectors_written: 0,
},
),
]);
assert_eq!(data, expected_data);
}
}

56
vendor/sysinfo/src/unix/linux/mod.rs vendored Normal file
View File

@@ -0,0 +1,56 @@
// Take a look at the license at the top of the repository in the LICENSE file.
pub(crate) mod utils;
cfg_if! {
if #[cfg(feature = "system")] {
pub mod cpu;
pub mod process;
pub mod system;
pub(crate) use self::cpu::CpuInner;
pub(crate) use self::process::ProcessInner;
pub(crate) use self::system::SystemInner;
pub use self::system::{MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS};
}
if #[cfg(feature = "disk")] {
pub mod disk;
pub(crate) use self::disk::DiskInner;
pub(crate) use crate::unix::DisksInner;
}
if #[cfg(feature = "component")] {
pub mod component;
pub(crate) use self::component::{ComponentInner, ComponentsInner};
}
if #[cfg(feature = "network")] {
pub mod network;
pub(crate) use self::network::{NetworkDataInner, NetworksInner};
}
if #[cfg(feature = "user")] {
pub(crate) use crate::unix::groups::get_groups;
pub(crate) use crate::unix::users::{get_users, UserInner};
}
}
#[doc = include_str!("../../../md_doc/is_supported.md")]
pub const IS_SUPPORTED_SYSTEM: bool = true;
// Make formattable by rustfmt.
#[cfg(any())]
mod component;
#[cfg(any())]
mod cpu;
#[cfg(any())]
mod disk;
#[cfg(any())]
mod network;
#[cfg(any())]
mod process;
#[cfg(any())]
mod system;

304
vendor/sysinfo/src/unix/linux/network.rs vendored Normal file
View File

@@ -0,0 +1,304 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use std::collections::{hash_map, HashMap};
use std::fs::File;
use std::io::Read;
use std::path::Path;
use crate::network::refresh_networks_addresses;
use crate::{IpNetwork, MacAddr, NetworkData};
macro_rules! old_and_new {
($ty_:expr, $name:ident, $old:ident) => {{
$ty_.$old = $ty_.$name;
$ty_.$name = $name;
}};
($ty_:expr, $name:ident, $old:ident, $path:expr) => {{
let _tmp = $path;
$ty_.$old = $ty_.$name;
$ty_.$name = _tmp;
}};
}
#[allow(clippy::ptr_arg)]
fn read<P: AsRef<Path>>(parent: P, path: &str, data: &mut Vec<u8>) -> u64 {
if let Ok(mut f) = File::open(parent.as_ref().join(path)) {
if let Ok(size) = f.read(data) {
let mut i = 0;
let mut ret = 0;
while i < size && i < data.len() && data[i] >= b'0' && data[i] <= b'9' {
ret *= 10;
ret += (data[i] - b'0') as u64;
i += 1;
}
return ret;
}
}
0
}
fn refresh_networks_list_from_sysfs(
interfaces: &mut HashMap<String, NetworkData>,
remove_not_listed_interfaces: bool,
sysfs_net: &Path,
) {
if let Ok(dir) = std::fs::read_dir(sysfs_net) {
let mut data = vec![0; 30];
for stats in interfaces.values_mut() {
stats.inner.updated = false;
}
for entry in dir.flatten() {
let parent = &entry.path().join("statistics");
let entry_path = &entry.path();
let entry = match entry.file_name().into_string() {
Ok(entry) => entry,
Err(_) => continue,
};
let rx_bytes = read(parent, "rx_bytes", &mut data);
let tx_bytes = read(parent, "tx_bytes", &mut data);
let rx_packets = read(parent, "rx_packets", &mut data);
let tx_packets = read(parent, "tx_packets", &mut data);
let rx_errors = read(parent, "rx_errors", &mut data);
let tx_errors = read(parent, "tx_errors", &mut data);
// let rx_compressed = read(parent, "rx_compressed", &mut data);
// let tx_compressed = read(parent, "tx_compressed", &mut data);
let mtu = read(entry_path, "mtu", &mut data);
match interfaces.entry(entry) {
hash_map::Entry::Occupied(mut e) => {
let interface = e.get_mut();
let interface = &mut interface.inner;
old_and_new!(interface, rx_bytes, old_rx_bytes);
old_and_new!(interface, tx_bytes, old_tx_bytes);
old_and_new!(interface, rx_packets, old_rx_packets);
old_and_new!(interface, tx_packets, old_tx_packets);
old_and_new!(interface, rx_errors, old_rx_errors);
old_and_new!(interface, tx_errors, old_tx_errors);
// old_and_new!(e, rx_compressed, old_rx_compressed);
// old_and_new!(e, tx_compressed, old_tx_compressed);
if interface.mtu != mtu {
interface.mtu = mtu;
}
interface.updated = true;
}
hash_map::Entry::Vacant(e) => {
e.insert(NetworkData {
inner: NetworkDataInner {
rx_bytes,
old_rx_bytes: rx_bytes,
tx_bytes,
old_tx_bytes: tx_bytes,
rx_packets,
old_rx_packets: rx_packets,
tx_packets,
old_tx_packets: tx_packets,
rx_errors,
old_rx_errors: rx_errors,
tx_errors,
old_tx_errors: tx_errors,
mac_addr: MacAddr::UNSPECIFIED,
ip_networks: vec![],
// rx_compressed,
// old_rx_compressed: rx_compressed,
// tx_compressed,
// old_tx_compressed: tx_compressed,
mtu,
updated: true,
},
});
}
};
}
}
// We do this here because `refresh_networks_list_remove_interface` test is checking that
// this is working as expected.
if remove_not_listed_interfaces {
// Remove interfaces which are gone.
interfaces.retain(|_, i| {
if !i.inner.updated {
return false;
}
i.inner.updated = false;
true
});
}
}
pub(crate) struct NetworksInner {
pub(crate) interfaces: HashMap<String, NetworkData>,
}
impl NetworksInner {
pub(crate) fn new() -> Self {
Self {
interfaces: HashMap::new(),
}
}
pub(crate) fn list(&self) -> &HashMap<String, NetworkData> {
&self.interfaces
}
pub(crate) fn refresh(&mut self, remove_not_listed_interfaces: bool) {
refresh_networks_list_from_sysfs(
&mut self.interfaces,
remove_not_listed_interfaces,
Path::new("/sys/class/net/"),
);
refresh_networks_addresses(&mut self.interfaces);
}
}
pub(crate) struct NetworkDataInner {
/// Total number of bytes received over interface.
rx_bytes: u64,
old_rx_bytes: u64,
/// Total number of bytes transmitted over interface.
tx_bytes: u64,
old_tx_bytes: u64,
/// Total number of packets received.
rx_packets: u64,
old_rx_packets: u64,
/// Total number of packets transmitted.
tx_packets: u64,
old_tx_packets: 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.
rx_errors: u64,
old_rx_errors: u64,
/// similar to `rx_errors`
tx_errors: u64,
old_tx_errors: u64,
/// MAC address
pub(crate) mac_addr: MacAddr,
pub(crate) ip_networks: Vec<IpNetwork>,
/// Interface Maximum Transfer Unit (MTU)
mtu: u64,
// /// Indicates the number of compressed packets received by this
// /// network device. This value might only be relevant for interfaces
// /// that support packet compression (e.g: PPP).
// rx_compressed: usize,
// old_rx_compressed: usize,
// /// Indicates the number of transmitted compressed packets. Note
// /// this might only be relevant for devices that support
// /// compression (e.g: PPP).
// tx_compressed: usize,
// old_tx_compressed: usize,
/// Whether or not the above data has been updated during refresh
updated: bool,
}
impl NetworkDataInner {
pub(crate) fn received(&self) -> u64 {
self.rx_bytes.saturating_sub(self.old_rx_bytes)
}
pub(crate) fn total_received(&self) -> u64 {
self.rx_bytes
}
pub(crate) fn transmitted(&self) -> u64 {
self.tx_bytes.saturating_sub(self.old_tx_bytes)
}
pub(crate) fn total_transmitted(&self) -> u64 {
self.tx_bytes
}
pub(crate) fn packets_received(&self) -> u64 {
self.rx_packets.saturating_sub(self.old_rx_packets)
}
pub(crate) fn total_packets_received(&self) -> u64 {
self.rx_packets
}
pub(crate) fn packets_transmitted(&self) -> u64 {
self.tx_packets.saturating_sub(self.old_tx_packets)
}
pub(crate) fn total_packets_transmitted(&self) -> u64 {
self.tx_packets
}
pub(crate) fn errors_on_received(&self) -> u64 {
self.rx_errors.saturating_sub(self.old_rx_errors)
}
pub(crate) fn total_errors_on_received(&self) -> u64 {
self.rx_errors
}
pub(crate) fn errors_on_transmitted(&self) -> u64 {
self.tx_errors.saturating_sub(self.old_tx_errors)
}
pub(crate) fn total_errors_on_transmitted(&self) -> u64 {
self.tx_errors
}
pub(crate) fn mac_address(&self) -> MacAddr {
self.mac_addr
}
pub(crate) fn ip_networks(&self) -> &[IpNetwork] {
&self.ip_networks
}
pub(crate) fn mtu(&self) -> u64 {
self.mtu
}
}
#[cfg(test)]
mod test {
use super::refresh_networks_list_from_sysfs;
use std::collections::HashMap;
use std::fs;
#[test]
fn refresh_networks_list_add_interface() {
let sys_net_dir = tempfile::tempdir().expect("failed to create temporary directory");
fs::create_dir(sys_net_dir.path().join("itf1")).expect("failed to create subdirectory");
let mut interfaces = HashMap::new();
refresh_networks_list_from_sysfs(&mut interfaces, false, sys_net_dir.path());
assert_eq!(interfaces.keys().collect::<Vec<_>>(), ["itf1"]);
fs::create_dir(sys_net_dir.path().join("itf2")).expect("failed to create subdirectory");
refresh_networks_list_from_sysfs(&mut interfaces, false, sys_net_dir.path());
let mut itf_names: Vec<String> = interfaces.keys().map(|n| n.to_owned()).collect();
itf_names.sort();
assert_eq!(itf_names, ["itf1", "itf2"]);
}
#[test]
fn refresh_networks_list_remove_interface() {
let sys_net_dir = tempfile::tempdir().expect("failed to create temporary directory");
let itf1_dir = sys_net_dir.path().join("itf1");
let itf2_dir = sys_net_dir.path().join("itf2");
fs::create_dir(&itf1_dir).expect("failed to create subdirectory");
fs::create_dir(itf2_dir).expect("failed to create subdirectory");
let mut interfaces = HashMap::new();
refresh_networks_list_from_sysfs(&mut interfaces, false, sys_net_dir.path());
let mut itf_names: Vec<String> = interfaces.keys().map(|n| n.to_owned()).collect();
itf_names.sort();
assert_eq!(itf_names, ["itf1", "itf2"]);
fs::remove_dir(&itf1_dir).expect("failed to remove subdirectory");
refresh_networks_list_from_sysfs(&mut interfaces, true, sys_net_dir.path());
assert_eq!(interfaces.keys().collect::<Vec<_>>(), ["itf2"]);
}
}

1056
vendor/sysinfo/src/unix/linux/process.rs vendored Normal file

File diff suppressed because it is too large Load Diff

988
vendor/sysinfo/src/unix/linux/system.rs vendored Normal file
View File

@@ -0,0 +1,988 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::sys::cpu::{get_physical_core_count, CpusWrapper};
use crate::sys::process::{compute_cpu_usage, refresh_procs};
use crate::sys::utils::{get_all_utf8_data, to_u64};
use crate::{
Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind,
ProcessesToUpdate,
};
use libc::{self, c_char, sysconf, _SC_CLK_TCK, _SC_HOST_NAME_MAX, _SC_PAGESIZE};
use std::cmp::min;
use std::collections::HashMap;
use std::ffi::CStr;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::str::FromStr;
use std::sync::{atomic::AtomicIsize, OnceLock};
use std::time::Duration;
// This whole thing is to prevent having too many files open at once. It could be problematic
// for processes using a lot of files and using sysinfo at the same time.
pub(crate) fn remaining_files() -> &'static AtomicIsize {
static REMAINING_FILES: OnceLock<AtomicIsize> = OnceLock::new();
REMAINING_FILES.get_or_init(|| unsafe {
let mut limits = libc::rlimit {
rlim_cur: 0,
rlim_max: 0,
};
if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 {
// Most Linux system now defaults to 1024.
return AtomicIsize::new(1024 / 2);
}
// We save the value in case the update fails.
let current = limits.rlim_cur;
// The set the soft limit to the hard one.
limits.rlim_cur = limits.rlim_max;
// In this part, we leave minimum 50% of the available file descriptors to the process
// using sysinfo.
AtomicIsize::new(if libc::setrlimit(libc::RLIMIT_NOFILE, &limits) == 0 {
limits.rlim_cur / 2
} else {
current / 2
} as _)
})
}
declare_signals! {
libc::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::Poll => libc::SIGPOLL,
Signal::Power => libc::SIGPWR,
Signal::Sys => libc::SIGSYS,
}
#[doc = include_str!("../../../md_doc/supported_signals.md")]
pub const SUPPORTED_SIGNALS: &[crate::Signal] = supported_signals();
#[doc = include_str!("../../../md_doc/minimum_cpu_update_interval.md")]
pub const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(200);
pub(crate) fn get_max_nb_fds() -> isize {
unsafe {
let mut limits = libc::rlimit {
rlim_cur: 0,
rlim_max: 0,
};
if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 {
// Most Linux system now defaults to 1024.
1024 / 2
} else {
limits.rlim_max as isize / 2
}
}
}
fn boot_time() -> u64 {
if let Ok(buf) = File::open("/proc/stat").and_then(|mut f| {
let mut buf = Vec::new();
f.read_to_end(&mut buf)?;
Ok(buf)
}) {
let line = buf.split(|c| *c == b'\n').find(|l| l.starts_with(b"btime"));
if let Some(line) = line {
return line
.split(|x| *x == b' ')
.filter(|s| !s.is_empty())
.nth(1)
.map(to_u64)
.unwrap_or(0);
}
}
// Either we didn't find "btime" or "/proc/stat" wasn't available for some reason...
unsafe {
let mut up: libc::timespec = std::mem::zeroed();
if libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut up) == 0 {
up.tv_sec as u64
} else {
sysinfo_debug!("clock_gettime failed: boot time cannot be retrieve...");
0
}
}
}
pub(crate) struct SystemInfo {
pub(crate) page_size_b: u64,
pub(crate) clock_cycle: u64,
pub(crate) boot_time: u64,
}
impl SystemInfo {
fn new() -> Self {
unsafe {
Self {
page_size_b: sysconf(_SC_PAGESIZE) as _,
clock_cycle: sysconf(_SC_CLK_TCK) as _,
boot_time: boot_time(),
}
}
}
}
pub(crate) struct SystemInner {
process_list: HashMap<Pid, Process>,
mem_total: u64,
mem_free: u64,
mem_available: u64,
mem_buffers: u64,
mem_page_cache: u64,
mem_shmem: u64,
mem_slab_reclaimable: u64,
swap_total: u64,
swap_free: u64,
info: SystemInfo,
cpus: CpusWrapper,
}
impl SystemInner {
/// It is sometime possible that a CPU usage computation is bigger than
/// `"number of CPUs" * 100`.
///
/// To prevent that, we compute ahead of time this maximum value and ensure that processes'
/// CPU usage don't go over it.
fn get_max_process_cpu_usage(&self) -> f32 {
self.cpus.len() as f32 * 100.
}
fn update_procs_cpu(&mut self, refresh_kind: ProcessRefreshKind) {
if !refresh_kind.cpu() {
return;
}
self.cpus
.refresh_if_needed(true, CpuRefreshKind::nothing().with_cpu_usage());
if self.cpus.is_empty() {
sysinfo_debug!("cannot compute processes CPU usage: no CPU found...");
return;
}
let (new, old) = self.cpus.get_global_raw_times();
let total_time = if old > new { 1 } else { new - old };
let total_time = total_time as f32 / self.cpus.len() as f32;
let max_value = self.get_max_process_cpu_usage();
for proc_ in self.process_list.values_mut() {
compute_cpu_usage(&mut proc_.inner, total_time, max_value);
}
}
fn refresh_cpus(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) {
self.cpus.refresh(only_update_global_cpu, refresh_kind);
}
}
impl SystemInner {
pub(crate) fn new() -> Self {
Self {
process_list: HashMap::new(),
mem_total: 0,
mem_free: 0,
mem_available: 0,
mem_buffers: 0,
mem_page_cache: 0,
mem_shmem: 0,
mem_slab_reclaimable: 0,
swap_total: 0,
swap_free: 0,
cpus: CpusWrapper::new(),
info: SystemInfo::new(),
}
}
pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) {
if !refresh_kind.ram() && !refresh_kind.swap() {
return;
}
let mut mem_available_found = false;
read_table("/proc/meminfo", ':', |key, value_kib| {
let field = match key {
"MemTotal" => &mut self.mem_total,
"MemFree" => &mut self.mem_free,
"MemAvailable" => {
mem_available_found = true;
&mut self.mem_available
}
"Buffers" => &mut self.mem_buffers,
"Cached" => &mut self.mem_page_cache,
"Shmem" => &mut self.mem_shmem,
"SReclaimable" => &mut self.mem_slab_reclaimable,
"SwapTotal" => &mut self.swap_total,
"SwapFree" => &mut self.swap_free,
_ => return,
};
// /proc/meminfo reports KiB, though it says "kB". Convert it.
*field = value_kib.saturating_mul(1_024);
});
// Linux < 3.14 may not have MemAvailable in /proc/meminfo
// So it should fallback to the old way of estimating available memory
// https://github.com/KittyKatt/screenFetch/issues/386#issuecomment-249312716
if !mem_available_found {
self.mem_available = self
.mem_free
.saturating_add(self.mem_buffers)
.saturating_add(self.mem_page_cache)
.saturating_add(self.mem_slab_reclaimable)
.saturating_sub(self.mem_shmem);
}
}
pub(crate) fn cgroup_limits(&self) -> Option<crate::CGroupLimits> {
crate::CGroupLimits::new(self)
}
pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) {
self.refresh_cpus(false, refresh_kind);
}
pub(crate) fn refresh_processes_specifics(
&mut self,
processes_to_update: ProcessesToUpdate<'_>,
refresh_kind: ProcessRefreshKind,
) -> usize {
let uptime = Self::uptime();
let nb_updated = refresh_procs(
&mut self.process_list,
Path::new("/proc"),
uptime,
&self.info,
processes_to_update,
refresh_kind,
);
self.update_procs_cpu(refresh_kind);
nb_updated
}
// COMMON PART
//
// Need to be moved into a "common" file to avoid duplication.
pub(crate) fn processes(&self) -> &HashMap<Pid, Process> {
&self.process_list
}
pub(crate) fn processes_mut(&mut self) -> &mut HashMap<Pid, Process> {
&mut self.process_list
}
pub(crate) fn process(&self, pid: Pid) -> Option<&Process> {
self.process_list.get(&pid)
}
pub(crate) fn global_cpu_usage(&self) -> f32 {
self.cpus.global_cpu.usage()
}
pub(crate) fn cpus(&self) -> &[Cpu] {
&self.cpus.cpus
}
pub(crate) fn total_memory(&self) -> u64 {
self.mem_total
}
pub(crate) fn free_memory(&self) -> u64 {
self.mem_free
}
pub(crate) fn available_memory(&self) -> u64 {
self.mem_available
}
pub(crate) fn used_memory(&self) -> u64 {
self.mem_total - self.mem_available
}
pub(crate) fn total_swap(&self) -> u64 {
self.swap_total
}
pub(crate) fn free_swap(&self) -> u64 {
self.swap_free
}
// need to be checked
pub(crate) fn used_swap(&self) -> u64 {
self.swap_total - self.swap_free
}
pub(crate) fn uptime() -> u64 {
let content = get_all_utf8_data("/proc/uptime", 50).unwrap_or_default();
content
.split('.')
.next()
.and_then(|t| t.parse().ok())
.unwrap_or_default()
}
pub(crate) fn boot_time() -> u64 {
boot_time()
}
pub(crate) fn load_average() -> LoadAvg {
let mut s = String::new();
if File::open("/proc/loadavg")
.and_then(|mut f| f.read_to_string(&mut s))
.is_err()
{
return LoadAvg::default();
}
let loads = s
.trim()
.split(' ')
.take(3)
.map(|val| val.parse::<f64>().unwrap())
.collect::<Vec<f64>>();
LoadAvg {
one: loads[0],
five: loads[1],
fifteen: loads[2],
}
}
#[cfg(not(target_os = "android"))]
pub(crate) fn name() -> Option<String> {
get_system_info_linux(
InfoType::Name,
Path::new("/etc/os-release"),
Path::new("/etc/lsb-release"),
)
}
#[cfg(target_os = "android")]
pub(crate) fn name() -> Option<String> {
get_system_info_android(InfoType::Name)
}
#[cfg(not(target_os = "android"))]
pub(crate) fn long_os_version() -> Option<String> {
let mut long_name = "Linux".to_owned();
let distro_name = Self::name();
let distro_version = Self::os_version();
if let Some(distro_version) = &distro_version {
// "Linux (Ubuntu 24.04)"
long_name.push_str(" (");
long_name.push_str(distro_name.as_deref().unwrap_or("unknown"));
long_name.push(' ');
long_name.push_str(distro_version);
long_name.push(')');
} else if let Some(distro_name) = &distro_name {
// "Linux (Ubuntu)"
long_name.push_str(" (");
long_name.push_str(distro_name);
long_name.push(')');
}
Some(long_name)
}
#[cfg(target_os = "android")]
pub(crate) fn long_os_version() -> Option<String> {
let mut long_name = "Android".to_owned();
if let Some(os_version) = Self::os_version() {
long_name.push(' ');
long_name.push_str(&os_version);
}
// Android's name() is extracted from the system property "ro.product.model"
// which is documented as "The end-user-visible name for the end product."
// So this produces a long_os_version like "Android 15 on Pixel 9 Pro".
if let Some(product_name) = Self::name() {
long_name.push_str(" on ");
long_name.push_str(&product_name);
}
Some(long_name)
}
pub(crate) fn host_name() -> Option<String> {
unsafe {
let hostname_max = sysconf(_SC_HOST_NAME_MAX);
let mut buffer = vec![0_u8; hostname_max as usize];
if libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len()) == 0 {
if let Some(pos) = buffer.iter().position(|x| *x == 0) {
// Shrink buffer to terminate the null bytes
buffer.resize(pos, 0);
}
String::from_utf8(buffer).ok()
} else {
sysinfo_debug!("gethostname failed: hostname cannot be retrieved...");
None
}
}
}
pub(crate) fn kernel_version() -> Option<String> {
let mut raw = std::mem::MaybeUninit::<libc::utsname>::zeroed();
unsafe {
if libc::uname(raw.as_mut_ptr()) == 0 {
let info = raw.assume_init();
let release = info
.release
.iter()
.filter(|c| **c != 0)
.map(|c| *c as u8 as char)
.collect::<String>();
Some(release)
} else {
None
}
}
}
#[cfg(not(target_os = "android"))]
pub(crate) fn os_version() -> Option<String> {
get_system_info_linux(
InfoType::OsVersion,
Path::new("/etc/os-release"),
Path::new("/etc/lsb-release"),
)
}
#[cfg(target_os = "android")]
pub(crate) fn os_version() -> Option<String> {
get_system_info_android(InfoType::OsVersion)
}
#[cfg(not(target_os = "android"))]
pub(crate) fn distribution_id() -> String {
get_system_info_linux(
InfoType::DistributionID,
Path::new("/etc/os-release"),
Path::new(""),
)
.unwrap_or_else(|| std::env::consts::OS.to_owned())
}
#[cfg(target_os = "android")]
pub(crate) fn distribution_id() -> String {
// Currently get_system_info_android doesn't support InfoType::DistributionID and always
// returns None. This call is done anyway for consistency with non-Android implementation
// and to suppress dead-code warning for DistributionID on Android.
get_system_info_android(InfoType::DistributionID)
.unwrap_or_else(|| std::env::consts::OS.to_owned())
}
#[cfg(not(target_os = "android"))]
pub(crate) fn distribution_id_like() -> Vec<String> {
system_info_as_list(get_system_info_linux(
InfoType::DistributionIDLike,
Path::new("/etc/os-release"),
Path::new(""),
))
}
#[cfg(target_os = "android")]
pub(crate) fn distribution_id_like() -> Vec<String> {
// Currently get_system_info_android doesn't support InfoType::DistributionIDLike and always
// returns None. This call is done anyway for consistency with non-Android implementation
// and to suppress dead-code warning for DistributionIDLike on Android.
system_info_as_list(get_system_info_android(InfoType::DistributionIDLike))
}
#[cfg(not(target_os = "android"))]
pub(crate) fn kernel_name() -> Option<&'static str> {
Some("Linux")
}
#[cfg(target_os = "android")]
pub(crate) fn kernel_name() -> Option<&'static str> {
Some("Android kernel")
}
pub(crate) fn cpu_arch() -> Option<String> {
let mut raw = std::mem::MaybeUninit::<libc::utsname>::uninit();
unsafe {
if libc::uname(raw.as_mut_ptr()) != 0 {
return None;
}
let info = raw.assume_init();
// Converting `&[i8]` to `&[u8]`.
let machine: &[u8] =
std::slice::from_raw_parts(info.machine.as_ptr() as *const _, info.machine.len());
CStr::from_bytes_until_nul(machine)
.ok()
.and_then(|res| match res.to_str() {
Ok(arch) => Some(arch.to_string()),
Err(_) => None,
})
}
}
pub(crate) fn physical_core_count() -> Option<usize> {
get_physical_core_count()
}
pub(crate) fn refresh_cpu_list(&mut self, refresh_kind: CpuRefreshKind) {
self.cpus = CpusWrapper::new();
self.refresh_cpu_specifics(refresh_kind);
}
}
fn read_u64(filename: &str) -> Option<u64> {
let result = get_all_utf8_data(filename, 16_635)
.ok()
.and_then(|d| u64::from_str(d.trim()).ok());
if result.is_none() {
sysinfo_debug!("Failed to read u64 in filename {}", filename);
}
result
}
fn read_table<F>(filename: &str, colsep: char, mut f: F)
where
F: FnMut(&str, u64),
{
if let Ok(content) = get_all_utf8_data(filename, 16_635) {
content
.split('\n')
.flat_map(|line| {
let mut split = line.split(colsep);
let key = split.next()?;
let value = split.next()?;
let value0 = value.trim_start().split(' ').next()?;
let value0_u64 = u64::from_str(value0).ok()?;
Some((key, value0_u64))
})
.for_each(|(k, v)| f(k, v));
}
}
fn read_table_key(filename: &str, target_key: &str, colsep: char) -> Option<u64> {
if let Ok(content) = get_all_utf8_data(filename, 16_635) {
return content.split('\n').find_map(|line| {
let mut split = line.split(colsep);
let key = split.next()?;
if key != target_key {
return None;
}
let value = split.next()?;
let value0 = value.trim_start().split(' ').next()?;
u64::from_str(value0).ok()
});
}
None
}
impl crate::CGroupLimits {
fn new(sys: &SystemInner) -> Option<Self> {
assert!(
sys.mem_total != 0,
"You need to call System::refresh_memory before trying to get cgroup limits!",
);
if let (Some(mem_cur), Some(mem_max), Some(mem_rss)) = (
// cgroups v2
read_u64("/sys/fs/cgroup/memory.current"),
// memory.max contains `max` when no limit is set.
read_u64("/sys/fs/cgroup/memory.max").or(Some(u64::MAX)),
read_table_key("/sys/fs/cgroup/memory.stat", "anon", ' '),
) {
let mut limits = Self {
total_memory: sys.mem_total,
free_memory: sys.mem_free,
free_swap: sys.swap_free,
rss: mem_rss,
};
limits.total_memory = min(mem_max, sys.mem_total);
limits.free_memory = limits.total_memory.saturating_sub(mem_cur);
if let Some(swap_cur) = read_u64("/sys/fs/cgroup/memory.swap.current") {
limits.free_swap = sys.swap_total.saturating_sub(swap_cur);
}
Some(limits)
} else if let (Some(mem_cur), Some(mem_max), Some(mem_rss)) = (
// cgroups v1
read_u64("/sys/fs/cgroup/memory/memory.usage_in_bytes"),
read_u64("/sys/fs/cgroup/memory/memory.limit_in_bytes"),
read_table_key("/sys/fs/cgroup/memory/memory.stat", "total_rss", ' '),
) {
let mut limits = Self {
total_memory: sys.mem_total,
free_memory: sys.mem_free,
free_swap: sys.swap_free,
rss: mem_rss,
};
limits.total_memory = min(mem_max, sys.mem_total);
limits.free_memory = limits.total_memory.saturating_sub(mem_cur);
Some(limits)
} else {
None
}
}
}
#[derive(PartialEq, Eq)]
enum InfoType {
/// The end-user friendly name of:
/// - Android: The device model
/// - Linux: The distributions name
Name,
OsVersion,
/// Machine-parseable ID of a distribution, see
/// https://www.freedesktop.org/software/systemd/man/os-release.html#ID=
DistributionID,
/// Machine-parseable ID_LIKE of related distributions, see
/// <https://www.freedesktop.org/software/systemd/man/latest/os-release.html#ID_LIKE=>
DistributionIDLike,
}
#[cfg(not(target_os = "android"))]
fn get_system_info_linux(info: InfoType, path: &Path, fallback_path: &Path) -> Option<String> {
if let Ok(buf) = File::open(path).and_then(|mut f| {
let mut buf = String::new();
f.read_to_string(&mut buf)?;
Ok(buf)
}) {
let info_str = match info {
InfoType::Name => "NAME=",
InfoType::OsVersion => "VERSION_ID=",
InfoType::DistributionID => "ID=",
InfoType::DistributionIDLike => "ID_LIKE=",
};
for line in buf.lines() {
if let Some(stripped) = line.strip_prefix(info_str) {
return Some(stripped.replace('"', ""));
}
}
}
// Fallback to `/etc/lsb-release` file for systems where VERSION_ID is not included.
// VERSION_ID is not required in the `/etc/os-release` file
// per https://www.linux.org/docs/man5/os-release.html
// If this fails for some reason, fallback to None
let buf = File::open(fallback_path)
.and_then(|mut f| {
let mut buf = String::new();
f.read_to_string(&mut buf)?;
Ok(buf)
})
.ok()?;
let info_str = match info {
InfoType::OsVersion => "DISTRIB_RELEASE=",
InfoType::Name => "DISTRIB_ID=",
InfoType::DistributionID => {
// lsb-release is inconsistent with os-release and unsupported.
return None;
}
InfoType::DistributionIDLike => {
// lsb-release doesn't support ID_LIKE.
return None;
}
};
for line in buf.lines() {
if let Some(stripped) = line.strip_prefix(info_str) {
return Some(stripped.replace('"', ""));
}
}
None
}
/// Returns a system info value as a list of strings.
/// Absence of a value is treated as an empty list.
fn system_info_as_list(sysinfo: Option<String>) -> Vec<String> {
match sysinfo {
Some(value) => value.split_ascii_whitespace().map(String::from).collect(),
// For list fields absence of a field is equivalent to an empty list.
None => Vec::new(),
}
}
#[cfg(target_os = "android")]
fn get_system_info_android(info: InfoType) -> Option<String> {
// https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/Build.java#58
let name: &'static [u8] = match info {
InfoType::Name => b"ro.product.model\0",
InfoType::OsVersion => b"ro.build.version.release\0",
InfoType::DistributionID => {
// Not supported.
return None;
}
InfoType::DistributionIDLike => {
// Not supported.
return None;
}
};
let mut value_buffer = vec![0u8; libc::PROP_VALUE_MAX as usize];
unsafe {
let len = libc::__system_property_get(
name.as_ptr() as *const c_char,
value_buffer.as_mut_ptr() as *mut c_char,
);
if len != 0 {
if let Some(pos) = value_buffer.iter().position(|c| *c == 0) {
value_buffer.resize(pos, 0);
}
String::from_utf8(value_buffer).ok()
} else {
None
}
}
}
#[cfg(test)]
mod test {
#[cfg(target_os = "android")]
use super::get_system_info_android;
#[cfg(not(target_os = "android"))]
use super::get_system_info_linux;
use super::read_table;
use super::read_table_key;
use super::system_info_as_list;
use super::InfoType;
use std::collections::HashMap;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_read_table() {
// Create a temporary file with test content
let mut file = NamedTempFile::new().unwrap();
writeln!(file, "KEY1:100 kB").unwrap();
writeln!(file, "KEY2:200 kB").unwrap();
writeln!(file, "KEY3:300 kB").unwrap();
writeln!(file, "KEY4:invalid").unwrap();
let file_path = file.path().to_str().unwrap();
// Test reading the table
let mut result = HashMap::new();
read_table(file_path, ':', |key, value| {
result.insert(key.to_string(), value);
});
assert_eq!(result.get("KEY1"), Some(&100));
assert_eq!(result.get("KEY2"), Some(&200));
assert_eq!(result.get("KEY3"), Some(&300));
assert_eq!(result.get("KEY4"), None);
// Test with different separator and units
let mut file = NamedTempFile::new().unwrap();
writeln!(file, "KEY1 400 MB").unwrap();
writeln!(file, "KEY2 500 GB").unwrap();
writeln!(file, "KEY3 600").unwrap();
let file_path = file.path().to_str().unwrap();
let mut result = HashMap::new();
read_table(file_path, ' ', |key, value| {
result.insert(key.to_string(), value);
});
assert_eq!(result.get("KEY1"), Some(&400));
assert_eq!(result.get("KEY2"), Some(&500));
assert_eq!(result.get("KEY3"), Some(&600));
// Test with empty file
let file = NamedTempFile::new().unwrap();
let file_path = file.path().to_str().unwrap();
let mut result = HashMap::new();
read_table(file_path, ':', |key, value| {
result.insert(key.to_string(), value);
});
assert!(result.is_empty());
// Test with non-existent file
let mut result = HashMap::new();
read_table("/nonexistent/file", ':', |key, value| {
result.insert(key.to_string(), value);
});
assert!(result.is_empty());
}
#[test]
fn test_read_table_key() {
// Create a temporary file with test content
let mut file = NamedTempFile::new().unwrap();
writeln!(file, "KEY1:100 kB").unwrap();
writeln!(file, "KEY2:200 kB").unwrap();
writeln!(file, "KEY3:300 kB").unwrap();
let file_path = file.path().to_str().unwrap();
// Test existing keys
assert_eq!(read_table_key(file_path, "KEY1", ':'), Some(100));
assert_eq!(read_table_key(file_path, "KEY2", ':'), Some(200));
assert_eq!(read_table_key(file_path, "KEY3", ':'), Some(300));
// Test non-existent key
assert_eq!(read_table_key(file_path, "KEY4", ':'), None);
// Test with different separator
let mut file = NamedTempFile::new().unwrap();
writeln!(file, "KEY1 400 kB").unwrap();
writeln!(file, "KEY2 500 kB").unwrap();
let file_path = file.path().to_str().unwrap();
assert_eq!(read_table_key(file_path, "KEY1", ' '), Some(400));
assert_eq!(read_table_key(file_path, "KEY2", ' '), Some(500));
// Test with invalid file
assert_eq!(read_table_key("/nonexistent/file", "KEY1", ':'), None);
}
#[test]
#[cfg(target_os = "android")]
fn lsb_release_fallback_android() {
assert!(get_system_info_android(InfoType::OsVersion).is_some());
assert!(get_system_info_android(InfoType::Name).is_some());
assert!(get_system_info_android(InfoType::DistributionID).is_none());
assert!(get_system_info_android(InfoType::DistributionIDLike).is_none());
}
#[test]
#[cfg(not(target_os = "android"))]
fn lsb_release_fallback_not_android() {
use std::path::Path;
let dir = tempfile::tempdir().expect("failed to create temporary directory");
let tmp1 = dir.path().join("tmp1");
let tmp2 = dir.path().join("tmp2");
// /etc/os-release
std::fs::write(
&tmp1,
r#"NAME="Ubuntu"
VERSION="20.10 (Groovy Gorilla)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.10"
VERSION_ID="20.10"
VERSION_CODENAME=groovy
UBUNTU_CODENAME=groovy
"#,
)
.expect("Failed to create tmp1");
// /etc/lsb-release
std::fs::write(
&tmp2,
r#"DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.10
DISTRIB_CODENAME=groovy
DISTRIB_DESCRIPTION="Ubuntu 20.10"
"#,
)
.expect("Failed to create tmp2");
// Check for the "normal" path: "/etc/os-release"
assert_eq!(
get_system_info_linux(InfoType::OsVersion, &tmp1, Path::new("")),
Some("20.10".to_owned())
);
assert_eq!(
get_system_info_linux(InfoType::Name, &tmp1, Path::new("")),
Some("Ubuntu".to_owned())
);
assert_eq!(
get_system_info_linux(InfoType::DistributionID, &tmp1, Path::new("")),
Some("ubuntu".to_owned())
);
assert_eq!(
get_system_info_linux(InfoType::DistributionIDLike, &tmp1, Path::new("")),
Some("debian".to_owned())
);
// Check for the "fallback" path: "/etc/lsb-release"
assert_eq!(
get_system_info_linux(InfoType::OsVersion, Path::new(""), &tmp2),
Some("20.10".to_owned())
);
assert_eq!(
get_system_info_linux(InfoType::Name, Path::new(""), &tmp2),
Some("Ubuntu".to_owned())
);
assert_eq!(
get_system_info_linux(InfoType::DistributionID, Path::new(""), &tmp2),
None
);
assert_eq!(
get_system_info_linux(InfoType::DistributionIDLike, Path::new(""), &tmp2),
None
);
}
#[test]
fn test_system_info_as_list() {
// No value.
assert_eq!(system_info_as_list(None), Vec::<String>::new());
// Empty value.
assert_eq!(
system_info_as_list(Some("".to_string())),
Vec::<String>::new(),
);
// Whitespaces only.
assert_eq!(
system_info_as_list(Some(" ".to_string())),
Vec::<String>::new(),
);
// Single value.
assert_eq!(
system_info_as_list(Some("debian".to_string())),
vec!["debian".to_string()],
);
// Multiple values.
assert_eq!(
system_info_as_list(Some("rhel fedora".to_string())),
vec!["rhel".to_string(), "fedora".to_string()],
);
// Multiple spaces.
assert_eq!(
system_info_as_list(Some("rhel fedora".to_string())),
vec!["rhel".to_string(), "fedora".to_string()],
);
}
}

105
vendor/sysinfo/src/unix/linux/utils.rs vendored Normal file
View File

@@ -0,0 +1,105 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[cfg(any(feature = "disk", feature = "system"))]
use std::fs::File;
#[cfg(any(feature = "disk", feature = "system"))]
use std::io::{self, Read, Seek};
#[cfg(any(feature = "disk", feature = "system"))]
use std::path::Path;
#[cfg(feature = "system")]
pub(crate) fn get_all_data_from_file(file: &mut File, size: usize) -> io::Result<Vec<u8>> {
let mut buf = Vec::with_capacity(size);
file.rewind()?;
file.read_to_end(&mut buf)?;
Ok(buf)
}
#[cfg(any(feature = "disk", feature = "system"))]
pub(crate) fn get_all_utf8_data_from_file(file: &mut File, size: usize) -> io::Result<String> {
let mut buf = String::with_capacity(size);
file.rewind()?;
file.read_to_string(&mut buf)?;
Ok(buf)
}
#[cfg(any(feature = "disk", feature = "system"))]
pub(crate) fn get_all_utf8_data<P: AsRef<Path>>(file_path: P, size: usize) -> io::Result<String> {
let mut file = File::open(file_path.as_ref())?;
get_all_utf8_data_from_file(&mut file, size)
}
#[cfg(feature = "system")]
#[allow(clippy::useless_conversion)]
pub(crate) fn realpath(path: &Path) -> Option<std::path::PathBuf> {
match std::fs::read_link(path) {
Ok(path) => Some(path),
Err(_e) => {
sysinfo_debug!("failed to get real path for {:?}: {:?}", path, _e);
None
}
}
}
/// This type is used in `retrieve_all_new_process_info` because we have a "parent" path and
/// from it, we `pop`/`join` every time because it's more memory efficient than using `Path::join`.
#[cfg(feature = "system")]
pub(crate) struct PathHandler(std::path::PathBuf);
#[cfg(feature = "system")]
impl PathHandler {
pub(crate) fn new(path: &Path) -> Self {
// `path` is the "parent" for all paths which will follow so we add a fake element at
// the end since every `PathHandler::join` call will first call `pop` internally.
Self(path.join("a"))
}
pub(crate) fn as_path(&self) -> &Path {
&self.0
}
}
#[cfg(feature = "system")]
pub(crate) trait PathPush {
fn join(&mut self, p: &str) -> &Path;
}
#[cfg(feature = "system")]
impl PathPush for PathHandler {
fn join(&mut self, p: &str) -> &Path {
self.0.pop();
self.0.push(p);
self.as_path()
}
}
// This implementation allows to skip one allocation that is done in `PathHandler`.
#[cfg(feature = "system")]
impl PathPush for std::path::PathBuf {
fn join(&mut self, p: &str) -> &Path {
self.push(p);
self.as_path()
}
}
#[cfg(feature = "system")]
pub(crate) fn to_u64(v: &[u8]) -> u64 {
let mut x = 0;
for c in v {
x *= 10;
x += u64::from(c - b'0');
}
x
}
/// Converts a path to a NUL-terminated `Vec<u8>` suitable for use with C functions.
#[cfg(feature = "disk")]
pub(crate) fn to_cpath(path: &std::path::Path) -> Vec<u8> {
use std::{ffi::OsStr, os::unix::ffi::OsStrExt};
let path_os: &OsStr = path.as_ref();
let mut cpath = path_os.as_bytes().to_vec();
cpath.push(0);
cpath
}

70
vendor/sysinfo/src/unix/mod.rs vendored Normal file
View File

@@ -0,0 +1,70 @@
// Take a look at the license at the top of the repository in the LICENSE file.
cfg_if! {
if #[cfg(any(target_os = "macos", target_os = "ios"))] {
pub(crate) mod apple;
pub(crate) use apple as sys;
#[allow(unused_imports)]
pub(crate) use libc::__error as libc_errno;
} else if #[cfg(any(target_os = "linux", target_os = "android"))] {
pub(crate) mod linux;
pub(crate) use linux as sys;
#[cfg(target_os = "linux")]
#[allow(unused_imports)]
pub(crate) use libc::__errno_location as libc_errno;
#[cfg(target_os = "android")]
#[allow(unused_imports)]
pub(crate) use libc::__errno as libc_errno;
} else if #[cfg(target_os = "freebsd")] {
pub(crate) mod freebsd;
pub(crate) use freebsd as sys;
#[allow(unused_imports)]
pub(crate) use libc::__error as libc_errno;
} else {
compile_error!("Invalid cfg!");
}
if #[cfg(feature = "disk")] {
pub(crate) struct DisksInner {
pub(crate) disks: Vec<crate::Disk>,
}
impl DisksInner {
pub(crate) fn from_vec(disks: Vec<crate::Disk>) -> Self {
Self { disks }
}
pub(crate) fn into_vec(self) -> Vec<crate::Disk> {
self.disks
}
}
}
if #[cfg(feature = "network")] {
pub(crate) mod network_helper;
}
if #[cfg(feature = "user")] {
pub(crate) mod users;
pub(crate) mod groups;
}
}
pub(crate) mod utils;
// Make formattable by rustfmt.
#[cfg(any())]
mod apple;
#[cfg(any())]
mod freebsd;
#[cfg(any())]
mod groups;
#[cfg(any())]
mod linux;
#[cfg(any())]
mod network_helper;
#[cfg(any())]
mod users;

View File

@@ -0,0 +1,393 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use std::collections::HashMap;
use std::collections::HashSet;
use std::ffi::CStr;
use std::mem::MaybeUninit;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
use std::os::raw::c_char;
use std::ptr::null_mut;
use std::str::from_utf8_unchecked;
use std::{io, mem};
use crate::{IpNetwork, MacAddr};
/// This iterator yields an interface name and address.
pub(crate) struct InterfaceAddressIterator {
/// Pointer to the current `ifaddrs` struct.
ifap: *mut libc::ifaddrs,
/// Pointer to the first element in linked list.
buf: *mut libc::ifaddrs,
}
impl Iterator for InterfaceAddressIterator {
type Item = (String, MacAddr);
fn next(&mut self) -> Option<Self::Item> {
unsafe {
while !self.ifap.is_null() {
// advance the pointer until a MAC address is found
let ifap = self.ifap;
self.ifap = (*ifap).ifa_next;
if let Some(addr) = parse_interface_address(ifap) {
let ifa_name = (*ifap).ifa_name;
if ifa_name.is_null() {
continue;
}
// libc::IFNAMSIZ + 6
// This size refers to ./apple/network.rs:75
let mut name = vec![0u8; libc::IFNAMSIZ + 6];
libc::strcpy(name.as_mut_ptr() as _, (*ifap).ifa_name);
name.set_len(libc::strlen((*ifap).ifa_name));
let name = String::from_utf8_unchecked(name);
return Some((name, addr));
}
}
None
}
}
}
impl Drop for InterfaceAddressIterator {
fn drop(&mut self) {
unsafe {
libc::freeifaddrs(self.buf);
}
}
}
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "ios"))]
impl From<&libc::sockaddr_dl> for MacAddr {
fn from(value: &libc::sockaddr_dl) -> Self {
let sdl_data = value.sdl_data;
// interface name length, NO trailing 0
let sdl_nlen = value.sdl_nlen as usize;
// make sure that it is never out of bound
if sdl_nlen + 5 < 12 {
MacAddr([
sdl_data[sdl_nlen] as u8,
sdl_data[sdl_nlen + 1] as u8,
sdl_data[sdl_nlen + 2] as u8,
sdl_data[sdl_nlen + 3] as u8,
sdl_data[sdl_nlen + 4] as u8,
sdl_data[sdl_nlen + 5] as u8,
])
} else {
MacAddr::UNSPECIFIED
}
}
}
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "ios"))]
unsafe fn parse_interface_address(ifap: *const libc::ifaddrs) -> Option<MacAddr> {
let sock_addr = (*ifap).ifa_addr;
if sock_addr.is_null() {
return None;
}
match (*sock_addr).sa_family as libc::c_int {
libc::AF_LINK => {
let addr = sock_addr as *const libc::sockaddr_dl;
Some(MacAddr::from(&*addr))
}
_ => None,
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
unsafe fn parse_interface_address(ifap: *const libc::ifaddrs) -> Option<MacAddr> {
use libc::sockaddr_ll;
let sock_addr = (*ifap).ifa_addr;
if sock_addr.is_null() {
return None;
}
match (*sock_addr).sa_family as libc::c_int {
libc::AF_PACKET => {
let addr = sock_addr as *const sockaddr_ll;
// Take the first 6 bytes
let [addr @ .., _, _] = (*addr).sll_addr;
Some(MacAddr(addr))
}
_ => None,
}
}
/// Return an iterator on (interface_name, address) pairs
pub(crate) unsafe fn get_interface_address() -> Result<InterfaceAddressIterator, String> {
let mut ifap = null_mut();
if retry_eintr!(libc::getifaddrs(&mut ifap)) == 0 && !ifap.is_null() {
Ok(InterfaceAddressIterator { ifap, buf: ifap })
} else {
Err("failed to call getifaddrs()".to_string())
}
}
pub(crate) unsafe fn get_interface_ip_networks() -> HashMap<String, HashSet<IpNetwork>> {
let mut ifaces: HashMap<String, HashSet<IpNetwork>> = HashMap::new();
let mut addrs: MaybeUninit<*mut libc::ifaddrs> = MaybeUninit::uninit();
// Safety: addrs.as_mut_ptr() is valid, it points to addrs.
if libc::getifaddrs(addrs.as_mut_ptr()) != 0 {
sysinfo_debug!("Failed to operate libc::getifaddrs as ifaddrs Uninitialized");
return ifaces;
}
// Safety: If there was an error, we would have already returned.
// Therefore, getifaddrs has initialized `addrs`.
let addrs = addrs.assume_init();
let mut addr = addrs;
while !addr.is_null() {
// Safety: We assume that addr is valid for the lifetime of this loop
// body, and is not mutated.
let addr_ref: &libc::ifaddrs = &*addr;
let c_str = addr_ref.ifa_name as *const c_char;
// Safety: ifa_name is a null terminated interface name
let bytes = CStr::from_ptr(c_str).to_bytes();
// Safety: Interfaces on unix must be valid UTF-8
let mut name = from_utf8_unchecked(bytes).to_owned();
// Interfaces names may be formatted as <interface name>:<sub-interface index>
if name.contains(':') {
if let Some(interface_name) = name.split(':').next() {
name = interface_name.to_string()
} else {
// The sub-interface is malformed, skipping to the next addr
addr = addr_ref.ifa_next;
continue;
}
}
let ip = sockaddr_to_network_addr(addr_ref.ifa_addr as *const libc::sockaddr);
let netmask = sockaddr_to_network_addr(addr_ref.ifa_netmask as *const libc::sockaddr);
let prefix = netmask
.and_then(|netmask| ip_mask_to_prefix(netmask).ok())
.unwrap_or(0);
if let Some(ip) = ip {
ifaces
.entry(name)
.and_modify(|values| {
values.insert(IpNetwork { addr: ip, prefix });
})
.or_insert(HashSet::from([IpNetwork { addr: ip, prefix }]));
}
addr = addr_ref.ifa_next;
}
// Safety: addrs has been previously allocated through getifaddrs
libc::freeifaddrs(addrs);
ifaces
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> Option<IpAddr> {
unsafe {
if sa.is_null() || (*sa).sa_family as libc::c_int == libc::AF_PACKET {
None
} else {
let addr = sockaddr_to_addr(
&(sa as *const libc::sockaddr_storage).read_unaligned(),
mem::size_of::<libc::sockaddr_storage>(),
);
match addr {
Ok(SocketAddr::V4(sa)) => Some(IpAddr::V4(*sa.ip())),
Ok(SocketAddr::V6(sa)) => Some(IpAddr::V6(*sa.ip())),
_ => None,
}
}
}
}
#[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
pub type InAddrType = libc::c_uint;
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
pub type InAddrType = libc::c_ulonglong;
fn sockaddr_to_addr(storage: &libc::sockaddr_storage, len: usize) -> io::Result<SocketAddr> {
match storage.ss_family as libc::c_int {
libc::AF_INET => {
assert!(len >= mem::size_of::<libc::sockaddr_in>());
let storage: &libc::sockaddr_in = unsafe { mem::transmute(storage) };
let ip = (storage.sin_addr.s_addr as InAddrType).to_be();
let a = (ip >> 24) as u8;
let b = (ip >> 16) as u8;
let c = (ip >> 8) as u8;
let d = ip as u8;
let sockaddrv4 = SocketAddrV4::new(Ipv4Addr::new(a, b, c, d), storage.sin_port.to_be());
Ok(SocketAddr::V4(sockaddrv4))
}
libc::AF_INET6 => {
assert!(len >= mem::size_of::<libc::sockaddr_in6>());
let storage: &libc::sockaddr_in6 = unsafe { mem::transmute(storage) };
let arr: [u16; 8] = unsafe { mem::transmute(storage.sin6_addr.s6_addr) };
let ip = Ipv6Addr::new(
arr[0].to_be(),
arr[1].to_be(),
arr[2].to_be(),
arr[3].to_be(),
arr[4].to_be(),
arr[5].to_be(),
arr[6].to_be(),
arr[7].to_be(),
);
Ok(SocketAddr::V6(SocketAddrV6::new(
ip,
storage.sin6_port.to_be(),
u32::from_be(storage.sin6_flowinfo),
storage.sin6_scope_id,
)))
}
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
"expected IPv4 or IPv6 socket",
)),
}
}
#[cfg(any(
target_os = "openbsd",
target_os = "freebsd",
target_os = "netbsd",
target_os = "illumos",
target_os = "solaris",
target_os = "macos",
target_os = "ios"
))]
fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> Option<IpAddr> {
unsafe {
if sa.is_null() || (*sa).sa_family as libc::c_int == 18 {
None
} else {
let addr = sockaddr_to_addr(
&(sa as *const libc::sockaddr_storage).read_unaligned(),
mem::size_of::<libc::sockaddr_storage>(),
);
match addr {
Ok(SocketAddr::V4(sa)) => Some(IpAddr::V4(*sa.ip())),
Ok(SocketAddr::V6(sa)) => Some(IpAddr::V6(*sa.ip())),
_ => None,
}
}
}
}
pub(crate) fn ip_mask_to_prefix(mask: IpAddr) -> Result<u8, &'static str> {
match mask {
IpAddr::V4(mask) => ipv4_mask_to_prefix(mask),
IpAddr::V6(mask) => ipv6_mask_to_prefix(mask),
}
}
pub(crate) fn ipv4_mask_to_prefix(mask: Ipv4Addr) -> Result<u8, &'static str> {
let mask = u32::from(mask);
let prefix = (!mask).leading_zeros() as u8;
if (u64::from(mask) << prefix) & 0xffff_ffff != 0 {
Err("invalid ipv4 prefix")
} else {
Ok(prefix)
}
}
pub(crate) fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, &'static str> {
let mask = mask.segments();
let mut mask_iter = mask.iter();
// Count the number of set bits from the start of the address
let mut prefix = 0;
for &segment in &mut mask_iter {
if segment == 0xffff {
prefix += 16;
} else if segment == 0 {
// Prefix finishes on a segment boundary
break;
} else {
let prefix_bits = (!segment).leading_zeros() as u8;
// Check that the remainder of the bits are all unset
if segment << prefix_bits != 0 {
return Err("invalid ipv6 prefix");
}
prefix += prefix_bits;
break;
}
}
// Now check all the remaining bits are unset
for &segment in mask_iter {
if segment != 0 {
return Err("invalid ipv6 prefix");
}
}
Ok(prefix)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ipv4_mask() {
let mask = Ipv4Addr::new(255, 255, 255, 0);
let prefix = ipv4_mask_to_prefix(mask).unwrap();
assert_eq!(prefix, 24);
}
#[test]
fn ipv4_mask_another() {
let mask = Ipv4Addr::new(255, 255, 255, 128);
let prefix = ipv4_mask_to_prefix(mask).unwrap();
assert_eq!(prefix, 25);
}
#[test]
fn v4_mask_to_prefix_invalid() {
let mask = Ipv4Addr::new(255, 128, 255, 0);
assert!(ipv4_mask_to_prefix(mask).is_err());
}
#[test]
fn ipv6_mask() {
let mask = Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0, 0, 0, 0, 0);
let prefix = ipv6_mask_to_prefix(mask).unwrap();
assert_eq!(prefix, 48);
}
#[test]
fn ipv6_mask_invalid() {
let mask = Ipv6Addr::new(0, 0xffff, 0xffff, 0, 0, 0, 0, 0);
assert!(ipv6_mask_to_prefix(mask).is_err());
}
#[test]
fn ip_mask_enum_ipv4() {
let mask = IpAddr::from(Ipv4Addr::new(255, 255, 255, 0));
let prefix = ip_mask_to_prefix(mask).unwrap();
assert_eq!(prefix, 24);
}
#[test]
fn ip_mask_enum_ipv4_invalid() {
let mask = IpAddr::from(Ipv4Addr::new(255, 0, 255, 0));
assert!(ip_mask_to_prefix(mask).is_err());
}
#[test]
fn ip_mask_enum_ipv6() {
let mask = IpAddr::from(Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0, 0, 0, 0, 0));
let prefix = ip_mask_to_prefix(mask).unwrap();
assert_eq!(prefix, 48);
}
#[test]
fn ip_mask_enum_ipv6_invalid() {
let mask = IpAddr::from(Ipv6Addr::new(0xffff, 0xffff, 0xff00, 0xffff, 0, 0, 0, 0));
assert!(ip_mask_to_prefix(mask).is_err());
}
}

149
vendor/sysinfo/src/unix/users.rs vendored Normal file
View File

@@ -0,0 +1,149 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{
common::{Gid, Uid},
Group,
};
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
use crate::User;
use libc::{getgrgid_r, getgrouplist};
pub(crate) struct UserInner {
pub(crate) uid: Uid,
pub(crate) gid: Gid,
pub(crate) name: String,
c_user: Vec<u8>,
}
impl UserInner {
pub(crate) fn new(uid: Uid, gid: Gid, name: String) -> Self {
let mut c_user = name.as_bytes().to_vec();
c_user.push(0);
Self {
uid,
gid,
name,
c_user,
}
}
pub(crate) fn id(&self) -> &Uid {
&self.uid
}
pub(crate) fn group_id(&self) -> Gid {
self.gid
}
pub(crate) fn name(&self) -> &str {
&self.name
}
pub(crate) fn groups(&self) -> Vec<Group> {
unsafe { get_user_groups(self.c_user.as_ptr() as *const _, self.gid.0 as _) }
}
}
pub(crate) unsafe fn get_group_name(
id: libc::gid_t,
buffer: &mut Vec<libc::c_char>,
) -> Option<String> {
let mut g = std::mem::MaybeUninit::<libc::group>::uninit();
let mut tmp_ptr = std::ptr::null_mut();
let mut last_errno = 0;
loop {
if retry_eintr!(set_to_0 => last_errno => getgrgid_r(
id as _,
g.as_mut_ptr() as _,
buffer.as_mut_ptr(),
buffer.capacity() as _,
&mut tmp_ptr as _
)) != 0
{
// If there was not enough memory, we give it more.
if last_errno == libc::ERANGE as _ {
// Needs to be updated for `Vec::reserve` to actually add additional capacity.
// In here it's "fine" since we never read from `buffer`.
buffer.set_len(buffer.capacity());
buffer.reserve(2048);
continue;
}
return None;
}
break;
}
let g = g.assume_init();
super::utils::cstr_to_rust(g.gr_name)
}
pub(crate) unsafe fn get_user_groups(
name: *const libc::c_char,
group_id: libc::gid_t,
) -> Vec<Group> {
let mut buffer = Vec::with_capacity(2048);
let mut groups = Vec::with_capacity(256);
loop {
let mut nb_groups = groups.capacity();
if getgrouplist(
name,
group_id as _,
groups.as_mut_ptr(),
&mut nb_groups as *mut _ as *mut _,
) == -1
{
// Ensure the length matches the number of returned groups.
// Needs to be updated for `Vec::reserve` to actually add additional capacity.
groups.set_len(nb_groups as _);
groups.reserve(256);
continue;
}
groups.set_len(nb_groups as _);
return groups
.iter()
.filter_map(|group_id| {
let name = get_group_name(*group_id as _, &mut buffer)?;
Some(Group {
inner: crate::GroupInner::new(Gid(*group_id as _), name),
})
})
.collect();
}
}
// Not used by mac.
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
pub(crate) fn get_users(users: &mut Vec<User>) {
use std::fs::File;
use std::io::Read;
#[inline]
fn parse_id(id: &str) -> Option<u32> {
id.parse::<u32>().ok()
}
users.clear();
let mut s = String::new();
let _ = File::open("/etc/passwd").and_then(|mut f| f.read_to_string(&mut s));
for line in s.lines() {
let mut parts = line.split(':');
if let Some(username) = parts.next() {
let mut parts = parts.skip(1);
// Skip the user if the uid cannot be parsed correctly
if let Some(uid) = parts.next().and_then(parse_id) {
if let Some(group_id) = parts.next().and_then(parse_id) {
users.push(User {
inner: UserInner::new(Uid(uid), Gid(group_id), username.to_owned()),
});
}
}
}
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub(crate) use crate::unix::apple::users::get_users;

60
vendor/sysinfo/src/unix/utils.rs vendored Normal file
View File

@@ -0,0 +1,60 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[cfg(feature = "user")]
pub(crate) fn cstr_to_rust(c: *const libc::c_char) -> Option<String> {
cstr_to_rust_with_size(c, None)
}
#[cfg(any(feature = "disk", feature = "system", feature = "user"))]
#[allow(dead_code)]
pub(crate) fn cstr_to_rust_with_size(
c: *const libc::c_char,
size: Option<usize>,
) -> Option<String> {
if c.is_null() {
return None;
}
let (mut s, max) = match size {
Some(len) => (Vec::with_capacity(len), len as isize),
None => (Vec::new(), isize::MAX),
};
let mut i = 0;
unsafe {
loop {
let value = *c.offset(i) as u8;
if value == 0 {
break;
}
s.push(value);
i += 1;
if i >= max {
break;
}
}
String::from_utf8(s).ok()
}
}
#[cfg(all(
feature = "system",
not(any(
target_os = "ios",
all(target_os = "macos", feature = "apple-sandbox",)
))
))]
pub(crate) fn wait_process(pid: crate::Pid) -> Option<std::process::ExitStatus> {
use std::os::unix::process::ExitStatusExt;
let mut status = 0;
// attempt waiting
unsafe {
if retry_eintr!(libc::waitpid(pid.0, &mut status, 0)) < 0 {
// attempt failed (non-child process) so loop until process ends
let duration = std::time::Duration::from_millis(10);
while libc::kill(pid.0, 0) == 0 {
std::thread::sleep(duration);
}
}
Some(std::process::ExitStatus::from_raw(status))
}
}

59
vendor/sysinfo/src/unknown/component.rs vendored Normal file
View File

@@ -0,0 +1,59 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::Component;
pub(crate) struct ComponentInner {
pub(crate) updated: bool,
}
impl ComponentInner {
pub(crate) fn temperature(&self) -> Option<f32> {
None
}
pub(crate) fn max(&self) -> Option<f32> {
None
}
pub(crate) fn critical(&self) -> Option<f32> {
None
}
pub(crate) fn label(&self) -> &str {
""
}
pub(crate) fn refresh(&mut self) {}
}
pub(crate) struct ComponentsInner {
pub(crate) components: Vec<Component>,
}
impl ComponentsInner {
pub(crate) fn new() -> Self {
Self {
components: Vec::new(),
}
}
pub(crate) fn from_vec(components: Vec<Component>) -> Self {
Self { components }
}
pub(crate) fn into_vec(self) -> Vec<Component> {
self.components
}
pub(crate) fn list(&self) -> &[Component] {
&self.components
}
pub(crate) fn list_mut(&mut self) -> &mut [Component] {
&mut self.components
}
pub(crate) fn refresh(&mut self) {
// Doesn't do anything.
}
}

25
vendor/sysinfo/src/unknown/cpu.rs vendored Normal file
View File

@@ -0,0 +1,25 @@
// Take a look at the license at the top of the repository in the LICENSE file.
pub(crate) struct CpuInner;
impl CpuInner {
pub(crate) fn cpu_usage(&self) -> f32 {
0.0
}
pub(crate) fn name(&self) -> &str {
""
}
pub(crate) fn frequency(&self) -> u64 {
0
}
pub(crate) fn vendor_id(&self) -> &str {
""
}
pub(crate) fn brand(&self) -> &str {
""
}
}

83
vendor/sysinfo/src/unknown/disk.rs vendored Normal file
View File

@@ -0,0 +1,83 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{Disk, DiskKind, DiskRefreshKind, DiskUsage};
use std::{ffi::OsStr, path::Path};
pub(crate) struct DiskInner;
impl DiskInner {
pub(crate) fn kind(&self) -> DiskKind {
DiskKind::Unknown(-1)
}
pub(crate) fn name(&self) -> &OsStr {
OsStr::new("")
}
pub(crate) fn file_system(&self) -> &OsStr {
Default::default()
}
pub(crate) fn mount_point(&self) -> &Path {
Path::new("")
}
pub(crate) fn total_space(&self) -> u64 {
0
}
pub(crate) fn available_space(&self) -> u64 {
0
}
pub(crate) fn is_removable(&self) -> bool {
false
}
pub(crate) fn is_read_only(&self) -> bool {
false
}
pub(crate) fn refresh_specifics(&mut self, _refreshes: DiskRefreshKind) -> bool {
true
}
pub(crate) fn usage(&self) -> DiskUsage {
DiskUsage::default()
}
}
pub(crate) struct DisksInner {
pub(crate) disks: Vec<Disk>,
}
impl DisksInner {
pub(crate) fn new() -> Self {
Self { disks: Vec::new() }
}
pub(crate) fn from_vec(disks: Vec<Disk>) -> Self {
Self { disks }
}
pub(crate) fn into_vec(self) -> Vec<Disk> {
self.disks
}
pub(crate) fn refresh_specifics(
&mut self,
_remove_not_listed_disks: bool,
_refreshes: DiskRefreshKind,
) {
// Does nothing.
}
pub(crate) fn list(&self) -> &[Disk] {
&self.disks
}
pub(crate) fn list_mut(&mut self) -> &mut [Disk] {
&mut self.disks
}
}

15
vendor/sysinfo/src/unknown/groups.rs vendored Normal file
View File

@@ -0,0 +1,15 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{Gid, Group, GroupInner};
impl GroupInner {
pub(crate) fn id(&self) -> &Gid {
&self.id
}
pub(crate) fn name(&self) -> &str {
&self.name
}
}
pub(crate) fn get_groups(_: &mut Vec<Group>) {}

61
vendor/sysinfo/src/unknown/mod.rs vendored Normal file
View File

@@ -0,0 +1,61 @@
// Take a look at the license at the top of the repository in the LICENSE file.
cfg_if! {
if #[cfg(feature = "system")] {
pub mod cpu;
pub mod process;
pub mod system;
pub(crate) use self::cpu::CpuInner;
pub(crate) use self::process::ProcessInner;
pub(crate) use self::system::SystemInner;
pub use self::system::{MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS};
}
if #[cfg(feature = "disk")] {
pub mod disk;
pub(crate) use self::disk::{DiskInner, DisksInner};
}
if #[cfg(feature = "component")] {
pub mod component;
pub(crate) use self::component::{ComponentInner, ComponentsInner};
}
if #[cfg(feature = "network")] {
pub mod network;
pub(crate) use self::network::{NetworkDataInner, NetworksInner};
}
if #[cfg(feature = "user")] {
pub mod groups;
pub mod users;
pub(crate) use self::groups::get_groups;
pub(crate) use self::users::{get_users, UserInner};
}
}
#[doc = include_str!("../../md_doc/is_supported.md")]
pub const IS_SUPPORTED_SYSTEM: bool = false;
// Make formattable by rustfmt.
#[cfg(any())]
mod component;
#[cfg(any())]
mod cpu;
#[cfg(any())]
mod disk;
#[cfg(any())]
mod groups;
#[cfg(any())]
mod network;
#[cfg(any())]
mod process;
#[cfg(any())]
mod system;
#[cfg(any())]
mod users;

87
vendor/sysinfo/src/unknown/network.rs vendored Normal file
View File

@@ -0,0 +1,87 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{IpNetwork, MacAddr, NetworkData};
use std::collections::HashMap;
pub(crate) struct NetworksInner {
pub(crate) interfaces: HashMap<String, NetworkData>,
}
impl NetworksInner {
pub(crate) fn new() -> Self {
Self {
interfaces: HashMap::new(),
}
}
pub(crate) fn list(&self) -> &HashMap<String, NetworkData> {
&self.interfaces
}
pub(crate) fn refresh(&mut self, _remove_not_listed_interfaces: bool) {}
}
pub(crate) struct NetworkDataInner;
impl NetworkDataInner {
pub(crate) fn received(&self) -> u64 {
0
}
pub(crate) fn total_received(&self) -> u64 {
0
}
pub(crate) fn transmitted(&self) -> u64 {
0
}
pub(crate) fn total_transmitted(&self) -> u64 {
0
}
pub(crate) fn packets_received(&self) -> u64 {
0
}
pub(crate) fn total_packets_received(&self) -> u64 {
0
}
pub(crate) fn packets_transmitted(&self) -> u64 {
0
}
pub(crate) fn total_packets_transmitted(&self) -> u64 {
0
}
pub(crate) fn errors_on_received(&self) -> u64 {
0
}
pub(crate) fn total_errors_on_received(&self) -> u64 {
0
}
pub(crate) fn errors_on_transmitted(&self) -> u64 {
0
}
pub(crate) fn total_errors_on_transmitted(&self) -> u64 {
0
}
pub(crate) fn mac_address(&self) -> MacAddr {
MacAddr::UNSPECIFIED
}
pub(crate) fn ip_networks(&self) -> &[IpNetwork] {
&[]
}
pub(crate) fn mtu(&self) -> u64 {
0
}
}

131
vendor/sysinfo/src/unknown/process.rs vendored Normal file
View File

@@ -0,0 +1,131 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{DiskUsage, Gid, Pid, ProcessStatus, Signal, Uid};
use std::ffi::{OsStr, OsString};
use std::fmt;
use std::path::Path;
use std::process::ExitStatus;
impl fmt::Display for ProcessStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Unknown")
}
}
pub(crate) struct ProcessInner {
pid: Pid,
parent: Option<Pid>,
}
impl ProcessInner {
pub(crate) fn kill_with(&self, _signal: Signal) -> Option<bool> {
None
}
pub(crate) fn name(&self) -> &OsStr {
OsStr::new("")
}
pub(crate) fn cmd(&self) -> &[OsString] {
&[]
}
pub(crate) fn exe(&self) -> Option<&Path> {
None
}
pub(crate) fn pid(&self) -> Pid {
self.pid
}
pub(crate) fn environ(&self) -> &[OsString] {
&[]
}
pub(crate) fn cwd(&self) -> Option<&Path> {
None
}
pub(crate) fn root(&self) -> Option<&Path> {
None
}
pub(crate) fn memory(&self) -> u64 {
0
}
pub(crate) fn virtual_memory(&self) -> u64 {
0
}
pub(crate) fn parent(&self) -> Option<Pid> {
self.parent
}
pub(crate) fn status(&self) -> ProcessStatus {
ProcessStatus::Unknown(0)
}
pub(crate) fn start_time(&self) -> u64 {
0
}
pub(crate) fn run_time(&self) -> u64 {
0
}
pub(crate) fn cpu_usage(&self) -> f32 {
0.0
}
pub(crate) fn accumulated_cpu_time(&self) -> u64 {
0
}
pub(crate) fn disk_usage(&self) -> DiskUsage {
DiskUsage::default()
}
pub(crate) fn user_id(&self) -> Option<&Uid> {
None
}
pub(crate) fn effective_user_id(&self) -> Option<&Uid> {
None
}
pub(crate) fn group_id(&self) -> Option<Gid> {
None
}
pub(crate) fn effective_group_id(&self) -> Option<Gid> {
None
}
pub(crate) fn wait(&self) -> Option<ExitStatus> {
None
}
pub(crate) fn session_id(&self) -> Option<Pid> {
None
}
pub(crate) fn switch_updated(&mut self) -> bool {
false
}
pub(crate) fn set_nonexistent(&mut self) {}
pub(crate) fn exists(&self) -> bool {
false
}
pub(crate) fn open_files(&self) -> Option<u32> {
None
}
pub(crate) fn open_files_limit(&self) -> Option<u32> {
None
}
}

156
vendor/sysinfo/src/unknown/system.rs vendored Normal file
View File

@@ -0,0 +1,156 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{
Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind,
ProcessesToUpdate,
};
use std::collections::HashMap;
use std::time::Duration;
declare_signals! {
(),
_ => None,
}
#[doc = include_str!("../../md_doc/supported_signals.md")]
pub const SUPPORTED_SIGNALS: &[crate::Signal] = supported_signals();
#[doc = include_str!("../../md_doc/minimum_cpu_update_interval.md")]
pub const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(0);
pub(crate) struct SystemInner {
process_list: HashMap<Pid, Process>,
}
impl SystemInner {
pub(crate) fn new() -> Self {
Self {
process_list: Default::default(),
}
}
pub(crate) fn refresh_memory_specifics(&mut self, _refresh_kind: MemoryRefreshKind) {}
pub(crate) fn cgroup_limits(&self) -> Option<crate::CGroupLimits> {
None
}
pub(crate) fn refresh_cpu_specifics(&mut self, _refresh_kind: CpuRefreshKind) {}
pub(crate) fn refresh_cpu_list(&mut self, _refresh_kind: CpuRefreshKind) {}
pub(crate) fn refresh_processes_specifics(
&mut self,
_processes_to_update: ProcessesToUpdate<'_>,
_refresh_kind: ProcessRefreshKind,
) -> usize {
0
}
// COMMON PART
//
// Need to be moved into a "common" file to avoid duplication.
pub(crate) fn processes(&self) -> &HashMap<Pid, Process> {
&self.process_list
}
pub(crate) fn processes_mut(&mut self) -> &mut HashMap<Pid, Process> {
&mut self.process_list
}
pub(crate) fn process(&self, _pid: Pid) -> Option<&Process> {
None
}
pub(crate) fn global_cpu_usage(&self) -> f32 {
0.
}
pub(crate) fn cpus(&self) -> &[Cpu] {
&[]
}
pub(crate) fn total_memory(&self) -> u64 {
0
}
pub(crate) fn free_memory(&self) -> u64 {
0
}
pub(crate) fn available_memory(&self) -> u64 {
0
}
pub(crate) fn used_memory(&self) -> u64 {
0
}
pub(crate) fn total_swap(&self) -> u64 {
0
}
pub(crate) fn free_swap(&self) -> u64 {
0
}
pub(crate) fn used_swap(&self) -> u64 {
0
}
pub(crate) fn uptime() -> u64 {
0
}
pub(crate) fn boot_time() -> u64 {
0
}
pub(crate) fn load_average() -> LoadAvg {
LoadAvg {
one: 0.,
five: 0.,
fifteen: 0.,
}
}
pub(crate) fn name() -> Option<String> {
None
}
pub(crate) fn long_os_version() -> Option<String> {
None
}
pub(crate) fn kernel_version() -> Option<String> {
None
}
pub(crate) fn os_version() -> Option<String> {
None
}
pub(crate) fn distribution_id() -> String {
std::env::consts::OS.to_owned()
}
pub(crate) fn distribution_id_like() -> Vec<String> {
Vec::new()
}
pub(crate) fn kernel_name() -> Option<&'static str> {
None
}
pub(crate) fn host_name() -> Option<String> {
None
}
pub(crate) fn cpu_arch() -> Option<String> {
None
}
pub(crate) fn physical_core_count() -> Option<usize> {
None
}
}

25
vendor/sysinfo/src/unknown/users.rs vendored Normal file
View File

@@ -0,0 +1,25 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{Gid, Group, Uid, User};
pub(crate) struct UserInner;
impl UserInner {
pub(crate) fn id(&self) -> &Uid {
&Uid(0)
}
pub(crate) fn group_id(&self) -> Gid {
Gid(0)
}
pub(crate) fn name(&self) -> &str {
""
}
pub(crate) fn groups(&self) -> Vec<Group> {
Vec::new()
}
}
pub(crate) fn get_users(_: &mut Vec<User>) {}

61
vendor/sysinfo/src/utils.rs vendored Normal file
View File

@@ -0,0 +1,61 @@
// Take a look at the license at the top of the repository in the LICENSE file.
cfg_if! {
if #[cfg(all(
feature = "multithread",
not(feature = "unknown-ci"),
not(all(target_os = "macos", feature = "apple-sandbox")),
))] {
/// Converts the value into a parallel iterator if the `multithread` feature is enabled.
/// Uses the `rayon::iter::IntoParallelIterator` trait.
#[cfg(all(
feature = "multithread",
not(feature = "unknown-ci"),
not(all(target_os = "macos", feature = "apple-sandbox")),
))]
#[allow(dead_code)]
pub(crate) fn into_iter<T>(val: T) -> T::Iter
where
T: rayon::iter::IntoParallelIterator,
{
val.into_par_iter()
}
// /// Converts the value into a parallel mutable iterator if the `multithread` feature is
// /// enabled. Uses the `rayon::iter::IntoParallelRefMutIterator` trait.
// #[cfg(feature = "component")]
// pub(crate) fn into_iter_mut<'a, T>(
// val: &'a mut T,
// ) -> <T as rayon::iter::IntoParallelRefMutIterator<'a>>::Iter
// where
// T: rayon::iter::IntoParallelRefMutIterator<'a> + ?Sized,
// {
// val.par_iter_mut()
// }
} else {
/// Converts the value into a sequential iterator if the `multithread` feature is disabled.
/// Uses the `std::iter::IntoIterator` trait.
#[allow(dead_code)]
pub(crate) fn into_iter<T>(val: T) -> T::IntoIter
where
T: IntoIterator,
{
val.into_iter()
}
// In the multithreaded version of `into_iter_mut` above, the `&mut` on the argument is
// indicating the parallel iterator is an exclusive reference. In the non-multithreaded
// case, the `&mut` is already part of `T` and specifying it will result in the argument
// being `&mut &mut T`.
// /// Converts the value into a sequential mutable iterator if the `multithread` feature is
// /// disabled. Uses the `std::iter::IntoIterator` trait.
// #[cfg(feature = "component")]
// pub(crate) fn into_iter_mut<T>(val: T) -> T::IntoIter
// where
// T: IntoIterator,
// {
// val.into_iter()
// }
}
}

341
vendor/sysinfo/src/windows/component.rs vendored Normal file
View File

@@ -0,0 +1,341 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::Component;
use windows::core::{w, VARIANT};
use windows::Win32::Foundation::{SysAllocString, SysFreeString};
use windows::Win32::Security::PSECURITY_DESCRIPTOR;
use windows::Win32::System::Com::{
CoCreateInstance, CoInitializeEx, CoInitializeSecurity, CoSetProxyBlanket,
CLSCTX_INPROC_SERVER, EOAC_NONE, RPC_C_AUTHN_LEVEL_CALL, RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE,
};
use windows::Win32::System::Rpc::{RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE};
use windows::Win32::System::Variant::VariantClear;
use windows::Win32::System::Wmi::{
IEnumWbemClassObject, IWbemLocator, IWbemServices, WbemLocator, WBEM_FLAG_FORWARD_ONLY,
WBEM_FLAG_NONSYSTEM_ONLY, WBEM_FLAG_RETURN_IMMEDIATELY, WBEM_INFINITE,
};
use std::cell::OnceCell;
use std::sync::OnceLock;
pub(crate) struct ComponentInner {
temperature: f32,
max: f32,
critical: Option<f32>,
label: String,
connection: Option<Connection>,
pub(crate) updated: bool,
}
impl ComponentInner {
/// Creates a new `ComponentInner` with the given information.
fn new() -> Option<Self> {
let mut c = Connection::new()
.and_then(|x| x.create_instance())
.and_then(|x| x.connect_server())
.and_then(|x| x.set_proxy_blanket())
.and_then(|x| x.exec_query())?;
c.temperature(true)
.map(|(temperature, critical)| ComponentInner {
temperature,
label: "Computer".to_owned(),
max: temperature,
critical,
connection: Some(c),
updated: true,
})
}
pub(crate) fn temperature(&self) -> Option<f32> {
Some(self.temperature)
}
pub(crate) fn max(&self) -> Option<f32> {
Some(self.max)
}
pub(crate) fn critical(&self) -> Option<f32> {
self.critical
}
pub(crate) fn label(&self) -> &str {
&self.label
}
pub(crate) fn refresh(&mut self) {
if self.connection.is_none() {
self.connection = Connection::new()
.and_then(|x| x.create_instance())
.and_then(|x| x.connect_server())
.and_then(|x| x.set_proxy_blanket());
}
self.connection = if let Some(x) = self.connection.take() {
x.exec_query()
} else {
None
};
if let Some(ref mut connection) = self.connection {
if let Some((temperature, _)) = connection.temperature(false) {
self.temperature = temperature;
if self.temperature > self.max {
self.max = self.temperature;
}
}
}
}
}
pub(crate) struct ComponentsInner {
pub(crate) components: Vec<Component>,
}
impl ComponentsInner {
pub(crate) fn new() -> Self {
Self {
components: Vec::new(),
}
}
pub(crate) fn from_vec(components: Vec<Component>) -> Self {
Self { components }
}
pub(crate) fn into_vec(self) -> Vec<Component> {
self.components
}
pub(crate) fn list(&self) -> &[Component] {
&self.components
}
pub(crate) fn list_mut(&mut self) -> &mut [Component] {
&mut self.components
}
pub(crate) fn refresh(&mut self) {
if self.components.is_empty() {
self.components = match ComponentInner::new() {
Some(c) => vec![Component { inner: c }],
None => Vec::new(),
};
} else {
// There should always be only one here but just in case...
for c in self.components.iter_mut() {
c.refresh();
c.inner.updated = true;
}
}
}
}
macro_rules! bstr {
($x:literal) => {{
SysAllocString(w!($x))
}};
}
struct Connection {
instance: Option<IWbemLocator>,
server_connection: Option<IWbemServices>,
enumerator: Option<IEnumWbemClassObject>,
}
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl Send for Connection {}
unsafe impl Sync for Connection {}
static SECURITY: OnceLock<Result<(), ()>> = OnceLock::new();
thread_local! {
pub static CONNECTION: OnceCell<Result<(), ()>> = const { OnceCell::new() };
}
unsafe fn initialize_connection() -> Result<(), ()> {
if CoInitializeEx(None, Default::default()).is_err() {
sysinfo_debug!("Failed to initialize connection");
Err(())
} else {
Ok(())
}
}
unsafe fn initialize_security() -> Result<(), ()> {
if CoInitializeSecurity(
PSECURITY_DESCRIPTOR::default(),
-1,
None,
None,
RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE,
None,
EOAC_NONE,
None,
)
.is_err()
{
sysinfo_debug!("Failed to initialize security");
Err(())
} else {
Ok(())
}
}
impl Connection {
#[allow(clippy::unnecessary_wraps)]
fn new() -> Option<Connection> {
if CONNECTION
.with(|x| *x.get_or_init(|| unsafe { initialize_connection() }))
.is_err()
|| SECURITY
.get_or_init(|| unsafe { initialize_security() })
.is_err()
{
return None;
}
Some(Connection {
instance: None,
server_connection: None,
enumerator: None,
})
}
fn create_instance(mut self) -> Option<Connection> {
let instance =
unsafe { CoCreateInstance(&WbemLocator, None, CLSCTX_INPROC_SERVER) }.ok()?;
self.instance = Some(instance);
Some(self)
}
fn connect_server(mut self) -> Option<Connection> {
let instance = self.instance.as_ref()?;
let svc = unsafe {
let s = bstr!("root\\WMI");
let res = instance.ConnectServer(
&s,
&Default::default(),
&Default::default(),
&Default::default(),
0,
&Default::default(),
None,
);
SysFreeString(&s);
res
}
.ok()?;
self.server_connection = Some(svc);
Some(self)
}
fn set_proxy_blanket(self) -> Option<Connection> {
unsafe {
CoSetProxyBlanket(
self.server_connection.as_ref()?,
RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE,
None,
RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE,
None,
EOAC_NONE,
)
}
.ok()?;
Some(self)
}
fn exec_query(mut self) -> Option<Connection> {
let server_connection = self.server_connection.as_ref()?;
let enumerator = unsafe {
let s = bstr!("WQL"); // query kind
let query = bstr!("SELECT * FROM MSAcpi_ThermalZoneTemperature");
let hres = server_connection.ExecQuery(
&s,
&query,
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
None,
);
SysFreeString(&s);
SysFreeString(&query);
hres
}
.ok()?;
self.enumerator = Some(enumerator);
Some(self)
}
fn temperature(&mut self, get_critical: bool) -> Option<(f32, Option<f32>)> {
let enumerator = self.enumerator.take()?;
let mut nb_returned = 0;
let mut obj = [None; 1];
unsafe {
let _r = enumerator.Next(
WBEM_INFINITE, // Time out
obj.as_mut_slice(),
&mut nb_returned,
);
if nb_returned == 0 {
return None; // not enough rights I suppose...
}
let class_obj = match &mut obj {
[Some(co)] => co,
_ => return None,
};
let _r = class_obj.BeginEnumeration(WBEM_FLAG_NONSYSTEM_ONLY.0);
let mut variant = std::mem::MaybeUninit::<VARIANT>::uninit();
// `Get` only initializes the variant if it succeeds, early returning is not a problem
//
// <https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemclassobject-get>
class_obj
.Get(
w!("CurrentTemperature"),
0,
variant.as_mut_ptr(),
None,
None,
)
.ok()?;
let mut variant = variant.assume_init();
// temperature is given in tenth of degrees Kelvin
let temp = (variant.as_raw().Anonymous.decVal.Anonymous2.Lo64 / 10) as f32 - 273.15;
let _r = VariantClear(&mut variant);
let mut critical = None;
if get_critical {
class_obj
.Get(w!("CriticalTripPoint"), 0, &mut variant, None, None)
.ok()?;
// temperature is given in tenth of degrees Kelvin
critical =
Some((variant.as_raw().Anonymous.decVal.Anonymous2.Lo64 / 10) as f32 - 273.15);
let _r = VariantClear(&mut variant);
}
Some((temp, critical))
}
}
}
impl Drop for Connection {
fn drop(&mut self) {
// Those three calls are here to enforce that they get dropped in the good order.
self.enumerator.take();
self.server_connection.take();
self.instance.take();
}
}

620
vendor/sysinfo/src/windows/cpu.rs vendored Normal file
View File

@@ -0,0 +1,620 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{Cpu, CpuRefreshKind, LoadAvg};
use std::collections::HashMap;
use std::ffi::c_void;
use std::io::Error;
use std::mem;
use std::ops::DerefMut;
use std::sync::{Mutex, OnceLock};
use windows::core::{s, PCSTR, PCWSTR, PSTR};
use windows::Win32::Foundation::{
CloseHandle, BOOLEAN, ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS, FALSE, HANDLE, TRUE,
};
use windows::Win32::System::Performance::{
PdhAddEnglishCounterA, PdhAddEnglishCounterW, PdhCloseQuery, PdhCollectQueryData,
PdhCollectQueryDataEx, PdhEnumObjectsA, PdhGetFormattedCounterValue, PdhOpenQueryA,
PdhRemoveCounter, PDH_FMT_COUNTERVALUE, PDH_FMT_DOUBLE, PERF_DETAIL_NOVICE,
};
use windows::Win32::System::Power::{
CallNtPowerInformation, ProcessorInformation, PROCESSOR_POWER_INFORMATION,
};
use windows::Win32::System::SystemInformation::{self, GetSystemInfo};
use windows::Win32::System::SystemInformation::{
GetLogicalProcessorInformationEx, RelationAll, RelationProcessorCore, SYSTEM_INFO,
SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX,
};
use windows::Win32::System::Threading::{
CreateEventA, RegisterWaitForSingleObject, INFINITE, WT_EXECUTEDEFAULT,
};
// This formula comes from Linux's include/linux/sched/loadavg.h
// https://github.com/torvalds/linux/blob/345671ea0f9258f410eb057b9ced9cefbbe5dc78/include/linux/sched/loadavg.h#L20-L23
#[allow(clippy::excessive_precision)]
const LOADAVG_FACTOR_1F: f64 = 0.9200444146293232478931553241;
#[allow(clippy::excessive_precision)]
const LOADAVG_FACTOR_5F: f64 = 0.9834714538216174894737477501;
#[allow(clippy::excessive_precision)]
const LOADAVG_FACTOR_15F: f64 = 0.9944598480048967508795473394;
// The time interval in seconds between taking load counts, same as Linux
const SAMPLING_INTERVAL: usize = 5;
// maybe use a read/write lock instead?
fn load_avg() -> &'static Mutex<Option<LoadAvg>> {
static LOAD_AVG: OnceLock<Mutex<Option<LoadAvg>>> = OnceLock::new();
LOAD_AVG.get_or_init(|| unsafe { init_load_avg() })
}
pub(crate) fn get_load_average() -> LoadAvg {
if let Ok(avg) = load_avg().lock() {
if let Some(avg) = &*avg {
return avg.clone();
}
}
LoadAvg::default()
}
unsafe extern "system" fn load_avg_callback(counter: *mut c_void, _: BOOLEAN) {
let mut display_value = mem::MaybeUninit::<PDH_FMT_COUNTERVALUE>::uninit();
if PdhGetFormattedCounterValue(
counter as _,
PDH_FMT_DOUBLE,
None,
display_value.as_mut_ptr(),
) != ERROR_SUCCESS.0
{
return;
}
let display_value = display_value.assume_init();
if let Ok(mut avg) = load_avg().lock() {
if let Some(avg) = avg.deref_mut() {
let current_load = display_value.Anonymous.doubleValue;
avg.one = avg.one * LOADAVG_FACTOR_1F + current_load * (1.0 - LOADAVG_FACTOR_1F);
avg.five = avg.five * LOADAVG_FACTOR_5F + current_load * (1.0 - LOADAVG_FACTOR_5F);
avg.fifteen =
avg.fifteen * LOADAVG_FACTOR_15F + current_load * (1.0 - LOADAVG_FACTOR_15F);
}
}
}
unsafe fn init_load_avg() -> Mutex<Option<LoadAvg>> {
// You can see the original implementation here: https://github.com/giampaolo/psutil
let mut query = 0;
if PdhOpenQueryA(PCSTR::null(), 0, &mut query) != ERROR_SUCCESS.0 {
sysinfo_debug!("init_load_avg: PdhOpenQueryA failed");
return Mutex::new(None);
}
let mut counter = 0;
if PdhAddEnglishCounterA(query, s!("\\System\\Cpu Queue Length"), 0, &mut counter)
!= ERROR_SUCCESS.0
{
PdhCloseQuery(query);
sysinfo_debug!("init_load_avg: failed to get CPU queue length");
return Mutex::new(None);
}
let event = match CreateEventA(None, FALSE, FALSE, s!("LoadUpdateEvent")) {
Ok(ev) => ev,
Err(_) => {
PdhCloseQuery(query);
sysinfo_debug!("init_load_avg: failed to create event `LoadUpdateEvent`");
return Mutex::new(None);
}
};
if PdhCollectQueryDataEx(query, SAMPLING_INTERVAL as _, event) != ERROR_SUCCESS.0 {
PdhCloseQuery(query);
sysinfo_debug!("init_load_avg: PdhCollectQueryDataEx failed");
return Mutex::new(None);
}
let mut wait_handle = HANDLE::default();
if RegisterWaitForSingleObject(
&mut wait_handle,
event,
Some(load_avg_callback),
Some(counter as *const c_void),
INFINITE,
WT_EXECUTEDEFAULT,
)
.is_ok()
{
Mutex::new(Some(LoadAvg::default()))
} else {
PdhRemoveCounter(counter);
PdhCloseQuery(query);
sysinfo_debug!("init_load_avg: RegisterWaitForSingleObject failed");
Mutex::new(None)
}
}
struct InternalQuery {
query: HANDLE,
event: HANDLE,
data: HashMap<String, HANDLE>,
}
unsafe impl Send for InternalQuery {}
unsafe impl Sync for InternalQuery {}
impl Drop for InternalQuery {
fn drop(&mut self) {
unsafe {
for (_, counter) in self.data.iter() {
PdhRemoveCounter(counter.0);
}
if !self.event.is_invalid() {
let _err = CloseHandle(self.event);
}
if !self.query.is_invalid() {
PdhCloseQuery(self.query.0);
}
}
}
}
pub(crate) struct Query {
internal: InternalQuery,
}
impl Query {
pub fn new(force_reload: bool) -> Option<Query> {
let mut query = 0;
unsafe {
if force_reload {
PdhEnumObjectsA(
PCSTR::null(),
PCSTR::null(),
PSTR::null(),
&mut 0,
PERF_DETAIL_NOVICE,
TRUE,
);
}
if PdhOpenQueryA(PCSTR::null(), 0, &mut query) == ERROR_SUCCESS.0 {
let q = InternalQuery {
query: HANDLE(query),
event: HANDLE::default(),
data: HashMap::new(),
};
Some(Query { internal: q })
} else {
sysinfo_debug!("Query::new: PdhOpenQueryA failed");
None
}
}
}
#[allow(clippy::ptr_arg)]
pub fn get(&self, name: &String) -> Option<f32> {
if let Some(counter) = self.internal.data.get(name) {
unsafe {
let mut display_value = mem::MaybeUninit::<PDH_FMT_COUNTERVALUE>::uninit();
return if PdhGetFormattedCounterValue(
counter.0,
PDH_FMT_DOUBLE,
None,
display_value.as_mut_ptr(),
) == ERROR_SUCCESS.0
{
let display_value = display_value.assume_init();
Some(display_value.Anonymous.doubleValue as f32)
} else {
sysinfo_debug!("Query::get: PdhGetFormattedCounterValue failed");
Some(0.)
};
}
}
None
}
#[allow(clippy::ptr_arg)]
pub fn add_english_counter(&mut self, name: &String, getter: Vec<u16>) -> bool {
if self.internal.data.contains_key(name) {
sysinfo_debug!("Query::add_english_counter: doesn't have key `{:?}`", name);
return false;
}
unsafe {
let mut counter = 0;
let ret = PdhAddEnglishCounterW(
self.internal.query.0,
PCWSTR::from_raw(getter.as_ptr()),
0,
&mut counter,
);
if ret == ERROR_SUCCESS.0 {
self.internal.data.insert(name.clone(), HANDLE(counter));
} else {
sysinfo_debug!(
"Query::add_english_counter: failed to add counter '{}': {:x}...",
name,
ret,
);
return false;
}
}
true
}
pub fn refresh(&self) {
unsafe {
if PdhCollectQueryData(self.internal.query.0) != ERROR_SUCCESS.0 {
sysinfo_debug!("failed to refresh CPU data");
}
}
}
}
pub(crate) struct CpusWrapper {
pub(crate) global: CpuUsage,
cpus: Vec<Cpu>,
}
impl CpusWrapper {
pub fn new() -> Self {
Self {
global: CpuUsage {
percent: 0f32,
key_used: None,
},
cpus: Vec::new(),
}
}
pub fn global_cpu_usage(&self) -> f32 {
self.global.percent
}
pub fn cpus(&self) -> &[Cpu] {
&self.cpus
}
fn init_if_needed(&mut self, refresh_kind: CpuRefreshKind) {
if self.cpus.is_empty() {
self.cpus = init_cpus(refresh_kind);
}
}
pub fn len(&mut self) -> usize {
self.init_if_needed(CpuRefreshKind::nothing());
self.cpus.len()
}
pub fn iter_mut(&mut self, refresh_kind: CpuRefreshKind) -> impl Iterator<Item = &mut Cpu> {
self.init_if_needed(refresh_kind);
self.cpus.iter_mut()
}
pub fn get_frequencies(&mut self) {
let frequencies = get_frequencies(self.cpus.len());
for (cpu, frequency) in self.cpus.iter_mut().zip(frequencies) {
cpu.inner.set_frequency(frequency);
}
}
}
pub(crate) struct CpuUsage {
percent: f32,
pub(crate) key_used: Option<KeyHandler>,
}
impl CpuUsage {
pub(crate) fn set_cpu_usage(&mut self, value: f32) {
self.percent = value;
}
}
pub(crate) struct CpuInner {
name: String,
vendor_id: String,
usage: CpuUsage,
brand: String,
frequency: u64,
}
impl CpuInner {
pub(crate) fn cpu_usage(&self) -> f32 {
self.usage.percent
}
pub(crate) fn name(&self) -> &str {
&self.name
}
pub(crate) fn frequency(&self) -> u64 {
self.frequency
}
pub(crate) fn vendor_id(&self) -> &str {
&self.vendor_id
}
pub(crate) fn brand(&self) -> &str {
&self.brand
}
pub(crate) fn new_with_values(
name: String,
vendor_id: String,
brand: String,
frequency: u64,
) -> Self {
Self {
name,
usage: CpuUsage {
percent: 0f32,
key_used: None,
},
vendor_id,
brand,
frequency,
}
}
pub(crate) fn set_cpu_usage(&mut self, value: f32) {
self.usage.set_cpu_usage(value);
}
pub(crate) fn set_frequency(&mut self, value: u64) {
self.frequency = value;
}
}
fn get_vendor_id_not_great(info: &SYSTEM_INFO) -> String {
// https://docs.microsoft.com/fr-fr/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info
unsafe {
match info.Anonymous.Anonymous.wProcessorArchitecture {
SystemInformation::PROCESSOR_ARCHITECTURE_INTEL => "Intel x86",
SystemInformation::PROCESSOR_ARCHITECTURE_MIPS => "MIPS",
SystemInformation::PROCESSOR_ARCHITECTURE_ALPHA => "RISC Alpha",
SystemInformation::PROCESSOR_ARCHITECTURE_PPC => "PPC",
SystemInformation::PROCESSOR_ARCHITECTURE_SHX => "SHX",
SystemInformation::PROCESSOR_ARCHITECTURE_ARM => "ARM",
SystemInformation::PROCESSOR_ARCHITECTURE_IA64 => "Intel Itanium-based x64",
SystemInformation::PROCESSOR_ARCHITECTURE_ALPHA64 => "RISC Alpha x64",
SystemInformation::PROCESSOR_ARCHITECTURE_MSIL => "MSIL",
SystemInformation::PROCESSOR_ARCHITECTURE_AMD64 => "(Intel or AMD) x64",
SystemInformation::PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 => "Intel Itanium-based x86",
SystemInformation::PROCESSOR_ARCHITECTURE_NEUTRAL => "unknown",
SystemInformation::PROCESSOR_ARCHITECTURE_ARM64 => "ARM x64",
SystemInformation::PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64 => "ARM",
SystemInformation::PROCESSOR_ARCHITECTURE_IA32_ON_ARM64 => "Intel Itanium-based x86",
_ => "unknown",
}
.to_owned()
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub(crate) fn get_vendor_id_and_brand(info: &SYSTEM_INFO) -> (String, String) {
#[cfg(target_arch = "x86")]
use std::arch::x86::__cpuid;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::__cpuid;
unsafe fn add_u32(v: &mut Vec<u8>, i: u32) {
let i = &i as *const u32 as *const u8;
v.push(*i);
v.push(*i.offset(1));
v.push(*i.offset(2));
v.push(*i.offset(3));
}
unsafe {
// First, we try to get the complete name.
let res = __cpuid(0x80000000);
let n_ex_ids = res.eax;
let brand = if n_ex_ids >= 0x80000004 {
let mut extdata = Vec::with_capacity(5);
for i in 0x80000000..=n_ex_ids {
extdata.push(__cpuid(i));
}
// 4 * u32 * nb_entries
let mut out = Vec::with_capacity(4 * std::mem::size_of::<u32>() * 3);
for data in extdata.iter().take(5).skip(2) {
add_u32(&mut out, data.eax);
add_u32(&mut out, data.ebx);
add_u32(&mut out, data.ecx);
add_u32(&mut out, data.edx);
}
let mut pos = 0;
for e in out.iter() {
if *e == 0 {
break;
}
pos += 1;
}
match std::str::from_utf8(&out[..pos]) {
Ok(s) => s.to_owned(),
_ => String::new(),
}
} else {
String::new()
};
// Failed to get full name, let's retry for the short version!
let res = __cpuid(0);
let mut x = Vec::with_capacity(3 * std::mem::size_of::<u32>());
add_u32(&mut x, res.ebx);
add_u32(&mut x, res.edx);
add_u32(&mut x, res.ecx);
let mut pos = 0;
for e in x.iter() {
if *e == 0 {
break;
}
pos += 1;
}
let vendor_id = match std::str::from_utf8(&x[..pos]) {
Ok(s) => s.to_owned(),
Err(_) => get_vendor_id_not_great(info),
};
(vendor_id, brand)
}
}
#[cfg(all(not(target_arch = "x86_64"), not(target_arch = "x86")))]
pub(crate) fn get_vendor_id_and_brand(info: &SYSTEM_INFO) -> (String, String) {
(get_vendor_id_not_great(info), String::new())
}
#[inline]
pub(crate) fn get_key_used(p: &mut Cpu) -> &mut Option<KeyHandler> {
&mut p.inner.usage.key_used
}
// From https://stackoverflow.com/a/43813138:
//
// If your PC has 64 or fewer logical cpus installed, the above code will work fine. However,
// if your PC has more than 64 logical cpus installed, use GetActiveCpuCount() or
// GetLogicalCpuInformation() to determine the total number of logical cpus installed.
pub(crate) fn get_frequencies(nb_cpus: usize) -> Vec<u64> {
let size = nb_cpus * mem::size_of::<PROCESSOR_POWER_INFORMATION>();
let mut infos: Vec<PROCESSOR_POWER_INFORMATION> = Vec::with_capacity(nb_cpus);
unsafe {
if CallNtPowerInformation(
ProcessorInformation,
None,
0,
Some(infos.as_mut_ptr() as _),
size as _,
)
.is_ok()
{
infos.set_len(nb_cpus);
// infos.Number
return infos
.into_iter()
.map(|i| i.CurrentMhz as u64)
.collect::<Vec<_>>();
}
}
sysinfo_debug!("get_frequencies: CallNtPowerInformation failed");
vec![0; nb_cpus]
}
pub(crate) fn get_physical_core_count() -> Option<usize> {
// We cannot use the number of cpus here to pre calculate the buf size.
// `GetLogicalCpuInformationEx` with `RelationProcessorCore` passed to it not only returns
// the logical cores but also numa nodes.
//
// GetLogicalCpuInformationEx: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformationex
let mut needed_size = 0;
unsafe {
// This function call will always return an error as it only returns "success" when it
// has written at least one item in the buffer (which it cannot do here).
let _err = GetLogicalProcessorInformationEx(RelationAll, None, &mut needed_size);
let mut buf: Vec<u8> = Vec::with_capacity(needed_size as _);
loop {
// Needs to be updated for `Vec::reserve` to actually add additional capacity if
// `GetLogicalProcessorInformationEx` fails because the buffer isn't big enough.
buf.set_len(needed_size as _);
if GetLogicalProcessorInformationEx(
RelationAll,
Some(buf.as_mut_ptr().cast()),
&mut needed_size,
)
.is_ok()
{
break;
} else {
let e = Error::last_os_error();
// For some reasons, the function might return a size not big enough...
match e.raw_os_error() {
Some(value) if value == ERROR_INSUFFICIENT_BUFFER.0 as i32 => {}
_ => {
sysinfo_debug!(
"get_physical_core_count: GetLogicalCpuInformationEx failed"
);
return None;
}
}
}
let reserve = if needed_size as usize > buf.capacity() {
needed_size as usize - buf.capacity()
} else {
1
};
needed_size = match needed_size.checked_add(reserve as _) {
Some(new_size) => new_size,
None => {
sysinfo_debug!(
"get_physical_core_count: buffer size is too big ({} + {})",
needed_size,
reserve,
);
return None;
}
};
buf.reserve(reserve);
}
buf.set_len(needed_size as _);
let mut i = 0;
let raw_buf = buf.as_ptr();
let mut count = 0;
while i < buf.len() {
let p = &*(raw_buf.add(i) as *const SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX);
i += p.Size as usize;
if p.Relationship == RelationProcessorCore {
// Only count the physical cores.
count += 1;
}
}
Some(count)
}
}
fn init_cpus(refresh_kind: CpuRefreshKind) -> Vec<Cpu> {
unsafe {
let mut sys_info = SYSTEM_INFO::default();
GetSystemInfo(&mut sys_info);
let (vendor_id, brand) = get_vendor_id_and_brand(&sys_info);
let nb_cpus = sys_info.dwNumberOfProcessors as usize;
let frequencies = if refresh_kind.frequency() {
get_frequencies(nb_cpus)
} else {
vec![0; nb_cpus]
};
let mut ret = Vec::with_capacity(nb_cpus + 1);
for (nb, frequency) in frequencies.iter().enumerate() {
ret.push(Cpu {
inner: CpuInner::new_with_values(
format!("CPU {}", nb + 1),
vendor_id.clone(),
brand.clone(),
*frequency,
),
});
}
ret
}
}
pub(crate) struct KeyHandler {
pub unique_id: String,
}
impl KeyHandler {
pub fn new(unique_id: String) -> Self {
Self { unique_id }
}
}

438
vendor/sysinfo/src/windows/disk.rs vendored Normal file
View File

@@ -0,0 +1,438 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::sys::utils::HandleWrapper;
use crate::{Disk, DiskKind, DiskRefreshKind, DiskUsage};
use std::ffi::{OsStr, OsString};
use std::mem::size_of;
use std::os::windows::ffi::OsStringExt;
use std::path::Path;
use windows::core::{Error, HRESULT, PCWSTR};
use windows::Win32::Foundation::MAX_PATH;
use windows::Win32::Storage::FileSystem::{
FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDiskFreeSpaceExW, GetDriveTypeW,
GetVolumeInformationW, GetVolumePathNamesForVolumeNameW,
};
use windows::Win32::System::Ioctl::{
PropertyStandardQuery, StorageDeviceSeekPenaltyProperty, DEVICE_SEEK_PENALTY_DESCRIPTOR,
DISK_PERFORMANCE, IOCTL_DISK_PERFORMANCE, IOCTL_STORAGE_QUERY_PROPERTY, STORAGE_PROPERTY_QUERY,
};
use windows::Win32::System::SystemServices::FILE_READ_ONLY_VOLUME;
use windows::Win32::System::WindowsProgramming::{DRIVE_FIXED, DRIVE_REMOVABLE};
use windows::Win32::System::IO::DeviceIoControl;
/// Creates a copy of the first zero-terminated wide string in `buf`.
/// The copy includes the zero terminator.
fn from_zero_terminated(buf: &[u16]) -> Vec<u16> {
let end = buf.iter().position(|&x| x == 0).unwrap_or(buf.len());
buf[..=end].to_vec()
}
// Realistically, volume names are probably not longer than 44 characters,
// but the example in the Microsoft documentation uses MAX_PATH as well.
// https://learn.microsoft.com/en-us/windows/win32/fileio/displaying-volume-paths
const VOLUME_NAME_SIZE: usize = MAX_PATH as usize + 1;
const ERROR_NO_MORE_FILES: HRESULT = windows::Win32::Foundation::ERROR_NO_MORE_FILES.to_hresult();
const ERROR_MORE_DATA: HRESULT = windows::Win32::Foundation::ERROR_MORE_DATA.to_hresult();
/// Returns a list of zero-terminated wide strings containing volume GUID paths.
/// Volume GUID paths have the form `\\?\{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\`.
///
/// Rather confusingly, the Win32 API _also_ calls these "volume names".
pub(crate) fn get_volume_guid_paths() -> Vec<Vec<u16>> {
let mut volume_names = Vec::new();
unsafe {
let mut buf = Box::new([0u16; VOLUME_NAME_SIZE]);
let Ok(handle) = FindFirstVolumeW(&mut buf[..]) else {
sysinfo_debug!(
"Error: FindFirstVolumeW() = {:?}",
Error::from_win32().code()
);
return Vec::new();
};
volume_names.push(from_zero_terminated(&buf[..]));
loop {
if FindNextVolumeW(handle, &mut buf[..]).is_err() {
if Error::from_win32().code() != ERROR_NO_MORE_FILES {
sysinfo_debug!("Error: FindNextVolumeW = {}", Error::from_win32().code());
}
break;
}
volume_names.push(from_zero_terminated(&buf[..]));
}
if FindVolumeClose(handle).is_err() {
sysinfo_debug!("Error: FindVolumeClose = {:?}", Error::from_win32().code());
};
}
volume_names
}
/// Given a volume GUID path (`\\?\{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\`), returns all
/// volume paths (drive letters and mount paths) associated with it
/// as zero terminated wide strings.
///
/// # Safety
/// `volume_name` must contain a zero-terminated wide string.
pub(crate) unsafe fn get_volume_path_names_for_volume_name(
volume_guid_path: &[u16],
) -> Vec<Vec<u16>> {
let volume_guid_path = PCWSTR::from_raw(volume_guid_path.as_ptr());
// Initial buffer size is just a guess. There is no direct connection between MAX_PATH
// the output of GetVolumePathNamesForVolumeNameW.
let mut path_names_buf = vec![0u16; MAX_PATH as usize];
let mut path_names_output_size = 0u32;
for _ in 0..10 {
let volume_path_names = GetVolumePathNamesForVolumeNameW(
volume_guid_path,
Some(path_names_buf.as_mut_slice()),
&mut path_names_output_size,
);
let code = volume_path_names.map_err(|_| Error::from_win32().code());
match code {
Ok(()) => break,
Err(ERROR_MORE_DATA) => {
// We need a bigger buffer. path_names_output_size contains the required buffer size.
path_names_buf = vec![0u16; path_names_output_size as usize];
continue;
}
Err(_e) => {
sysinfo_debug!("Error: GetVolumePathNamesForVolumeNameW() = {}", _e);
return Vec::new();
}
}
}
// path_names_buf contains multiple zero terminated wide strings.
// An additional zero terminates the list.
let mut path_names = Vec::new();
let mut buf = &path_names_buf[..];
while !buf.is_empty() && buf[0] != 0 {
let path = from_zero_terminated(buf);
buf = &buf[path.len()..];
path_names.push(path);
}
path_names
}
pub(crate) struct DiskInner {
type_: DiskKind,
name: OsString,
file_system: OsString,
mount_point: Vec<u16>,
s_mount_point: OsString,
total_space: u64,
available_space: u64,
is_removable: bool,
is_read_only: bool,
device_path: Vec<u16>,
old_written_bytes: u64,
old_read_bytes: u64,
written_bytes: u64,
read_bytes: u64,
updated: bool,
}
impl DiskInner {
pub(crate) fn kind(&self) -> DiskKind {
self.type_
}
pub(crate) fn name(&self) -> &OsStr {
&self.name
}
pub(crate) fn file_system(&self) -> &OsStr {
&self.file_system
}
pub(crate) fn mount_point(&self) -> &Path {
self.s_mount_point.as_ref()
}
pub(crate) fn total_space(&self) -> u64 {
self.total_space
}
pub(crate) fn available_space(&self) -> u64 {
self.available_space
}
pub(crate) fn is_removable(&self) -> bool {
self.is_removable
}
pub(crate) fn is_read_only(&self) -> bool {
self.is_read_only
}
pub(crate) fn refresh_specifics(&mut self, refreshes: DiskRefreshKind) -> bool {
if refreshes.kind() || refreshes.io_usage() {
unsafe {
if let Some(handle) =
HandleWrapper::new_from_file(&self.device_path, Default::default())
{
if refreshes.kind() && self.type_ == DiskKind::Unknown(-1) {
self.type_ = get_disk_kind(&handle);
}
if refreshes.io_usage() {
if let Some((read_bytes, written_bytes)) = get_disk_io(handle) {
self.old_read_bytes = self.read_bytes;
self.old_written_bytes = self.written_bytes;
self.read_bytes = read_bytes;
self.written_bytes = written_bytes;
} else {
sysinfo_debug!("Failed to update disk i/o stats");
}
}
}
}
}
if refreshes.storage() {
if let Some((total_space, available_space)) =
unsafe { get_drive_size(&self.mount_point) }
{
self.total_space = total_space;
self.available_space = available_space;
}
}
true
}
pub(crate) fn usage(&self) -> DiskUsage {
DiskUsage {
read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes),
total_read_bytes: self.read_bytes,
written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
total_written_bytes: self.written_bytes,
}
}
}
pub(crate) struct DisksInner {
pub(crate) disks: Vec<Disk>,
}
impl DisksInner {
pub(crate) fn new() -> Self {
Self {
disks: Vec::with_capacity(2),
}
}
pub(crate) fn from_vec(disks: Vec<Disk>) -> Self {
Self { disks }
}
pub(crate) fn into_vec(self) -> Vec<Disk> {
self.disks
}
pub(crate) fn refresh_specifics(
&mut self,
remove_not_listed_disks: bool,
refreshes: DiskRefreshKind,
) {
unsafe {
get_list(&mut self.disks, remove_not_listed_disks, refreshes);
}
}
pub(crate) fn list(&self) -> &[Disk] {
&self.disks
}
pub(crate) fn list_mut(&mut self) -> &mut [Disk] {
&mut self.disks
}
}
unsafe fn get_drive_size(mount_point: &[u16]) -> Option<(u64, u64)> {
let mut total_size = 0;
let mut available_space = 0;
let lpdirectoryname = PCWSTR::from_raw(mount_point.as_ptr());
if GetDiskFreeSpaceExW(
lpdirectoryname,
None,
Some(&mut total_size),
Some(&mut available_space),
)
.is_ok()
{
Some((total_size, available_space))
} else {
None
}
}
pub(crate) unsafe fn get_list(
disks: &mut Vec<Disk>,
remove_not_listed_disks: bool,
refreshes: DiskRefreshKind,
) {
for volume_name in get_volume_guid_paths() {
let mount_paths = get_volume_path_names_for_volume_name(&volume_name[..]);
if mount_paths.is_empty() {
continue;
}
let raw_volume_name = PCWSTR::from_raw(volume_name.as_ptr());
let drive_type = GetDriveTypeW(raw_volume_name);
if drive_type != DRIVE_FIXED && drive_type != DRIVE_REMOVABLE {
continue;
}
let is_removable = drive_type == DRIVE_REMOVABLE;
let mut name = [0u16; MAX_PATH as usize + 1];
let mut file_system = [0u16; 32];
let mut flags = 0;
let volume_info_res = GetVolumeInformationW(
raw_volume_name,
Some(&mut name),
None,
None,
Some(&mut flags),
Some(&mut file_system),
)
.is_ok();
if !volume_info_res {
sysinfo_debug!(
"Error: GetVolumeInformationW = {:?}",
Error::from_win32().code()
);
continue;
}
let is_read_only = (flags & FILE_READ_ONLY_VOLUME) != 0;
// The device path is the volume name without the trailing backslash.
let device_path = volume_name[..(volume_name.len() - 2)]
.iter()
.copied()
.chain([0])
.collect::<Vec<_>>();
let name = os_string_from_zero_terminated(&name);
let file_system = os_string_from_zero_terminated(&file_system);
for mount_path in mount_paths {
if let Some(disk) = disks
.iter_mut()
.find(|d| d.inner.mount_point == mount_path && d.inner.file_system == file_system)
{
disk.refresh_specifics(refreshes);
disk.inner.updated = true;
continue;
}
let mut disk = DiskInner {
type_: DiskKind::Unknown(-1),
name: name.clone(),
file_system: file_system.clone(),
s_mount_point: OsString::from_wide(&mount_path[..mount_path.len() - 1]),
mount_point: mount_path,
total_space: 0,
available_space: 0,
is_removable,
is_read_only,
device_path: device_path.clone(),
old_read_bytes: 0,
old_written_bytes: 0,
read_bytes: 0,
written_bytes: 0,
updated: true,
};
disk.refresh_specifics(refreshes);
disks.push(Disk { inner: disk });
}
}
if remove_not_listed_disks {
disks.retain_mut(|disk| {
if !disk.inner.updated {
return false;
}
disk.inner.updated = false;
true
});
} else {
for c in disks.iter_mut() {
c.inner.updated = false;
}
}
}
fn os_string_from_zero_terminated(name: &[u16]) -> OsString {
let len = name.iter().position(|&x| x == 0).unwrap_or(name.len());
OsString::from_wide(&name[..len])
}
unsafe fn get_disk_kind(handle: &HandleWrapper) -> DiskKind {
let spq_trim = STORAGE_PROPERTY_QUERY {
PropertyId: StorageDeviceSeekPenaltyProperty,
QueryType: PropertyStandardQuery,
AdditionalParameters: [0],
};
let mut result: DEVICE_SEEK_PENALTY_DESCRIPTOR = unsafe { std::mem::zeroed() };
let mut dw_size = 0;
let device_io_control = unsafe {
DeviceIoControl(
handle.0,
IOCTL_STORAGE_QUERY_PROPERTY,
Some(&spq_trim as *const STORAGE_PROPERTY_QUERY as *const _),
size_of::<STORAGE_PROPERTY_QUERY>() as _,
Some(&mut result as *mut DEVICE_SEEK_PENALTY_DESCRIPTOR as *mut _),
size_of::<DEVICE_SEEK_PENALTY_DESCRIPTOR>() as _,
Some(&mut dw_size),
None,
)
.is_ok()
};
if !device_io_control || dw_size != size_of::<DEVICE_SEEK_PENALTY_DESCRIPTOR>() as _ {
DiskKind::Unknown(-1)
} else {
let is_hdd = result.IncursSeekPenalty.as_bool();
if is_hdd {
DiskKind::HDD
} else {
DiskKind::SSD
}
}
}
/// Returns a tuple consisting of the total number of bytes read and written by the volume with the
/// specified device path
fn get_disk_io(handle: HandleWrapper) -> Option<(u64, u64)> {
let mut disk_perf = DISK_PERFORMANCE::default();
let mut bytes_returned = 0;
// SAFETY: the handle is checked for validity above
unsafe {
// See <https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-ioctl_disk_performance> for reference
DeviceIoControl(
handle.0,
IOCTL_DISK_PERFORMANCE,
None, // Must be None as per docs
0,
Some(&mut disk_perf as *mut _ as _),
size_of::<DISK_PERFORMANCE>() as u32,
Some(&mut bytes_returned),
None,
)
}
.map_err(|err| {
sysinfo_debug!("Error: DeviceIoControl(IOCTL_DISK_PERFORMANCE) = {:?}", err);
err
})
.ok()?;
Some((
disk_perf.BytesRead.try_into().ok()?,
disk_perf.BytesWritten.try_into().ok()?,
))
}

119
vendor/sysinfo/src/windows/groups.rs vendored Normal file
View File

@@ -0,0 +1,119 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::sys::utils::to_utf8_str;
use crate::windows::sid::Sid;
use crate::{Gid, Group, GroupInner};
use std::ptr::null_mut;
use windows::core::PCWSTR;
use windows::Win32::Foundation::ERROR_MORE_DATA;
use windows::Win32::NetworkManagement::NetManagement::{
NERR_Success, NetApiBufferFree, NetGroupEnum, NetGroupGetInfo, GROUP_INFO_0, GROUP_INFO_3,
MAX_PREFERRED_LENGTH,
};
impl GroupInner {
pub(crate) fn new(id: Gid, name: String) -> Self {
Self { id, name }
}
pub(crate) fn id(&self) -> &Gid {
&self.id
}
pub(crate) fn name(&self) -> &str {
&self.name
}
}
struct NetApiBuffer<T>(*mut T);
impl<T> Drop for NetApiBuffer<T> {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { NetApiBufferFree(Some(self.0.cast())) };
}
}
}
impl<T> Default for NetApiBuffer<T> {
fn default() -> Self {
Self(null_mut())
}
}
impl<T> NetApiBuffer<T> {
pub fn inner_mut(&mut self) -> &mut *mut T {
assert!(self.0.is_null());
&mut self.0
}
pub unsafe fn inner_mut_as_bytes(&mut self) -> &mut *mut u8 {
// https://doc.rust-lang.org/std/mem/fn.transmute.html
// Turning an &mut T into an &mut U:
&mut *(self.inner_mut() as *mut *mut T as *mut *mut u8)
}
}
pub(crate) fn get_groups(groups: &mut Vec<Group>) {
groups.clear();
let mut resume_handle: usize = 0;
unsafe {
loop {
let mut buffer: NetApiBuffer<GROUP_INFO_0> = Default::default();
let mut nb_read = 0;
let mut total = 0;
let status = NetGroupEnum(
PCWSTR::null(),
0,
buffer.inner_mut_as_bytes(),
MAX_PREFERRED_LENGTH,
&mut nb_read,
&mut total,
Some(&mut resume_handle),
);
if status == NERR_Success || status == ERROR_MORE_DATA.0 {
let entries = std::slice::from_raw_parts(buffer.0, nb_read as _);
for entry in entries {
if entry.grpi0_name.is_null() {
continue;
}
let mut group: NetApiBuffer<GROUP_INFO_3> = Default::default();
if NetGroupGetInfo(
PCWSTR::null(),
PCWSTR::from_raw(entry.grpi0_name.as_ptr()),
3,
group.inner_mut_as_bytes(),
) == NERR_Success
{
if let Some(_sid) = Sid::from_psid((*group.0).grpi3_group_sid) {
// Get the account name from the SID (because it's usually
// a better name), but fall back to the name we were given
// if this fails.
let name = to_utf8_str(entry.grpi0_name);
groups.push(Group {
inner: GroupInner::new(Gid(0), name),
});
}
}
}
} else {
sysinfo_debug!(
"NetGroupEnum error: {}",
if status == windows::Win32::Foundation::ERROR_ACCESS_DENIED.0 {
"access denied"
} else if status == windows::Win32::Foundation::ERROR_INVALID_LEVEL.0 {
"invalid level"
} else {
"unknown error"
}
);
}
if status != ERROR_MORE_DATA.0 {
break;
}
}
}
}

74
vendor/sysinfo/src/windows/mod.rs vendored Normal file
View File

@@ -0,0 +1,74 @@
// Take a look at the license at the top of the repository in the LICENSE file.
mod utils;
cfg_if! {
if #[cfg(feature = "system")] {
mod process;
mod cpu;
mod system;
pub(crate) use self::cpu::CpuInner;
pub(crate) use self::process::ProcessInner;
pub(crate) use self::system::SystemInner;
pub use self::system::{MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS};
}
if #[cfg(feature = "disk")] {
mod disk;
pub(crate) use self::disk::{DiskInner, DisksInner};
}
if #[cfg(feature = "component")] {
pub mod component;
pub(crate) use self::component::{ComponentInner, ComponentsInner};
}
if #[cfg(feature = "network")] {
mod network;
pub(crate) mod network_helper;
pub(crate) use self::network::{NetworkDataInner, NetworksInner};
}
if #[cfg(feature = "user")] {
mod groups;
mod users;
pub(crate) use self::groups::get_groups;
pub(crate) use self::users::get_users;
pub(crate) use self::users::UserInner;
}
if #[cfg(any(feature = "user", feature = "system"))] {
mod sid;
pub(crate) use self::sid::Sid;
}
}
#[doc = include_str!("../../md_doc/is_supported.md")]
pub const IS_SUPPORTED_SYSTEM: bool = true;
// Make formattable by rustfmt.
#[cfg(any())]
mod component;
#[cfg(any())]
mod cpu;
#[cfg(any())]
mod disk;
#[cfg(any())]
mod groups;
#[cfg(any())]
mod network;
#[cfg(any())]
mod network_helper;
#[cfg(any())]
mod process;
#[cfg(any())]
mod sid;
#[cfg(any())]
mod system;
#[cfg(any())]
mod users;

246
vendor/sysinfo/src/windows/network.rs vendored Normal file
View File

@@ -0,0 +1,246 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::network::refresh_networks_addresses;
use crate::{IpNetwork, MacAddr, NetworkData};
use std::collections::{hash_map, HashMap};
use windows::Win32::NetworkManagement::IpHelper::{FreeMibTable, GetIfTable2, MIB_IF_TABLE2};
use windows::Win32::NetworkManagement::Ndis::MediaConnectStateDisconnected;
macro_rules! old_and_new {
($ty_:expr, $name:ident, $old:ident, $new_val:expr) => {{
$ty_.$old = $ty_.$name;
$ty_.$name = $new_val;
}};
}
pub(crate) struct NetworksInner {
pub(crate) interfaces: HashMap<String, NetworkData>,
}
impl NetworksInner {
pub(crate) fn new() -> Self {
Self {
interfaces: HashMap::new(),
}
}
pub(crate) fn list(&self) -> &HashMap<String, NetworkData> {
&self.interfaces
}
pub(crate) fn refresh(&mut self, remove_not_listed_interfaces: bool) {
let mut table: *mut MIB_IF_TABLE2 = std::ptr::null_mut();
unsafe {
if GetIfTable2(&mut table).is_err() {
return;
}
for (_, data) in self.interfaces.iter_mut() {
data.inner.updated = false;
}
// In here, this is tricky: we have to filter out the software interfaces to only keep
// the hardware ones. To do so, we first check the connection potential speed (if 0, not
// interesting), then we check its state: if not open, not interesting either. And finally,
// we count the members of a same group: if there is more than 1, then it's software level.
let mut groups = HashMap::new();
let mut indexes = Vec::new();
let ptr = (*table).Table.as_ptr();
for i in 0..(*table).NumEntries {
let ptr = &*ptr.offset(i as _);
if (ptr.TransmitLinkSpeed == 0 && ptr.ReceiveLinkSpeed == 0)
|| ptr.MediaConnectState == MediaConnectStateDisconnected
|| ptr.PhysicalAddressLength == 0
{
continue;
}
let id = vec![
ptr.InterfaceGuid.data2,
ptr.InterfaceGuid.data3,
ptr.InterfaceGuid.data4[0] as _,
ptr.InterfaceGuid.data4[1] as _,
ptr.InterfaceGuid.data4[2] as _,
ptr.InterfaceGuid.data4[3] as _,
ptr.InterfaceGuid.data4[4] as _,
ptr.InterfaceGuid.data4[5] as _,
ptr.InterfaceGuid.data4[6] as _,
ptr.InterfaceGuid.data4[7] as _,
];
let entry = groups.entry(id.clone()).or_insert(0);
*entry += 1;
if *entry > 1 {
continue;
}
indexes.push((i, id));
}
for (i, id) in indexes {
let ptr = &*ptr.offset(i as _);
if *groups.get(&id).unwrap_or(&0) > 1 {
continue;
}
let mut pos = 0;
for x in ptr.Alias.iter() {
if *x == 0 {
break;
}
pos += 1;
}
let interface_name = match String::from_utf16(&ptr.Alias[..pos]) {
Ok(s) => s,
_ => continue,
};
let mtu = ptr.Mtu as u64;
match self.interfaces.entry(interface_name) {
hash_map::Entry::Occupied(mut e) => {
let interface = e.get_mut();
let interface = &mut interface.inner;
old_and_new!(interface, current_out, old_out, ptr.OutOctets);
old_and_new!(interface, current_in, old_in, ptr.InOctets);
old_and_new!(
interface,
packets_in,
old_packets_in,
ptr.InUcastPkts.saturating_add(ptr.InNUcastPkts)
);
old_and_new!(
interface,
packets_out,
old_packets_out,
ptr.OutUcastPkts.saturating_add(ptr.OutNUcastPkts)
);
old_and_new!(interface, errors_in, old_errors_in, ptr.InErrors);
old_and_new!(interface, errors_out, old_errors_out, ptr.OutErrors);
if interface.mtu != mtu {
interface.mtu = mtu;
}
interface.updated = true;
}
hash_map::Entry::Vacant(e) => {
let packets_in = ptr.InUcastPkts.saturating_add(ptr.InNUcastPkts);
let packets_out = ptr.OutUcastPkts.saturating_add(ptr.OutNUcastPkts);
e.insert(NetworkData {
inner: NetworkDataInner {
current_out: ptr.OutOctets,
old_out: ptr.OutOctets,
current_in: ptr.InOctets,
old_in: ptr.InOctets,
packets_in,
old_packets_in: packets_in,
packets_out,
old_packets_out: packets_out,
errors_in: ptr.InErrors,
old_errors_in: ptr.InErrors,
errors_out: ptr.OutErrors,
old_errors_out: ptr.OutErrors,
mac_addr: MacAddr::UNSPECIFIED,
ip_networks: vec![],
mtu,
updated: true,
},
});
}
}
}
FreeMibTable(table as _);
}
if remove_not_listed_interfaces {
// Remove interfaces which are gone.
self.interfaces.retain(|_, i| {
if !i.inner.updated {
return false;
}
i.inner.updated = false;
true
});
}
// Refresh all interfaces' addresses.
refresh_networks_addresses(&mut self.interfaces);
}
}
pub(crate) struct NetworkDataInner {
current_out: u64,
old_out: u64,
current_in: u64,
old_in: u64,
packets_in: u64,
old_packets_in: u64,
packets_out: u64,
old_packets_out: u64,
errors_in: u64,
old_errors_in: u64,
errors_out: u64,
old_errors_out: u64,
updated: bool,
pub(crate) mac_addr: MacAddr,
pub(crate) ip_networks: Vec<IpNetwork>,
/// Interface Maximum Transfer Unit (MTU)
mtu: u64,
}
impl NetworkDataInner {
pub(crate) fn received(&self) -> u64 {
self.current_in.saturating_sub(self.old_in)
}
pub(crate) fn total_received(&self) -> u64 {
self.current_in
}
pub(crate) fn transmitted(&self) -> u64 {
self.current_out.saturating_sub(self.old_out)
}
pub(crate) fn total_transmitted(&self) -> u64 {
self.current_out
}
pub(crate) fn packets_received(&self) -> u64 {
self.packets_in.saturating_sub(self.old_packets_in)
}
pub(crate) fn total_packets_received(&self) -> u64 {
self.packets_in
}
pub(crate) fn packets_transmitted(&self) -> u64 {
self.packets_out.saturating_sub(self.old_packets_out)
}
pub(crate) fn total_packets_transmitted(&self) -> u64 {
self.packets_out
}
pub(crate) fn errors_on_received(&self) -> u64 {
self.errors_in.saturating_sub(self.old_errors_in)
}
pub(crate) fn total_errors_on_received(&self) -> u64 {
self.errors_in
}
pub(crate) fn errors_on_transmitted(&self) -> u64 {
self.errors_out.saturating_sub(self.old_errors_out)
}
pub(crate) fn total_errors_on_transmitted(&self) -> u64 {
self.errors_out
}
pub(crate) fn mac_address(&self) -> MacAddr {
self.mac_addr
}
pub(crate) fn ip_networks(&self) -> &[IpNetwork] {
&self.ip_networks
}
pub(crate) fn mtu(&self) -> u64 {
self.mtu
}
}

View File

@@ -0,0 +1,171 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use std::collections::{HashMap, HashSet};
use std::net::IpAddr;
use std::ptr::{null_mut, NonNull};
use windows::Win32::Foundation::{ERROR_BUFFER_OVERFLOW, ERROR_SUCCESS};
use windows::Win32::NetworkManagement::IpHelper::{
GetAdaptersAddresses, GAA_FLAG_SKIP_ANYCAST, GAA_FLAG_SKIP_DNS_SERVER, GAA_FLAG_SKIP_MULTICAST,
IP_ADAPTER_ADDRESSES_LH, IP_ADAPTER_UNICAST_ADDRESS_LH,
};
use windows::Win32::Networking::WinSock::{
AF_INET, AF_INET6, AF_UNSPEC, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6,
};
use crate::{IpNetwork, MacAddr};
/// this iterator yields an interface name and address
pub(crate) struct InterfaceAddressIterator {
/// The first item in the linked list
buf: *mut IP_ADAPTER_ADDRESSES_LH,
/// The current adapter
adapter: *mut IP_ADAPTER_ADDRESSES_LH,
}
impl InterfaceAddressIterator {
fn new() -> Self {
Self {
buf: null_mut(),
adapter: null_mut(),
}
}
unsafe fn realloc(mut self, size: libc::size_t) -> Result<Self, String> {
let new_buf = libc::realloc(self.buf as _, size) as *mut IP_ADAPTER_ADDRESSES_LH;
if new_buf.is_null() {
// insufficient memory available
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/malloc?view=msvc-170#return-value
// malloc is not documented to set the last-error code
Err("failed to allocate memory for IP_ADAPTER_ADDRESSES".to_string())
} else {
self.buf = new_buf;
self.adapter = new_buf;
Ok(self)
}
}
}
impl Iterator for InterfaceAddressIterator {
type Item = (String, MacAddr);
fn next(&mut self) -> Option<Self::Item> {
if self.adapter.is_null() {
return None;
}
unsafe {
let adapter = self.adapter;
// Move to the next adapter
self.adapter = (*adapter).Next;
if let Ok(interface_name) = (*adapter).FriendlyName.to_string() {
// take the first 6 bytes and return the MAC address instead
let [mac @ .., _, _] = (*adapter).PhysicalAddress;
Some((interface_name, MacAddr(mac)))
} else {
// Not sure whether error can occur when parsing adapter name.
self.next()
}
}
}
}
impl InterfaceAddressIterator {
pub fn generate_ip_networks(&mut self) -> HashMap<String, HashSet<IpNetwork>> {
let mut results = HashMap::new();
while !self.adapter.is_null() {
unsafe {
let adapter = self.adapter;
// Move to the next adapter
self.adapter = (*adapter).Next;
if let Ok(interface_name) = (*adapter).FriendlyName.to_string() {
let ip_networks = get_ip_networks((*adapter).FirstUnicastAddress);
results.insert(interface_name, ip_networks);
}
}
}
results
}
}
pub(crate) unsafe fn get_interface_ip_networks() -> HashMap<String, HashSet<IpNetwork>> {
match get_interface_address() {
Ok(mut interface_iter) => interface_iter.generate_ip_networks(),
_ => HashMap::new(),
}
}
impl Drop for InterfaceAddressIterator {
fn drop(&mut self) {
unsafe {
libc::free(self.buf as _);
}
}
}
pub(crate) unsafe fn get_interface_address() -> Result<InterfaceAddressIterator, String> {
// https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses#remarks
// A 15k buffer is recommended
let mut size: u32 = 15 * 1024;
let mut ret = ERROR_SUCCESS.0;
let mut iterator = InterfaceAddressIterator::new();
// https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses#examples
// Try to retrieve adapter information up to 3 times
for _ in 0..3 {
iterator = iterator.realloc(size as _)?;
ret = GetAdaptersAddresses(
AF_UNSPEC.0.into(),
GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_DNS_SERVER,
None,
Some(iterator.buf),
&mut size,
);
if ret == ERROR_SUCCESS.0 {
return Ok(iterator);
} else if ret != ERROR_BUFFER_OVERFLOW.0 {
break;
}
// if the given memory size is too small to hold the adapter information,
// the SizePointer returned will point to the required size of the buffer,
// and we should continue.
// Otherwise, break the loop and check the return code again
}
Err(format!("GetAdaptersAddresses() failed with code {ret}"))
}
fn get_ip_networks(mut prefixes_ptr: *mut IP_ADAPTER_UNICAST_ADDRESS_LH) -> HashSet<IpNetwork> {
let mut ip_networks = HashSet::new();
while !prefixes_ptr.is_null() {
let prefix = unsafe { prefixes_ptr.read_unaligned() };
if let Some(socket_address) = NonNull::new(prefix.Address.lpSockaddr) {
if let Some(ipaddr) = get_ip_address_from_socket_address(socket_address) {
ip_networks.insert(IpNetwork {
addr: ipaddr,
prefix: prefix.OnLinkPrefixLength,
});
}
}
prefixes_ptr = prefix.Next;
}
ip_networks
}
/// Converts a Windows socket address to an ip address.
fn get_ip_address_from_socket_address(socket_address: NonNull<SOCKADDR>) -> Option<IpAddr> {
let socket_address_family = unsafe { socket_address.as_ref().sa_family };
match socket_address_family {
AF_INET => {
let socket_address = unsafe { socket_address.cast::<SOCKADDR_IN>().as_ref() };
let address = unsafe { socket_address.sin_addr.S_un.S_addr };
let ipv4_address = IpAddr::from(address.to_ne_bytes());
Some(ipv4_address)
}
AF_INET6 => {
let socket_address = unsafe { socket_address.cast::<SOCKADDR_IN6>().as_ref() };
let address = unsafe { socket_address.sin6_addr.u.Byte };
let ipv6_address = IpAddr::from(address);
Some(ipv6_address)
}
_ => None,
}
}

1118
vendor/sysinfo/src/windows/process.rs vendored Normal file

File diff suppressed because it is too large Load Diff

157
vendor/sysinfo/src/windows/sid.rs vendored Normal file
View File

@@ -0,0 +1,157 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use std::{fmt::Display, str::FromStr};
use windows::core::{PCWSTR, PWSTR};
#[cfg(feature = "user")]
use windows::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
use windows::Win32::Foundation::{LocalFree, HLOCAL, PSID};
use windows::Win32::Security::Authorization::{ConvertSidToStringSidW, ConvertStringSidToSidW};
use windows::Win32::Security::{CopySid, GetLengthSid, IsValidSid};
#[cfg(feature = "user")]
use windows::Win32::Security::{LookupAccountSidW, SidTypeUnknown};
use crate::sys::utils::to_utf8_str;
#[doc = include_str!("../../md_doc/sid.md")]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Sid {
sid: Vec<u8>,
}
impl Sid {
/// Creates an `Sid` by making a copy of the given raw SID.
pub(crate) unsafe fn from_psid(psid: PSID) -> Option<Self> {
if psid.is_invalid() {
return None;
}
if !IsValidSid(psid).as_bool() {
return None;
}
let length = GetLengthSid(psid);
let mut sid = vec![0; length as usize];
if CopySid(length, PSID(sid.as_mut_ptr().cast()), psid).is_err() {
sysinfo_debug!("CopySid failed: {:?}", std::io::Error::last_os_error());
return None;
}
// We are making assumptions about the SID internal structure,
// and these only hold if the revision is 1
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid
// Namely:
// 1. SIDs can be compared directly (memcmp).
// 2. Following from this, to hash a SID we can just hash its bytes.
// These are the basis for deriving PartialEq, Eq, and Hash.
// And since we also need PartialOrd and Ord, we might as well derive them
// too. The default implementation will be consistent with Eq,
// and we don't care about the actual order, just that there is one.
// So it should all work out.
// Why bother with this? Because it makes the implementation that
// much simpler :)
assert_eq!(sid[0], 1, "Expected SID revision to be 1");
Some(Self { sid })
}
/// Retrieves the account name of this SID.
#[cfg(feature = "user")]
pub(crate) fn account_name(&self) -> Option<String> {
unsafe {
let mut name_len = 0;
let mut domain_len = 0;
let mut name_use = SidTypeUnknown;
let sid = PSID((self.sid.as_ptr() as *mut u8).cast());
if let Err(err) = LookupAccountSidW(
PCWSTR::null(),
sid,
PWSTR::null(),
&mut name_len,
PWSTR::null(),
&mut domain_len,
&mut name_use,
) {
if err.code() != ERROR_INSUFFICIENT_BUFFER.to_hresult() {
sysinfo_debug!("LookupAccountSidW failed: {:?}", err);
return None;
}
}
let mut name = vec![0; name_len as usize];
// Reset length to 0 since we're still passing a NULL pointer
// for the domain.
domain_len = 0;
if LookupAccountSidW(
PCWSTR::null(),
sid,
PWSTR::from_raw(name.as_mut_ptr()),
&mut name_len,
PWSTR::null(),
&mut domain_len,
&mut name_use,
)
.is_err()
{
sysinfo_debug!(
"LookupAccountSidW failed: {:?}",
std::io::Error::last_os_error()
);
return None;
}
Some(to_utf8_str(PWSTR::from_raw(name.as_mut_ptr())))
}
}
}
impl Display for Sid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
unsafe fn convert_sid_to_string_sid(sid: PSID) -> Option<String> {
let mut string_sid = PWSTR::null();
if let Err(_err) = ConvertSidToStringSidW(sid, &mut string_sid) {
sysinfo_debug!("ConvertSidToStringSidW failed: {:?}", _err);
return None;
}
let result = to_utf8_str(string_sid);
let _err = LocalFree(HLOCAL(string_sid.0 as _));
Some(result)
}
let string_sid =
unsafe { convert_sid_to_string_sid(PSID((self.sid.as_ptr() as *mut u8).cast())) };
let string_sid = string_sid.ok_or(std::fmt::Error)?;
write!(f, "{string_sid}")
}
}
impl FromStr for Sid {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
unsafe {
let mut string_sid: Vec<u16> = s.encode_utf16().collect();
string_sid.push(0);
let mut psid = PSID::default();
if let Err(err) =
ConvertStringSidToSidW(PCWSTR::from_raw(string_sid.as_ptr()), &mut psid)
{
return Err(format!("ConvertStringSidToSidW failed: {:?}", err));
}
let sid = Self::from_psid(psid);
let _err = LocalFree(HLOCAL(psid.0 as _));
// Unwrapping because ConvertStringSidToSidW should've performed
// all the necessary validations. If it returned an invalid SID,
// we better fail fast.
Ok(sid.unwrap())
}
}
}

619
vendor/sysinfo/src/windows/system.rs vendored Normal file
View File

@@ -0,0 +1,619 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::{
Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, ProcessRefreshKind, ProcessesToUpdate,
};
use crate::sys::cpu::*;
use crate::{Process, ProcessInner};
use std::collections::HashMap;
use std::ffi::OsStr;
use std::mem::{size_of, zeroed};
use std::os::windows::ffi::OsStrExt;
use std::time::{Duration, SystemTime};
use windows::core::{PCWSTR, PWSTR};
use windows::Win32::Foundation::{self, HANDLE, STILL_ACTIVE};
use windows::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, TH32CS_SNAPPROCESS,
};
use windows::Win32::System::ProcessStatus::{K32GetPerformanceInfo, PERFORMANCE_INFORMATION};
use windows::Win32::System::Registry::{
RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY, HKEY_LOCAL_MACHINE, KEY_READ, REG_NONE,
};
use windows::Win32::System::SystemInformation::{self, GetSystemInfo};
use windows::Win32::System::SystemInformation::{
ComputerNamePhysicalDnsHostname, GetComputerNameExW, GetTickCount64, GlobalMemoryStatusEx,
MEMORYSTATUSEX, SYSTEM_INFO,
};
use windows::Win32::System::Threading::GetExitCodeProcess;
declare_signals! {
(),
Signal::Kill => (),
_ => None,
}
#[doc = include_str!("../../md_doc/supported_signals.md")]
pub const SUPPORTED_SIGNALS: &[crate::Signal] = supported_signals();
#[doc = include_str!("../../md_doc/minimum_cpu_update_interval.md")]
pub const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(200);
const WINDOWS_ELEVEN_BUILD_NUMBER: u32 = 22000;
impl SystemInner {
fn is_windows_eleven() -> bool {
WINDOWS_ELEVEN_BUILD_NUMBER
<= Self::kernel_version()
.unwrap_or_default()
.parse()
.unwrap_or(0)
}
}
/// Calculates system boot time in seconds with improved precision.
/// Uses nanoseconds throughout to avoid rounding errors in uptime calculation,
/// converting to seconds only at the end for stable results. Result is capped
/// within u64 limits to handle edge cases.
unsafe fn boot_time() -> u64 {
match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
Ok(n) => {
let system_time_ns = n.as_nanos();
// milliseconds to nanoseconds
let tick_count_ns = GetTickCount64() as u128 * 1_000_000;
// nanoseconds to seconds
let boot_time_sec = system_time_ns.saturating_sub(tick_count_ns) / 1_000_000_000;
boot_time_sec.try_into().unwrap_or(u64::MAX)
}
Err(_e) => {
sysinfo_debug!("Failed to compute boot time: {:?}", _e);
0
}
}
}
pub(crate) struct SystemInner {
process_list: HashMap<Pid, Process>,
mem_total: u64,
mem_available: u64,
swap_total: u64,
swap_used: u64,
cpus: CpusWrapper,
query: Option<Query>,
}
impl SystemInner {
pub(crate) fn new() -> Self {
Self {
process_list: HashMap::with_capacity(500),
mem_total: 0,
mem_available: 0,
swap_total: 0,
swap_used: 0,
cpus: CpusWrapper::new(),
query: None,
}
}
fn initialize_cpu_counters(&mut self, refresh_kind: CpuRefreshKind) {
if let Some(ref mut query) = self.query {
add_english_counter(
r"\Processor(_Total)\% Idle Time".to_string(),
query,
&mut self.cpus.global.key_used,
"tot_0".to_owned(),
);
for (pos, proc_) in self.cpus.iter_mut(refresh_kind).enumerate() {
add_english_counter(
format!(r"\Processor({pos})\% Idle Time"),
query,
get_key_used(proc_),
format!("{pos}_0"),
);
}
}
}
pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) {
if self.query.is_none() {
self.query = Query::new(false);
self.initialize_cpu_counters(refresh_kind);
} else if self.cpus.global.key_used.is_none() {
self.query = Query::new(true);
self.initialize_cpu_counters(refresh_kind);
}
if let Some(ref mut query) = self.query {
query.refresh();
let mut total_idle_time = None;
if let Some(ref key_used) = self.cpus.global.key_used {
total_idle_time = Some(
query
.get(&key_used.unique_id)
.expect("global_key_idle disappeared"),
);
}
if let Some(total_idle_time) = total_idle_time {
self.cpus.global.set_cpu_usage(100.0 - total_idle_time);
}
for cpu in self.cpus.iter_mut(refresh_kind) {
let mut idle_time = None;
if let Some(ref key_used) = *get_key_used(cpu) {
idle_time = Some(
query
.get(&key_used.unique_id)
.expect("key_used disappeared"),
);
}
if let Some(idle_time) = idle_time {
cpu.inner.set_cpu_usage(100.0 - idle_time);
}
}
if refresh_kind.frequency() {
self.cpus.get_frequencies();
}
}
}
pub(crate) fn refresh_cpu_list(&mut self, refresh_kind: CpuRefreshKind) {
self.cpus = CpusWrapper::new();
self.refresh_cpu_specifics(refresh_kind);
}
pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) {
unsafe {
if refresh_kind.ram() {
let mut mem_info: MEMORYSTATUSEX = zeroed();
mem_info.dwLength = size_of::<MEMORYSTATUSEX>() as _;
let _err = GlobalMemoryStatusEx(&mut mem_info);
self.mem_total = mem_info.ullTotalPhys as _;
self.mem_available = mem_info.ullAvailPhys as _;
}
if refresh_kind.swap() {
let mut perf_info: PERFORMANCE_INFORMATION = zeroed();
if K32GetPerformanceInfo(&mut perf_info, size_of::<PERFORMANCE_INFORMATION>() as _)
.as_bool()
{
let page_size = perf_info.PageSize as u64;
let physical_total = perf_info.PhysicalTotal as u64;
let commit_limit = perf_info.CommitLimit as u64;
let commit_total = perf_info.CommitTotal as u64;
self.swap_total =
page_size.saturating_mul(commit_limit.saturating_sub(physical_total));
self.swap_used =
page_size.saturating_mul(commit_total.saturating_sub(physical_total));
}
}
}
}
pub(crate) fn cgroup_limits(&self) -> Option<crate::CGroupLimits> {
None
}
#[allow(clippy::cast_ptr_alignment)]
pub(crate) fn refresh_processes_specifics(
&mut self,
processes_to_update: ProcessesToUpdate<'_>,
refresh_kind: ProcessRefreshKind,
) -> usize {
#[inline(always)]
fn real_filter(e: Pid, filter: &[Pid]) -> bool {
filter.contains(&e)
}
#[inline(always)]
fn empty_filter(_e: Pid, _filter: &[Pid]) -> bool {
true
}
#[allow(clippy::type_complexity)]
let (filter_array, filter_callback): (
&[Pid],
&(dyn Fn(Pid, &[Pid]) -> bool + Sync + Send),
) = match processes_to_update {
ProcessesToUpdate::All => (&[], &empty_filter),
ProcessesToUpdate::Some(pids) => {
if pids.is_empty() {
return 0;
}
(pids, &real_filter)
}
};
let now = get_now();
let nb_cpus = if refresh_kind.cpu() {
self.cpus.len() as u64
} else {
0
};
// Use the amazing and cool CreateToolhelp32Snapshot function.
// Take a snapshot of all running processes. Match the result to an error
let snapshot = match unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) } {
Ok(handle) => handle,
Err(_err) => {
sysinfo_debug!(
"Error capturing process snapshot: CreateToolhelp32Snapshot returned {}",
_err
);
return 0;
}
};
// https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/ns-tlhelp32-processentry32w
// Microsoft documentation states that for PROCESSENTRY32W, before calling Process32FirstW,
// the 'dwSize' field MUST be set to the size of the PROCESSENTRY32W. Otherwise, Process32FirstW fails.
let mut process_entry = PROCESSENTRY32W {
dwSize: size_of::<PROCESSENTRY32W>() as u32,
..Default::default()
};
let mut num_procs = 0; // keep track of the number of updated processes
let process_list = &mut self.process_list;
// process the first process
unsafe {
if let Err(_error) = Process32FirstW(snapshot, &mut process_entry) {
sysinfo_debug!("Process32FirstW has failed: {_error:?}");
return 0;
}
}
// Iterate over processes in the snapshot.
// Use Process32NextW to process the next PROCESSENTRY32W in the snapshot
loop {
let proc_id = Pid::from_u32(process_entry.th32ProcessID);
if filter_callback(proc_id, filter_array) {
// exists already
if let Some(p) = process_list.get_mut(&proc_id) {
// Update with the most recent information
let p = &mut p.inner;
p.update(refresh_kind, nb_cpus, now, false);
// Update parent process
let parent = if process_entry.th32ParentProcessID == 0 {
None
} else {
Some(Pid::from_u32(process_entry.th32ParentProcessID))
};
p.parent = parent;
} else {
// Make a new 'ProcessInner' using the Windows PROCESSENTRY32W struct.
let mut p = ProcessInner::from_process_entry(&process_entry, now);
p.update(refresh_kind, nb_cpus, now, false);
process_list.insert(proc_id, Process { inner: p });
}
num_procs += 1;
}
// nothing else to process
if unsafe { Process32NextW(snapshot, &mut process_entry).is_err() } {
break;
}
}
num_procs
}
pub(crate) fn processes(&self) -> &HashMap<Pid, Process> {
&self.process_list
}
pub(crate) fn processes_mut(&mut self) -> &mut HashMap<Pid, Process> {
&mut self.process_list
}
pub(crate) fn process(&self, pid: Pid) -> Option<&Process> {
self.process_list.get(&pid)
}
pub(crate) fn global_cpu_usage(&self) -> f32 {
self.cpus.global_cpu_usage()
}
pub(crate) fn cpus(&self) -> &[Cpu] {
self.cpus.cpus()
}
pub(crate) fn total_memory(&self) -> u64 {
self.mem_total
}
pub(crate) fn free_memory(&self) -> u64 {
// MEMORYSTATUSEX doesn't report free memory
self.mem_available
}
pub(crate) fn available_memory(&self) -> u64 {
self.mem_available
}
pub(crate) fn used_memory(&self) -> u64 {
self.mem_total - self.mem_available
}
pub(crate) fn total_swap(&self) -> u64 {
self.swap_total
}
pub(crate) fn free_swap(&self) -> u64 {
self.swap_total - self.swap_used
}
pub(crate) fn used_swap(&self) -> u64 {
self.swap_used
}
pub(crate) fn uptime() -> u64 {
unsafe { GetTickCount64() / 1_000 }
}
pub(crate) fn boot_time() -> u64 {
unsafe { boot_time() }
}
pub(crate) fn load_average() -> LoadAvg {
get_load_average()
}
pub(crate) fn name() -> Option<String> {
Some("Windows".to_owned())
}
pub(crate) fn long_os_version() -> Option<String> {
if Self::is_windows_eleven() {
return get_reg_string_value(
HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\Windows NT\CurrentVersion",
"ProductName",
)
.map(|product_name| product_name.replace("Windows 10 ", "Windows 11 "));
}
get_reg_string_value(
HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\Windows NT\CurrentVersion",
"ProductName",
)
}
pub(crate) fn host_name() -> Option<String> {
get_dns_hostname()
}
pub(crate) fn kernel_version() -> Option<String> {
get_reg_string_value(
HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\Windows NT\CurrentVersion",
"CurrentBuildNumber",
)
}
pub(crate) fn os_version() -> Option<String> {
let build_number = get_reg_string_value(
HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\Windows NT\CurrentVersion",
"CurrentBuildNumber",
)
.unwrap_or_default();
let major = if Self::is_windows_eleven() {
11u32
} else {
u32::from_le_bytes(
get_reg_value_u32(
HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\Windows NT\CurrentVersion",
"CurrentMajorVersionNumber",
)
.unwrap_or_default(),
)
};
Some(format!("{major} ({build_number})"))
}
pub(crate) fn distribution_id() -> String {
std::env::consts::OS.to_owned()
}
pub(crate) fn distribution_id_like() -> Vec<String> {
Vec::new()
}
pub(crate) fn kernel_name() -> Option<&'static str> {
Some("Windows")
}
pub(crate) fn cpu_arch() -> Option<String> {
unsafe {
// https://docs.microsoft.com/fr-fr/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info
let mut info = SYSTEM_INFO::default();
GetSystemInfo(&mut info);
match info.Anonymous.Anonymous.wProcessorArchitecture {
SystemInformation::PROCESSOR_ARCHITECTURE_ALPHA => Some("alpha".to_string()),
SystemInformation::PROCESSOR_ARCHITECTURE_ALPHA64 => Some("alpha64".to_string()),
SystemInformation::PROCESSOR_ARCHITECTURE_AMD64 => Some("x86_64".to_string()),
SystemInformation::PROCESSOR_ARCHITECTURE_ARM => Some("arm".to_string()),
SystemInformation::PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64 => Some("arm".to_string()),
SystemInformation::PROCESSOR_ARCHITECTURE_ARM64 => Some("arm64".to_string()),
SystemInformation::PROCESSOR_ARCHITECTURE_IA32_ON_ARM64
| SystemInformation::PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 => {
Some("ia32".to_string())
}
SystemInformation::PROCESSOR_ARCHITECTURE_IA64 => Some("ia64".to_string()),
SystemInformation::PROCESSOR_ARCHITECTURE_INTEL => Some("x86".to_string()),
SystemInformation::PROCESSOR_ARCHITECTURE_MIPS => Some("mips".to_string()),
SystemInformation::PROCESSOR_ARCHITECTURE_PPC => Some("powerpc".to_string()),
_ => None,
}
}
}
pub(crate) fn physical_core_count() -> Option<usize> {
get_physical_core_count()
}
}
pub(crate) fn is_proc_running(handle: HANDLE) -> bool {
let mut exit_code = 0;
unsafe { GetExitCodeProcess(handle, &mut exit_code) }.is_ok()
&& exit_code == STILL_ACTIVE.0 as u32
}
fn get_dns_hostname() -> Option<String> {
let mut buffer_size = 0;
// Running this first to get the buffer size since the DNS name can be longer than MAX_COMPUTERNAME_LENGTH
// setting the `lpBuffer` to null will return the buffer size
// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw
unsafe {
let _err = GetComputerNameExW(
ComputerNamePhysicalDnsHostname,
PWSTR::null(),
&mut buffer_size,
);
// Setting the buffer with the new length
let mut buffer = vec![0_u16; buffer_size as usize];
// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ne-sysinfoapi-computer_name_format
if GetComputerNameExW(
ComputerNamePhysicalDnsHostname,
PWSTR::from_raw(buffer.as_mut_ptr()),
&mut buffer_size,
)
.is_ok()
{
if let Some(pos) = buffer.iter().position(|c| *c == 0) {
buffer.resize(pos, 0);
}
return String::from_utf16(&buffer).ok();
}
}
sysinfo_debug!("Failed to get computer hostname");
None
}
fn add_english_counter(
s: String,
query: &mut super::cpu::Query,
keys: &mut Option<KeyHandler>,
counter_name: String,
) {
let mut full = s.encode_utf16().collect::<Vec<_>>();
full.push(0);
if query.add_english_counter(&counter_name, full) {
*keys = Some(KeyHandler::new(counter_name));
}
}
fn get_now() -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|n| n.as_secs())
.unwrap_or(0)
}
fn utf16_str<S: AsRef<OsStr> + ?Sized>(text: &S) -> Vec<u16> {
OsStr::new(text)
.encode_wide()
.chain(Some(0))
.collect::<Vec<_>>()
}
struct RegKey(HKEY);
impl RegKey {
unsafe fn open(hkey: HKEY, path: &[u16]) -> Option<Self> {
let mut new_hkey = Default::default();
if RegOpenKeyExW(
hkey,
PCWSTR::from_raw(path.as_ptr()),
0,
KEY_READ,
&mut new_hkey,
)
.is_err()
{
return None;
}
Some(Self(new_hkey))
}
unsafe fn get_value(
&self,
field_name: &[u16],
buf: &mut [u8],
buf_len: &mut u32,
) -> windows::core::Result<()> {
let mut buf_type = REG_NONE;
RegQueryValueExW(
self.0,
PCWSTR::from_raw(field_name.as_ptr()),
None,
Some(&mut buf_type),
Some(buf.as_mut_ptr()),
Some(buf_len),
)
.ok()
}
}
impl Drop for RegKey {
fn drop(&mut self) {
let _err = unsafe { RegCloseKey(self.0) };
}
}
pub(crate) fn get_reg_string_value(hkey: HKEY, path: &str, field_name: &str) -> Option<String> {
let c_path = utf16_str(path);
let c_field_name = utf16_str(field_name);
unsafe {
let new_key = RegKey::open(hkey, &c_path)?;
let mut buf_len: u32 = 2048;
let mut buf: Vec<u8> = Vec::with_capacity(buf_len as usize);
loop {
match new_key.get_value(&c_field_name, &mut buf, &mut buf_len) {
Ok(()) => break,
Err(err) if err.code() == Foundation::ERROR_MORE_DATA.to_hresult() => {
// Needs to be updated for `Vec::reserve` to actually add additional capacity.
buf.set_len(buf.capacity());
buf.reserve(buf_len as _);
}
_ => return None,
}
}
buf.set_len(buf_len as _);
let words = std::slice::from_raw_parts(buf.as_ptr() as *const u16, buf.len() / 2);
let mut s = String::from_utf16_lossy(words);
while s.ends_with('\u{0}') {
s.pop();
}
Some(s)
}
}
pub(crate) fn get_reg_value_u32(hkey: HKEY, path: &str, field_name: &str) -> Option<[u8; 4]> {
let c_path = utf16_str(path);
let c_field_name = utf16_str(field_name);
unsafe {
let new_key = RegKey::open(hkey, &c_path)?;
let mut buf_len: u32 = 4;
let mut buf = [0u8; 4];
new_key
.get_value(&c_field_name, &mut buf, &mut buf_len)
.map(|_| buf)
.ok()
}
}

276
vendor/sysinfo/src/windows/users.rs vendored Normal file
View File

@@ -0,0 +1,276 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::sys::utils::to_utf8_str;
use crate::{windows::sid::Sid, Gid, Group, GroupInner, Uid, User};
use std::ptr::null_mut;
use windows::core::PCWSTR;
use windows::Win32::Foundation::{ERROR_MORE_DATA, LUID};
use windows::Win32::NetworkManagement::NetManagement::{
NERR_Success, NetApiBufferFree, NetUserEnum, NetUserGetInfo, NetUserGetLocalGroups,
FILTER_NORMAL_ACCOUNT, LG_INCLUDE_INDIRECT, LOCALGROUP_USERS_INFO_0, MAX_PREFERRED_LENGTH,
USER_INFO_0, USER_INFO_23,
};
use windows::Win32::Security::Authentication::Identity::{
LsaEnumerateLogonSessions, LsaFreeReturnBuffer, LsaGetLogonSessionData,
SECURITY_LOGON_SESSION_DATA, SECURITY_LOGON_TYPE,
};
pub(crate) struct UserInner {
pub(crate) uid: Uid,
pub(crate) gid: Gid,
pub(crate) name: String,
c_user_name: Option<Vec<u16>>,
is_local: bool,
}
impl UserInner {
fn new(uid: Uid, name: String, c_name: PCWSTR, is_local: bool) -> Self {
let c_user_name = if c_name.is_null() {
None
} else {
Some(unsafe { c_name.as_wide() }.into())
};
Self {
uid,
gid: Gid(0),
name,
c_user_name,
is_local,
}
}
pub(crate) fn id(&self) -> &Uid {
&self.uid
}
pub(crate) fn group_id(&self) -> Gid {
self.gid
}
pub(crate) fn name(&self) -> &str {
&self.name
}
pub(crate) fn groups(&self) -> Vec<Group> {
if let (Some(c_user_name), true) = (&self.c_user_name, self.is_local) {
// Convert the wide string to a PCWSTR, and ensure it has a null terminator.
// Since the Vec is created here, we can ensure it will not be dropped prematurely.
let username = {
let mut null_terminated = c_user_name.to_vec();
if null_terminated.last().is_some_and(|v| *v != 0) {
null_terminated.push(0);
}
null_terminated
};
unsafe { get_groups_for_user(PCWSTR::from_raw(username.as_ptr())) }
} else {
Vec::new()
}
}
}
struct NetApiBuffer<T>(*mut T);
impl<T> Drop for NetApiBuffer<T> {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { NetApiBufferFree(Some(self.0.cast())) };
}
}
}
impl<T> Default for NetApiBuffer<T> {
fn default() -> Self {
Self(null_mut())
}
}
impl<T> NetApiBuffer<T> {
pub fn inner_mut(&mut self) -> &mut *mut T {
assert!(self.0.is_null());
&mut self.0
}
pub unsafe fn inner_mut_as_bytes(&mut self) -> &mut *mut u8 {
// https://doc.rust-lang.org/std/mem/fn.transmute.html
// Turning an &mut T into an &mut U:
&mut *(self.inner_mut() as *mut *mut T as *mut *mut u8)
}
}
struct LsaBuffer<T>(*mut T);
impl<T> Drop for LsaBuffer<T> {
fn drop(&mut self) {
if !self.0.is_null() {
let _r = unsafe { LsaFreeReturnBuffer(self.0 as *mut _) };
}
}
}
impl<T> Default for LsaBuffer<T> {
fn default() -> Self {
Self(null_mut())
}
}
impl<T> LsaBuffer<T> {
pub fn inner_mut(&mut self) -> &mut *mut T {
assert!(self.0.is_null());
&mut self.0
}
}
/// Get the groups for a user.
///
/// # Safety
/// The caller must ensure that the `username` is a valid wide Unicode string with a null terminator.
unsafe fn get_groups_for_user(username: PCWSTR) -> Vec<Group> {
let mut buf: NetApiBuffer<LOCALGROUP_USERS_INFO_0> = Default::default();
let mut nb_entries = 0;
let mut total_entries = 0;
let mut groups: Vec<Group>;
let status = NetUserGetLocalGroups(
PCWSTR::null(),
username,
0,
LG_INCLUDE_INDIRECT,
buf.inner_mut_as_bytes(),
MAX_PREFERRED_LENGTH,
&mut nb_entries,
&mut total_entries,
);
if status == NERR_Success {
groups = Vec::with_capacity(nb_entries as _);
if !buf.0.is_null() {
let entries = std::slice::from_raw_parts(buf.0, nb_entries as _);
groups.extend(entries.iter().map(|entry| Group {
inner: GroupInner::new(Gid(0), to_utf8_str(entry.lgrui0_name)),
}));
}
} else {
groups = Vec::new();
sysinfo_debug!("NetUserGetLocalGroups failed with ret code {}", status);
}
groups
}
pub(crate) fn get_users(users: &mut Vec<User>) {
users.clear();
let mut resume_handle: u32 = 0;
unsafe {
loop {
let mut buffer: NetApiBuffer<USER_INFO_0> = Default::default();
let mut nb_read = 0;
let mut total = 0;
let status = NetUserEnum(
PCWSTR::null(),
0,
FILTER_NORMAL_ACCOUNT,
buffer.inner_mut_as_bytes(),
MAX_PREFERRED_LENGTH,
&mut nb_read,
&mut total,
Some(&mut resume_handle),
);
if status == NERR_Success || status == ERROR_MORE_DATA.0 {
let entries = std::slice::from_raw_parts(buffer.0, nb_read as _);
for entry in entries {
if entry.usri0_name.is_null() {
continue;
}
let mut user: NetApiBuffer<USER_INFO_23> = Default::default();
if NetUserGetInfo(
PCWSTR::null(),
PCWSTR::from_raw(entry.usri0_name.as_ptr()),
23,
user.inner_mut_as_bytes(),
) == NERR_Success
{
if let Some(sid) = Sid::from_psid((*user.0).usri23_user_sid) {
// Get the account name from the SID (because it's usually
// a better name), but fall back to the name we were given
// if this fails.
let name = sid
.account_name()
.unwrap_or_else(|| to_utf8_str(entry.usri0_name));
users.push(User {
inner: UserInner::new(
Uid(sid),
name,
PCWSTR(entry.usri0_name.0 as *const _),
true,
),
});
}
}
}
} else {
sysinfo_debug!(
"NetUserEnum error: {}",
if status == windows::Win32::Foundation::ERROR_ACCESS_DENIED.0 {
"access denied"
} else if status == windows::Win32::Foundation::ERROR_INVALID_LEVEL.0 {
"invalid level"
} else {
"unknown error"
}
);
}
if status != ERROR_MORE_DATA.0 {
break;
}
}
// First part done. Second part now!
let mut nb_sessions = 0;
let mut uids: LsaBuffer<LUID> = Default::default();
if LsaEnumerateLogonSessions(&mut nb_sessions, uids.inner_mut()).is_err() {
sysinfo_debug!("LsaEnumerateLogonSessions failed");
} else {
let entries = std::slice::from_raw_parts_mut(uids.0, nb_sessions as _);
for entry in entries {
let mut data: LsaBuffer<SECURITY_LOGON_SESSION_DATA> = Default::default();
if LsaGetLogonSessionData(entry, data.inner_mut()).is_ok() && !data.0.is_null() {
let data = *data.0;
if data.LogonType == SECURITY_LOGON_TYPE::Network.0 as u32 {
continue;
}
let sid = match Sid::from_psid(data.Sid) {
Some(sid) => sid,
None => continue,
};
if users.iter().any(|u| u.inner.uid.0 == sid) {
continue;
}
// Get the account name from the SID (because it's usually
// a better name), but fall back to the name we were given
// if this fails.
let name = sid.account_name().unwrap_or_else(|| {
String::from_utf16(std::slice::from_raw_parts(
data.UserName.Buffer.as_ptr(),
data.UserName.Length as usize / std::mem::size_of::<u16>(),
))
.unwrap_or_else(|_err| {
sysinfo_debug!("Failed to convert from UTF-16 string: {}", _err);
String::new()
})
});
users.push(User {
inner: UserInner::new(Uid(sid), name, PCWSTR::null(), false),
});
}
}
}
}
}

79
vendor/sysinfo/src/windows/utils.rs vendored Normal file
View File

@@ -0,0 +1,79 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[cfg(feature = "disk")]
use windows::Win32::Storage::FileSystem::{
CreateFileW, FILE_ACCESS_RIGHTS, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
};
#[cfg(any(feature = "user", feature = "system"))]
pub(crate) unsafe fn to_utf8_str(p: windows::core::PWSTR) -> String {
if p.is_null() {
return String::new();
}
p.to_string().unwrap_or_else(|_e| {
sysinfo_debug!("Failed to convert to UTF-16 string: {}", _e);
String::new()
})
}
cfg_if! {
if #[cfg(any(feature = "disk", feature = "system"))] {
use windows::Win32::Foundation::{CloseHandle, HANDLE};
use std::ops::Deref;
pub(crate) struct HandleWrapper(pub(crate) HANDLE);
impl HandleWrapper {
#[cfg(feature = "system")]
pub(crate) fn new(handle: HANDLE) -> Option<Self> {
if handle.is_invalid() {
None
} else {
Some(Self(handle))
}
}
#[cfg(feature = "disk")]
pub(crate) unsafe fn new_from_file(
drive_name: &[u16],
open_rights: FILE_ACCESS_RIGHTS,
) -> Option<Self> {
let lpfilename = windows::core::PCWSTR::from_raw(drive_name.as_ptr());
let handle = CreateFileW(
lpfilename,
open_rights.0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
None,
OPEN_EXISTING,
Default::default(),
HANDLE::default(),
)
.ok()?;
if handle.is_invalid() {
sysinfo_debug!(
"Expected handle to {:?} to be valid",
String::from_utf16_lossy(drive_name)
);
None
} else {
Some(Self(handle))
}
}
}
impl Deref for HandleWrapper {
type Target = HANDLE;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Drop for HandleWrapper {
fn drop(&mut self) {
let _err = unsafe { CloseHandle(self.0) };
}
}
}
}