sysinfo/
lib.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3#![cfg_attr(
4    all(feature = "system", feature = "disk", feature = "component", feature = "system"),
5    doc = include_str!("../README.md")
6)]
7#![cfg_attr(
8    not(all(
9        feature = "system",
10        feature = "disk",
11        feature = "component",
12        feature = "system"
13    )),
14    doc = "For crate-level documentation, all features need to be enabled."
15)]
16#![cfg_attr(feature = "serde", doc = include_str!("../md_doc/serde.md"))]
17#![allow(unknown_lints)]
18#![deny(missing_docs)]
19#![deny(rustdoc::broken_intra_doc_links)]
20#![allow(clippy::upper_case_acronyms)]
21#![allow(clippy::non_send_fields_in_send_ty)]
22#![allow(renamed_and_removed_lints)]
23#![allow(clippy::assertions_on_constants)]
24
25#[macro_use]
26mod macros;
27
28cfg_if! {
29    if #[cfg(feature = "unknown-ci")] {
30        // This is used in CI to check that the build for unknown targets is compiling fine.
31        mod unknown;
32        use crate::unknown as sys;
33
34        #[cfg(test)]
35        pub(crate) const MIN_USERS: usize = 0;
36    } else if #[cfg(any(
37        target_os = "macos", target_os = "ios",
38        target_os = "linux", target_os = "android",
39        target_os = "freebsd", target_os = "netbsd"))]
40    {
41        mod unix;
42        use crate::unix::sys as sys;
43
44        #[cfg(feature = "network")]
45        mod network;
46        #[cfg(feature = "network")]
47        use crate::unix::network_helper;
48
49        #[cfg(test)]
50        pub(crate) const MIN_USERS: usize = 1;
51    } else if #[cfg(windows)] {
52        mod windows;
53        use crate::windows as sys;
54
55        #[cfg(feature = "network")]
56        mod network;
57        #[cfg(feature = "network")]
58        use crate::windows::network_helper;
59
60        #[cfg(test)]
61        pub(crate) const MIN_USERS: usize = 1;
62    } else {
63        mod unknown;
64        use crate::unknown as sys;
65
66        #[cfg(test)]
67        pub(crate) const MIN_USERS: usize = 0;
68    }
69}
70
71#[cfg(feature = "component")]
72pub use crate::common::component::{Component, Components};
73#[cfg(feature = "disk")]
74pub use crate::common::disk::{Disk, DiskKind, DiskRefreshKind, Disks};
75#[cfg(feature = "network")]
76pub use crate::common::network::{
77    IpNetwork, IpNetworkFromStrError, MacAddr, MacAddrFromStrError, NetworkData, Networks,
78};
79#[cfg(feature = "system")]
80pub use crate::common::system::{
81    CGroupLimits, Cpu, CpuRefreshKind, KillError, LoadAvg, MemoryRefreshKind, Motherboard, Pid,
82    Process, ProcessRefreshKind, ProcessStatus, ProcessesToUpdate, Product, RefreshKind, Signal,
83    System, ThreadKind, UpdateKind, get_current_pid,
84};
85#[cfg(feature = "user")]
86pub use crate::common::user::{Group, Groups, User, Users};
87#[cfg(any(feature = "user", feature = "system"))]
88pub use crate::common::{Gid, Uid};
89#[cfg(feature = "system")]
90pub use crate::sys::{MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS};
91
92#[cfg(any(feature = "system", feature = "disk"))]
93pub use crate::common::DiskUsage;
94
95#[cfg(feature = "user")]
96pub(crate) use crate::common::user::GroupInner;
97#[cfg(feature = "user")]
98pub(crate) use crate::sys::UserInner;
99#[cfg(feature = "component")]
100pub(crate) use crate::sys::{ComponentInner, ComponentsInner};
101#[cfg(feature = "system")]
102pub(crate) use crate::sys::{CpuInner, MotherboardInner, ProcessInner, ProductInner, SystemInner};
103#[cfg(feature = "disk")]
104pub(crate) use crate::sys::{DiskInner, DisksInner};
105#[cfg(feature = "network")]
106pub(crate) use crate::sys::{NetworkDataInner, NetworksInner};
107
108pub use crate::sys::IS_SUPPORTED_SYSTEM;
109
110#[cfg(feature = "c-interface")]
111pub use crate::c_interface::*;
112
113#[cfg(feature = "c-interface")]
114mod c_interface;
115mod common;
116mod debug;
117#[cfg(feature = "serde")]
118mod serde;
119pub(crate) mod utils;
120
121// Make formattable by rustfmt.
122#[cfg(any())]
123mod network;
124#[cfg(any())]
125mod unix;
126#[cfg(any())]
127mod unknown;
128#[cfg(any())]
129mod windows;
130
131/// This function is only used on Linux targets, when the `system` feature is enabled. In other
132/// cases, it does nothing and returns `false`.
133///
134/// On Linux, to improve performance, we keep a `/proc` file open for each process we index with
135/// a maximum number of files open equivalent to half of the system limit.
136///
137/// The problem is that some users might need all the available file descriptors so we need to
138/// allow them to change this limit.
139///
140/// Note that if you set a limit bigger than the system limit, the system limit will be set.
141///
142/// Returns `true` if the new value has been set.
143///
144#[cfg_attr(feature = "system", doc = "```no_run")]
145#[cfg_attr(not(feature = "system"), doc = "```ignore")]
146/// use sysinfo::{System, set_open_files_limit};
147///
148/// // We call the function before any call to the processes update.
149/// if !set_open_files_limit(10) {
150///     // It'll always return false on non-linux targets.
151///     eprintln!("failed to update the open files limit...");
152/// }
153/// let s = System::new_all();
154/// ```
155pub fn set_open_files_limit(mut _new_limit: usize) -> bool {
156    cfg_if! {
157        if #[cfg(all(feature = "system", not(feature = "unknown-ci"), any(target_os = "linux", target_os = "android")))]
158        {
159            use crate::sys::system::remaining_files;
160            use std::sync::atomic::Ordering;
161
162            let max = sys::system::get_max_nb_fds();
163            if _new_limit > max {
164                _new_limit = max;
165            }
166
167            // If files are already open, to be sure that the number won't be bigger when those
168            // files are closed, we subtract the current number of opened files to the new
169            // limit.
170            if remaining_files().fetch_update(Ordering::SeqCst, Ordering::SeqCst, |remaining| {
171                let _new_limit = _new_limit as isize;
172                let diff = (max as isize).saturating_sub(remaining);
173                Some(_new_limit.saturating_sub(diff))
174            }).is_err() {
175                sysinfo_debug!("failed to update open files limit");
176            }
177
178            true
179        } else {
180            false
181        }
182    }
183}
184
185#[cfg(doctest)]
186mod doctest {
187    macro_rules! compile_fail_import {
188        ($mod_name:ident => $($imports:ident),+ $(,)?) => {
189            $(#[doc = concat!(r"```compile_fail
190use sysinfo::", stringify!($imports), r";
191```
192")])+
193            mod $mod_name {}
194        };
195    }
196
197    #[cfg(not(feature = "system"))]
198    compile_fail_import!(
199        no_system_feature =>
200        get_current_pid,
201        CGroupLimits,
202        Cpu,
203        CpuRefreshKind,
204        DiskUsage,
205        KillError,
206        LoadAvg,
207        MemoryRefreshKind,
208        Motherboard,
209        Pid,
210        Process,
211        ProcessesToUpdate,
212        ProcessRefreshKind,
213        ProcessStatus,
214        Product,
215        RefreshKind,
216        Signal,
217        System,
218        ThreadKind,
219        UpdateKind,
220    );
221
222    #[cfg(not(feature = "disk"))]
223    compile_fail_import!(
224        no_disk_feature =>
225        Disk,
226        Disks,
227        DiskKind,
228    );
229
230    #[cfg(not(feature = "component"))]
231    compile_fail_import!(
232        no_component_feature =>
233        Component,
234        Components,
235    );
236
237    #[cfg(not(feature = "network"))]
238    compile_fail_import!(
239        no_network_feature =>
240        IpNetwork,
241        MacAddr,
242        NetworkData,
243        Networks,
244    );
245
246    #[cfg(not(feature = "user"))]
247    compile_fail_import!(
248        no_user_feature =>
249        Group,
250        Groups,
251        User,
252        Users,
253    );
254}
255
256#[cfg(test)]
257mod test {
258    use crate::*;
259
260    #[cfg(feature = "unknown-ci")]
261    #[test]
262    fn check_unknown_ci_feature() {
263        assert!(!IS_SUPPORTED_SYSTEM);
264    }
265
266    // If this test doesn't compile, it means the current OS doesn't implement them correctly.
267    #[test]
268    fn check_macro_types() {
269        fn check_is_supported(_: bool) {}
270
271        check_is_supported(IS_SUPPORTED_SYSTEM);
272    }
273
274    // If this test doesn't compile, it means the current OS doesn't implement them correctly.
275    #[cfg(feature = "system")]
276    #[test]
277    fn check_macro_types2() {
278        fn check_supported_signals(_: &'static [Signal]) {}
279        fn check_minimum_cpu_update_interval(_: std::time::Duration) {}
280
281        check_supported_signals(SUPPORTED_SIGNALS);
282        check_minimum_cpu_update_interval(MINIMUM_CPU_UPDATE_INTERVAL);
283    }
284
285    #[cfg(feature = "user")]
286    #[test]
287    fn check_uid_gid() {
288        let mut users = Users::new();
289        assert!(users.list().is_empty());
290        users.refresh();
291        let user_list = users.list();
292        assert!(user_list.len() >= MIN_USERS);
293
294        if IS_SUPPORTED_SYSTEM {
295            #[cfg(not(target_os = "windows"))]
296            {
297                let user = user_list
298                    .iter()
299                    .find(|u| u.name() == "root")
300                    .expect("no root user");
301                assert_eq!(**user.id(), 0);
302                assert_eq!(*user.group_id(), 0);
303                if let Some(user) = users.iter().find(|u| *u.group_id() > 0) {
304                    assert!(**user.id() > 0);
305                    assert!(*user.group_id() > 0);
306                }
307                assert!(user_list.iter().filter(|u| **u.id() > 0).count() > 0);
308            }
309
310            #[cfg(feature = "system")]
311            {
312                // And now check that our `get_user_by_id` method works.
313                let s =
314                    System::new_with_specifics(RefreshKind::nothing().with_processes(
315                        ProcessRefreshKind::nothing().with_user(UpdateKind::Always),
316                    ));
317                assert!(
318                    s.processes()
319                        .iter()
320                        .filter_map(|(_, p)| p.user_id())
321                        .any(|uid| users.get_user_by_id(uid).is_some())
322                );
323            }
324        }
325    }
326
327    #[cfg(all(feature = "system", feature = "user"))]
328    #[test]
329    fn check_all_process_uids_resolvable() {
330        // On linux, some user IDs don't have an associated user (no idea why though).
331        // If `getent` doesn't find them, we can assume it's a dark secret from the linux land.
332        if IS_SUPPORTED_SYSTEM && cfg!(not(target_os = "linux")) {
333            let s = System::new_with_specifics(
334                RefreshKind::nothing()
335                    .with_processes(ProcessRefreshKind::nothing().with_user(UpdateKind::Always)),
336            );
337            let users = Users::new_with_refreshed_list();
338
339            // For every process where we can get a user ID, we should also be able
340            // to find that user ID in the global user list
341            for process in s.processes().values() {
342                if let Some(uid) = process.user_id() {
343                    assert!(users.get_user_by_id(uid).is_some(), "No UID {uid:?} found");
344                }
345            }
346        }
347    }
348
349    #[test]
350    fn ensure_is_supported_is_set_correctly() {
351        if MIN_USERS > 0 {
352            assert!(IS_SUPPORTED_SYSTEM);
353        } else {
354            assert!(!IS_SUPPORTED_SYSTEM);
355        }
356    }
357
358    // If it doesn't compile, it means types don't implement expected traits.
359    #[cfg(any(
360        feature = "system",
361        feature = "disk",
362        feature = "component",
363        feature = "user",
364        feature = "network"
365    ))]
366    #[test]
367    fn test_send_and_sync() {
368        #[allow(dead_code)]
369        trait HasSendAndSync: Send + Sync {}
370
371        // Structs
372        impl HasSendAndSync for CGroupLimits {}
373        impl HasSendAndSync for Component {}
374        impl HasSendAndSync for Components {}
375        impl HasSendAndSync for Cpu {}
376        impl HasSendAndSync for CpuRefreshKind {}
377        impl HasSendAndSync for Disk {}
378        impl HasSendAndSync for Disks {}
379        impl HasSendAndSync for DiskRefreshKind {}
380        impl HasSendAndSync for DiskUsage {}
381        impl HasSendAndSync for Gid {}
382        impl HasSendAndSync for Group {}
383        impl HasSendAndSync for Groups {}
384        impl HasSendAndSync for IpNetwork {}
385        impl HasSendAndSync for LoadAvg {}
386        impl HasSendAndSync for MacAddr {}
387        impl HasSendAndSync for MemoryRefreshKind {}
388        impl HasSendAndSync for NetworkData {}
389        impl HasSendAndSync for Networks {}
390        impl HasSendAndSync for Pid {}
391        impl HasSendAndSync for Process {}
392        impl HasSendAndSync for ProcessRefreshKind {}
393        impl HasSendAndSync for Product {}
394        impl HasSendAndSync for RefreshKind {}
395        impl HasSendAndSync for System {}
396        impl HasSendAndSync for Uid {}
397        impl HasSendAndSync for User {}
398        impl HasSendAndSync for Users {}
399
400        // Enums
401        impl HasSendAndSync for DiskKind {}
402        impl HasSendAndSync for IpNetworkFromStrError {}
403        impl HasSendAndSync for KillError {}
404        impl HasSendAndSync for MacAddrFromStrError {}
405        impl HasSendAndSync for ProcessStatus {}
406        impl HasSendAndSync for ProcessesToUpdate<'_> {}
407        impl HasSendAndSync for Signal {}
408        impl HasSendAndSync for ThreadKind {}
409        impl HasSendAndSync for UpdateKind {}
410    }
411}