Repository: GuillaumeGomez/sysinfo
Pull request #1480 (2025-03-08 22:34:06)

Add `Process::open_files` and `Process::open_files_limit`
Fixes https://github.com/GuillaumeGomez/sysinfo/issues/1202.

For now only supports linux.


Patch:
diff --git a/examples/simple.rs b/examples/simple.rs
index 2beaf7c..b94fb6a 100644
--- a/examples/simple.rs
+++ b/examples/simple.rs
@@ -143,7 +143,6 @@ fn print_help() {
         &mut io::stdout(),
         "pid                : Display this example's PID"
     );
-
     writeln!(&mut io::stdout(), "quit               : Exit the program");
 }
 
@@ -285,9 +284,19 @@ fn interpret_input(
                 writeln!(&mut io::stdout(), "example: show 1254");
             } else if let Ok(pid) = Pid::from_str(tmp[1]) {
                 match sys.process(pid) {
-                    Some(p) => writeln!(&mut io::stdout(), "{:?}", *p),
-                    None => writeln!(&mut io::stdout(), "pid \"{pid:?}\" not found"),
-                };
+                    Some(p) => {
+                        writeln!(&mut io::stdout(), "{:?}", *p);
+                        writeln!(
+                            &mut io::stdout(),
+                            "Files open/limit: {:?}/{:?}",
+                            p.open_files(),
+                            p.open_files_limit(),
+                        );
+                    }
+                    None => {
+                        writeln!(&mut io::stdout(), "pid \"{pid:?}\" not found");
+                    }
+                }
             } else {
                 let proc_name = tmp[1];
                 for proc_ in sys.processes_by_name(proc_name.as_ref()) {
@@ -439,14 +448,12 @@ fn interpret_input(
                  System kernel version:    {}\n\
                  System OS version:        {}\n\
                  System OS (long) version: {}\n\
-                 System host name:         {}\n\
-		 System kernel:            {}",
+                 System host name:         {}",
                 System::name().unwrap_or_else(|| "<unknown>".to_owned()),
                 System::kernel_version().unwrap_or_else(|| "<unknown>".to_owned()),
                 System::os_version().unwrap_or_else(|| "<unknown>".to_owned()),
                 System::long_os_version().unwrap_or_else(|| "<unknown>".to_owned()),
                 System::host_name().unwrap_or_else(|| "<unknown>".to_owned()),
-                System::kernel_long_version(),
             );
         }
         e => {
diff --git a/src/common/system.rs b/src/common/system.rs
index 74055d4..2e9fd66 100644
--- a/src/common/system.rs
+++ b/src/common/system.rs
@@ -792,6 +792,34 @@ impl System {
         SystemInner::distribution_id()
     }
 
+    /// Provides kernel version following this string format:
+    ///
+    /// "<code>[distribution_id] OS Build [kernel_version]</code>"
+    ///
+    /// If any of the information is not available, it will be replaced with "unknown".
+    ///
+    /// **Important**: this information is computed every time this function is called.
+    ///
+    /// ```no_run
+    /// use sysinfo::System;
+    ///
+    /// println!("Kernel long version: {}", System::kernel_long_version());
+    /// ```
+    ///
+    /// [distribution_id]: System::distribution_id
+    /// [kernel_version]: System::kernel_version
+    pub fn kernel_long_version() -> String {
+        let mut distribution_id = System::distribution_id();
+        if distribution_id.is_empty() {
+            distribution_id.push_str("Unknown");
+        }
+        let kernel_version = match System::kernel_version() {
+            None => "unknown".to_string(),
+            Some(s) => s,
+        };
+        distribution_id + " OS Build " + &kernel_version
+    }
+
     /// Returns the distribution ids of operating systems that are closely
     /// related to the local operating system in regards to packaging and
     /// programming interfaces, for example listing one or more OS identifiers
@@ -819,41 +847,6 @@ impl System {
         SystemInner::distribution_id_like()
     }
 
-    /// Provides kernel version following this string format:
-    ///
-    /// | Platform | Result |
-    /// |-|-|
-    /// | Windows | Windows OS Build 20348.2227 |
-    /// | Linux | Linux 6.12.13-200.fc41.x86_64 |
-    /// | Android | Android 612.13-200 |
-    /// | MacOS | Darwin 21.6.0 |
-    /// | FreeBSD | FreeBSD 199506 |
-    ///
-    /// If any of the information is not available, it will be replaced with "unknown".
-    ///
-    /// **Important**: this information is computed every time this function is called.
-    ///
-    /// ```no_run
-    /// use sysinfo::System;
-    ///
-    /// println!("Kernel long version: {}", System::kernel_long_version());
-    /// ```
-    ///
-    /// [distribution_id]: System::distribution_id
-    /// [kernel_version]: System::kernel_version
-    pub fn kernel_long_version() -> String {
-        let kernel_version = match System::kernel_version() {
-            None => "unknown".to_string(),
-            Some(s) => s,
-        };
-        let kernel_name = SystemInner::kernel_name().unwrap_or("Unknown");
-        if cfg!(windows) {
-            format!("{kernel_name} OS Build {kernel_version}")
-        } else {
-            format!("{kernel_name} {kernel_version}")
-        }
-    }
-
     /// Returns the system hostname based off DNS.
     ///
     /// **Important**: this information is computed every time this function is called.
@@ -1781,6 +1774,55 @@ impl Process {
     pub fn exists(&self) -> bool {
         self.inner.exists()
     }
+
+    /// Returns the number of open files in the current process.
+    ///
+    /// Returns `None` if it failed retrieving the information or if the current system is not
+    /// supported.
+    ///
+    /// **Important**: this information is computed every time this function is called (except on
+    /// FreeBSD).
+    ///
+    /// ```no_run
+    /// use sysinfo::System;
+    ///
+    /// let s = System::new_all();
+    ///
+    /// for (_, process) in s.processes() {
+    ///     println!(
+    ///         "Process {:?} {:?}",
+    ///         process.pid(),
+    ///         process.open_files(),
+    ///     );
+    /// }
+    /// ```
+    pub fn open_files(&self) -> Option<u32> {
+        self.inner.open_files()
+    }
+
+    /// Returns the maximum number of open files for the current process.
+    ///
+    /// Returns `None` if it failed retrieving the information or if the current system is not
+    /// supported.
+    ///
+    /// **Important**: this information is computed every time this function is called.
+    ///
+    /// ```no_run
+    /// use sysinfo::System;
+    ///
+    /// let s = System::new_all();
+    ///
+    /// for (_, process) in s.processes() {
+    ///     println!(
+    ///         "Process {:?} {:?}",
+    ///         process.pid(),
+    ///         process.open_files_limit(),
+    ///     );
+    /// }
+    /// ```
+    pub fn open_files_limit(&self) -> Option<u32> {
+        self.inner.open_files_limit()
+    }
 }
 
 macro_rules! pid_decl {
diff --git a/src/unix/apple/ffi.rs b/src/unix/apple/ffi.rs
index 07fd03b..eedaf25 100644
--- a/src/unix/apple/ffi.rs
+++ b/src/unix/apple/ffi.rs
@@ -23,6 +23,16 @@ cfg_if! {
             pub fn objc_autoreleasePoolPush() -> *mut c_void;
         }
     }
+    if #[cfg(feature = "system")] {
+        // FIXME: to be removed once https://github.com/rust-lang/libc/pull/4310 is merged.
+        #[allow(non_camel_case_types, dead_code)]
+        pub struct proc_fdinfo {
+            pub proc_fd: i32,
+            pub proc_fdtype: u32,
+        }
+
+        pub const PROC_PIDLISTFDS: libc::c_int = 1;
+    }
 }
 
 #[cfg_attr(feature = "debug", derive(Eq, Hash, PartialEq))]
diff --git a/src/unix/apple/process.rs b/src/unix/apple/process.rs
index 1a251da..102a1b0 100644
--- a/src/unix/apple/process.rs
+++ b/src/unix/apple/process.rs
@@ -75,3 +75,39 @@ impl From<i32> for ThreadStatus {
         }
     }
 }
+
+impl ProcessInner {
+    pub(crate) fn open_files(&self) -> Option<u32> {
+        let buffer_size_bytes = unsafe {
+            libc::proc_pidinfo(
+                self.pid().0,
+                crate::sys::ffi::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::<crate::sys::ffi::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)
+    }
+}
diff --git a/src/unix/apple/system.rs b/src/unix/apple/system.rs
index 3f224fd..4fc5753 100644
--- a/src/unix/apple/system.rs
+++ b/src/unix/apple/system.rs
@@ -501,10 +501,6 @@ impl SystemInner {
         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 _];
diff --git a/src/unix/freebsd/disk.rs b/src/unix/freebsd/disk.rs
index 1100dca..40f1100 100644
--- a/src/unix/freebsd/disk.rs
+++ b/src/unix/freebsd/disk.rs
@@ -8,12 +8,11 @@ use std::path::{Path, PathBuf};
 use std::ptr::{null_mut, NonNull};
 use std::sync::OnceLock;
 
-use libc::c_void;
+use libc::{c_void, devstat, devstat_getversion};
 
 use super::ffi::{
-    devstat, devstat_getversion, geom_stats_open, geom_stats_snapshot_free,
-    geom_stats_snapshot_get, geom_stats_snapshot_next, geom_stats_snapshot_reset, DEVSTAT_READ,
-    DEVSTAT_WRITE,
+    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};
diff --git a/src/unix/freebsd/ffi.rs b/src/unix/freebsd/ffi.rs
index dde1a35..aba679e 100644
--- a/src/unix/freebsd/ffi.rs
+++ b/src/unix/freebsd/ffi.rs
@@ -1,55 +1,13 @@
 // Take a look at the license at the top of the repository in the LICENSE file.
 
-#![allow(non_camel_case_types)]
+#![allow(non_camel_case_types, dead_code)]
 
-use libc::{bintime, c_char, c_int, c_uint, c_void, kvm_t};
+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
 
-pub type devstat_priority = c_int;
-pub type devstat_support_flags = c_int;
-pub type devstat_type_flags = c_int;
-
-#[repr(C)]
-pub(crate) struct tailq {
-    pub(crate) stqe_next: *mut devstat,
-}
-
-#[repr(C)]
-pub(crate) struct devstat {
-    pub(crate) sequence0: c_uint,
-    pub(crate) allocated: c_int,
-    pub(crate) start_count: c_uint,
-    pub(crate) end_count: c_uint,
-    pub(crate) busy_from: bintime,
-    pub(crate) dev_links: tailq,
-    pub(crate) device_number: u32,
-    pub(crate) device_name: [c_char; DEVSTAT_NAME_LEN],
-    pub(crate) unit_number: c_int,
-    pub(crate) bytes: [u64; DEVSTAT_N_TRANS_FLAGS],
-    pub(crate) operations: [u64; DEVSTAT_N_TRANS_FLAGS],
-    pub(crate) duration: [bintime; DEVSTAT_N_TRANS_FLAGS],
-    pub(crate) busy_time: bintime,
-    pub(crate) creation_time: bintime,
-    pub(crate) block_size: u32,
-    pub(crate) tag_types: [u64; 3],
-    pub(crate) flags: devstat_support_flags,
-    pub(crate) device_type: devstat_type_flags,
-    pub(crate) priority: devstat_priority,
-    pub(crate) id: *const c_void,
-    pub(crate) sequence1: c_uint,
-}
-
-// #[repr(C)]
-// pub(crate) struct devinfo {
-//     pub(crate) devices: *mut devstat,
-//     pub(crate) mem_ptr: *mut u8,
-//     pub(crate) generation: c_long,
-//     pub(crate) numdevs: c_int,
-// }
-
 // #[repr(C)]
 // pub(crate) struct statinfo {
 //     pub(crate) cp_time: [c_long; CPUSTATES as usize],
@@ -59,8 +17,51 @@ pub(crate) struct devstat {
 //     pub(crate) snap_time: c_long_double,
 // }
 
-pub(crate) const DEVSTAT_N_TRANS_FLAGS: usize = 4;
-pub(crate) const DEVSTAT_NAME_LEN: usize = 16;
+// 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;
 
@@ -69,8 +70,6 @@ pub(crate) const DEVSTAT_WRITE: usize = 0x02;
 // pub(crate) const DSM_TOTAL_BYTES_WRITE: c_int = 3;
 
 extern "C" {
-    pub(crate) fn devstat_getversion(kd: *mut kvm_t) -> c_int;
-    // pub(crate) fn devstat_getdevs(kd: *mut kvm_t, stats: *mut statinfo) -> c_int;
     // pub(crate) fn devstat_compute_statistics(current: *mut devstat, previous: *mut devstat, etime: c_long_double, ...) -> c_int;
 }
 
@@ -78,7 +77,7 @@ extern "C" {
 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 devstat;
+    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);
 }
diff --git a/src/unix/freebsd/mod.rs b/src/unix/freebsd/mod.rs
index 88b7225..1c37308 100644
--- a/src/unix/freebsd/mod.rs
+++ b/src/unix/freebsd/mod.rs
@@ -15,12 +15,15 @@ cfg_if! {
     }
     if #[cfg(feature = "disk")] {
         pub mod disk;
-        pub mod ffi;
 
         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;
 
diff --git a/src/unix/freebsd/process.rs b/src/unix/freebsd/process.rs
index 4b95e5c..b4532cb 100644
--- a/src/unix/freebsd/process.rs
+++ b/src/unix/freebsd/process.rs
@@ -7,7 +7,7 @@ use std::fmt;
 use std::path::{Path, PathBuf};
 use std::process::ExitStatus;
 
-use super::utils::{get_sys_value_str, WrapMap};
+use super::utils::{get_sys_value_by_name, get_sys_value_str, WrapMap};
 
 #[doc(hidden)]
 impl From<libc::c_char> for ProcessStatus {
@@ -66,6 +66,9 @@ pub(crate) struct ProcessInner {
     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 {
@@ -185,6 +188,21 @@ impl ProcessInner {
     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]
@@ -234,6 +252,13 @@ pub(crate) unsafe fn get_process_data(
     // 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;
+    if !kproc.ki_fd.is_null() {
+        let ki_fd = kproc.ki_fd as *mut super::ffi::filedesc;
+        if !(*ki_fd).fd_files.is_null() {
+            open_files = Some((*(*ki_fd).fd_files).fdt_nfiles as _);
+        }
+    }
 
     if let Some(proc_) = (*wrap.0.get()).get_mut(&Pid(kproc.ki_pid)) {
         let proc_ = &mut proc_.inner;
@@ -260,6 +285,8 @@ pub(crate) unsafe fn get_process_data(
                 proc_.accumulated_cpu_time = get_accumulated_cpu_time(kproc);
             }
 
+            proc_.open_files = open_files;
+
             return Ok(None);
         }
     }
@@ -316,6 +343,7 @@ pub(crate) unsafe fn get_process_data(
             },
             updated: true,
             exists: true,
+            open_files,
         },
     }))
 }
diff --git a/src/unix/freebsd/system.rs b/src/unix/freebsd/system.rs
index d4ffaab..39ee1fd 100644
--- a/src/unix/freebsd/system.rs
+++ b/src/unix/freebsd/system.rs
@@ -222,7 +222,7 @@ impl SystemInner {
     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);
+            init_mib(b"kern.osrelease\0", &mut os_release);
             get_system_info(&os_release, None)
         }
     }
@@ -236,13 +236,10 @@ impl SystemInner {
     }
 
     pub(crate) fn kernel_version() -> Option<String> {
+        let mut kern_version: [c_int; 2] = [0; 2];
         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
-            }
+            init_mib(b"kern.version\0", &mut kern_version);
+            get_system_info(&kern_version, None)
         }
     }
 
@@ -254,10 +251,6 @@ impl SystemInner {
         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 _];
diff --git a/src/unix/linux/process.rs b/src/unix/linux/process.rs
index e26a8c0..5e32fdd 100644
--- a/src/unix/linux/process.rs
+++ b/src/unix/linux/process.rs
@@ -290,6 +290,43 @@ impl ProcessInner {
     pub(crate) fn exists(&self) -> bool {
         self.exists
     }
+
+    pub(crate) fn open_files(&self) -> Option<u32> {
+        let open_files_dir = self.proc_path.as_path().join("fd");
+        match fs::read_dir(&open_files_dir) {
+            Ok(entries) => Some(entries.count() as u32),
+            Err(_error) => {
+                sysinfo_debug!(
+                    "Failed to get open files in `{}`: {_error:?}",
+                    open_files_dir.display(),
+                );
+                None
+            }
+        }
+    }
+
+    pub(crate) fn open_files_limit(&self) -> Option<u32> {
+        let limits_files = self.proc_path.as_path().join("limits");
+        match fs::read_to_string(&limits_files) {
+            Ok(content) => {
+                for line in content.lines() {
+                    if let Some(line) = line.strip_prefix("Max open files ") {
+                        if let Some(nb) = line.split_whitespace().find(|p| !p.is_empty()) {
+                            return u32::from_str(nb).ok();
+                        }
+                    }
+                }
+                None
+            }
+            Err(_error) => {
+                sysinfo_debug!(
+                    "Failed to get limits in `{}`: {_error:?}",
+                    limits_files.display()
+                );
+                None
+            }
+        }
+    }
 }
 
 pub(crate) fn compute_cpu_usage(p: &mut ProcessInner, total_time: f32, max_value: f32) {
diff --git a/src/unix/linux/system.rs b/src/unix/linux/system.rs
index b8a8cf6..13c745d 100644
--- a/src/unix/linux/system.rs
+++ b/src/unix/linux/system.rs
@@ -516,16 +516,6 @@ impl SystemInner {
         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();
 
diff --git a/src/unix/linux/utils.rs b/src/unix/linux/utils.rs
index 6783333..7f6a3e4 100644
--- a/src/unix/linux/utils.rs
+++ b/src/unix/linux/utils.rs
@@ -53,6 +53,10 @@ impl PathHandler {
         // 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")]
@@ -65,7 +69,7 @@ impl PathPush for PathHandler {
     fn join(&mut self, p: &str) -> &Path {
         self.0.pop();
         self.0.push(p);
-        self.0.as_path()
+        self.as_path()
     }
 }
 
diff --git a/src/unknown/process.rs b/src/unknown/process.rs
index ce122fd..1abcf6a 100644
--- a/src/unknown/process.rs
+++ b/src/unknown/process.rs
@@ -120,4 +120,12 @@ impl ProcessInner {
     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
+    }
 }
diff --git a/src/unknown/system.rs b/src/unknown/system.rs
index f44b191..365b79d 100644
--- a/src/unknown/system.rs
+++ b/src/unknown/system.rs
@@ -139,10 +139,6 @@ impl SystemInner {
         Vec::new()
     }
 
-    pub(crate) fn kernel_name() -> Option<&'static str> {
-        None
-    }
-
     pub(crate) fn host_name() -> Option<String> {
         None
     }
diff --git a/src/windows/process.rs b/src/windows/process.rs
index cb2d740..0161031 100644
--- a/src/windows/process.rs
+++ b/src/windows/process.rs
@@ -44,9 +44,10 @@ use windows::Win32::System::ProcessStatus::{
 use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;
 use windows::Win32::System::SystemInformation::OSVERSIONINFOEXW;
 use windows::Win32::System::Threading::{
-    GetExitCodeProcess, GetProcessIoCounters, GetProcessTimes, GetSystemTimes, OpenProcess,
-    OpenProcessToken, CREATE_NO_WINDOW, IO_COUNTERS, PEB, PROCESS_BASIC_INFORMATION,
-    PROCESS_QUERY_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ,
+    GetExitCodeProcess, GetProcessHandleCount, GetProcessIoCounters, GetProcessTimes,
+    GetSystemTimes, OpenProcess, OpenProcessToken, CREATE_NO_WINDOW, IO_COUNTERS, PEB,
+    PROCESS_BASIC_INFORMATION, PROCESS_QUERY_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION,
+    PROCESS_VM_READ,
 };
 use windows::Win32::UI::Shell::CommandLineToArgvW;
 
@@ -501,6 +502,37 @@ impl ProcessInner {
     pub(crate) fn exists(&self) -> bool {
         self.exists
     }
+
+    pub(crate) fn open_files(&self) -> Option<u32> {
+        if let Some(ref handle) = self.handle {
+            let mut handles_count = 0;
+            unsafe {
+                if let Err(_error) = GetProcessHandleCount(***handle, &mut handles_count) {
+                    sysinfo_debug!("GetProcessHandleCount failed: {_error:?}");
+                    None
+                } else {
+                    Some(handles_count)
+                }
+            }
+        } else {
+            None
+        }
+    }
+
+    pub(crate) fn open_files_limit(&self) -> Option<u32> {
+        // Apparently when using C run-time libraries, it's limited by _NHANDLE_.
+        // It's a define:
+        //
+        // ```
+        // #define IOINFO_L2E          6
+        // #define IOINFO_ARRAY_ELTS   (1 << IOINFO_L2E)
+        // #define IOINFO_ARRAYS       128
+        // #define _NHANDLE_ (IOINFO_ARRAYS * IOINFO_ARRAY_ELTS)
+        // ```
+        //
+        // So 128 * (1 << 6) = 8192
+        Some(8192)
+    }
 }
 
 #[inline]
diff --git a/src/windows/system.rs b/src/windows/system.rs
index 04d0d06..3512f5d 100644
--- a/src/windows/system.rs
+++ b/src/windows/system.rs
@@ -423,10 +423,6 @@ impl SystemInner {
         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


Test patch:
diff --git a/tests/process.rs b/tests/process.rs
index 28da513..c171de2 100644
--- a/tests/process.rs
+++ b/tests/process.rs
@@ -1103,3 +1103,22 @@ fn test_tasks() {
     });
     scheduler.join().expect("Scheduler panicked");
 }
+
+#[test]
+fn open_files() {
+    if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") {
+        return;
+    }
+    let pid = sysinfo::get_current_pid().expect("failed to get current pid");
+    let _file =
+        std::fs::File::create(std::env::temp_dir().join("sysinfo-open-files.test")).unwrap();
+    let mut s = System::new();
+    s.refresh_processes(ProcessesToUpdate::Some(&[pid]), false);
+    let cur_process = s.process(pid).unwrap();
+    assert!(cur_process
+        .open_files()
+        .is_some_and(|open_files| open_files > 0));
+    assert!(cur_process
+        .open_files_limit()
+        .is_some_and(|open_files| open_files > 0));
+}