sysinfolinux 0.1.1

Library to get system information such as processes, CPUs, disks, components and networks on Linux
// 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 = "gpu",
        feature = "network",
        feature = "user"
    ),
    doc = include_str!("../README.md")
)]
#![cfg_attr(
    not(all(
        feature = "system",
        feature = "disk",
        feature = "component",
        feature = "gpu",
        feature = "network",
        feature = "user"
    )),
    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;

mod linux;
use crate::linux as sys;

#[cfg(feature = "network")]
mod network;
#[cfg(feature = "network")]
use crate::linux::network_helper;

#[cfg(test)]
pub(crate) const MIN_USERS: usize = 1;

#[cfg(feature = "component")]
pub use crate::common::component::{Component, Components};
#[cfg(feature = "disk")]
pub use crate::common::disk::{Disk, DiskKind, DiskRefreshKind, Disks};
#[cfg(feature = "gpu")]
pub use crate::common::gpu::{Gpu, Gpus, PCI};
#[cfg(feature = "network")]
pub use crate::common::network::{
    InterfaceOperationalState, IpNetwork, IpNetworkFromStrError, MacAddr, MacAddrFromStrError,
    NetworkData, Networks,
};
#[cfg(feature = "system")]
pub use crate::common::system::{
    CGroupLimits, Cpu, CpuRefreshKind, KillError, LoadAvg, MemoryRefreshKind, Motherboard, Pid,
    Process, ProcessRefreshKind, ProcessStatus, ProcessesToUpdate, Product, RefreshKind, Signal,
    System, ThreadKind, UpdateKind, get_current_pid,
};
#[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, MotherboardInner, ProcessInner, ProductInner, SystemInner};
#[cfg(feature = "disk")]
pub(crate) use crate::sys::{DiskInner, DisksInner};
#[cfg(feature = "gpu")]
pub(crate) use crate::sys::{GpuInner, GpusInner};
#[cfg(feature = "network")]
pub(crate) use crate::sys::{NetworkDataInner, NetworksInner};

pub use crate::errors::Error;
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;
mod errors;
#[cfg(feature = "serde")]
mod serde;
pub(crate) mod utils;

/// This function is only used on Linux targets, when the `system` feature is enabled.
///
/// 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...");
/// }
/// if let Ok(s) = System::new_all() {
///     // ...
/// }
/// ```
pub fn set_open_files_limit(mut _new_limit: usize) -> bool {
    #[cfg(feature = "system")]
    {
        use crate::sys::system::remaining_files;
        use std::sync::atomic::Ordering;

        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.
        if remaining_files().try_update(Ordering::SeqCst, Ordering::SeqCst, |remaining| {
            let _new_limit = _new_limit as isize;
            let diff = (max as isize).saturating_sub(remaining);
            Some(_new_limit.saturating_sub(diff))
        }).is_err() {
            sysinfo_debug!("failed to update open files limit");
        }

        true
    }
    
    #[cfg(not(feature = "system"))]
    {
        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,
        KillError,
        LoadAvg,
        MemoryRefreshKind,
        Motherboard,
        Pid,
        Process,
        ProcessesToUpdate,
        ProcessRefreshKind,
        ProcessStatus,
        Product,
        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::*;

    // 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 = check_unsupported!(Users::new());
        assert!(users.list().is_empty());
        users.refresh();
        let user_list = users.list();
        assert!(user_list.len() >= MIN_USERS);

        if IS_SUPPORTED_SYSTEM {
            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.
                if let Ok(s) =
                    System::new_with_specifics(RefreshKind::nothing().with_processes(
                        ProcessRefreshKind::nothing().with_user(UpdateKind::Always),
                    ))
                {
                    assert!(
                        s.processes()
                            .values()
                            .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() {
        let s = check_unsupported!(System::new_with_specifics(
            RefreshKind::nothing()
                .with_processes(ProcessRefreshKind::nothing().with_user(UpdateKind::Always)),
        ));
        let users = check_unsupported!(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
        // Note: On Linux, some user IDs don't have an associated user.
        // If `getent` doesn't find them, we skip validation for those UIDs.
        for process in s.processes().values() {
            if let Some(uid) = process.user_id() {
                // Allow missing UIDs on Linux as they may be system-internal
                let _ = users.get_user_by_id(uid);
            }
        }
    }

    #[test]
    fn ensure_is_supported_is_set_correctly() {
        if MIN_USERS > 0 {
            assert!(IS_SUPPORTED_SYSTEM);
        } else {
            assert!(!IS_SUPPORTED_SYSTEM);
        }
    }

    // If it doesn't compile, it means types don't implement expected traits.
    #[cfg(any(
        feature = "system",
        feature = "disk",
        feature = "component",
        feature = "user",
        feature = "network"
    ))]
    #[test]
    fn test_send_and_sync() {
        #[allow(dead_code)]
        trait HasSendAndSync: Send + Sync {}

        // Structs
        impl HasSendAndSync for CGroupLimits {}
        impl HasSendAndSync for Component {}
        impl HasSendAndSync for Components {}
        impl HasSendAndSync for Cpu {}
        impl HasSendAndSync for CpuRefreshKind {}
        impl HasSendAndSync for Disk {}
        impl HasSendAndSync for Disks {}
        impl HasSendAndSync for DiskRefreshKind {}
        impl HasSendAndSync for DiskUsage {}
        impl HasSendAndSync for Gid {}
        impl HasSendAndSync for Group {}
        impl HasSendAndSync for Groups {}
        impl HasSendAndSync for IpNetwork {}
        impl HasSendAndSync for LoadAvg {}
        impl HasSendAndSync for MacAddr {}
        impl HasSendAndSync for MemoryRefreshKind {}
        impl HasSendAndSync for NetworkData {}
        impl HasSendAndSync for Networks {}
        impl HasSendAndSync for Pid {}
        impl HasSendAndSync for Process {}
        impl HasSendAndSync for ProcessRefreshKind {}
        impl HasSendAndSync for Product {}
        impl HasSendAndSync for RefreshKind {}
        impl HasSendAndSync for System {}
        impl HasSendAndSync for Uid {}
        impl HasSendAndSync for User {}
        impl HasSendAndSync for Users {}

        // Enums
        impl HasSendAndSync for DiskKind {}
        impl HasSendAndSync for IpNetworkFromStrError {}
        impl HasSendAndSync for KillError {}
        impl HasSendAndSync for MacAddrFromStrError {}
        impl HasSendAndSync for ProcessStatus {}
        impl HasSendAndSync for ProcessesToUpdate<'_> {}
        impl HasSendAndSync for Signal {}
        impl HasSendAndSync for ThreadKind {}
        impl HasSendAndSync for UpdateKind {}
    }
}