Skip to main content

laminar_core/detect/
kernel.rs

1//! # Kernel Version Detection
2//!
3//! Detects the Linux kernel version to determine which features are available.
4//!
5//! ## Usage
6//!
7//! ```rust,ignore
8//! use laminar_core::detect::KernelVersion;
9//!
10//! if let Some(version) = KernelVersion::detect() {
11//!     println!("Kernel: {}", version);
12//!     if version.supports_io_uring_sqpoll() {
13//!         println!("SQPOLL is supported!");
14//!     }
15//! }
16//! ```
17
18use std::cmp::Ordering;
19use std::fmt;
20
21#[cfg(any(target_os = "linux", target_os = "macos"))]
22use libc;
23
24/// Linux kernel version.
25///
26/// Parsed from `/proc/version` or `uname -r`.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub struct KernelVersion {
29    /// Major version (e.g., 5 in 5.15.0)
30    pub major: u32,
31    /// Minor version (e.g., 15 in 5.15.0)
32    pub minor: u32,
33    /// Patch version (e.g., 0 in 5.15.0)
34    pub patch: u32,
35}
36
37impl KernelVersion {
38    /// Creates a new kernel version.
39    #[must_use]
40    pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
41        Self {
42            major,
43            minor,
44            patch,
45        }
46    }
47
48    /// Detects the current kernel version.
49    ///
50    /// Returns `None` on non-Linux platforms or if detection fails.
51    #[must_use]
52    pub fn detect() -> Option<Self> {
53        #[cfg(target_os = "linux")]
54        {
55            Self::detect_linux()
56        }
57
58        #[cfg(target_os = "macos")]
59        {
60            Self::detect_macos()
61        }
62
63        #[cfg(not(any(target_os = "linux", target_os = "macos")))]
64        {
65            None
66        }
67    }
68
69    /// Detect kernel version on Linux.
70    #[cfg(target_os = "linux")]
71    fn detect_linux() -> Option<Self> {
72        // Try /proc/version first (most reliable)
73        if let Ok(version) = std::fs::read_to_string("/proc/version") {
74            if let Some(v) = Self::parse_proc_version(&version) {
75                return Some(v);
76            }
77        }
78
79        // Fallback to uname
80        Self::detect_uname()
81    }
82
83    /// Detect kernel version on macOS (Darwin kernel).
84    #[cfg(target_os = "macos")]
85    fn detect_macos() -> Option<Self> {
86        Self::detect_uname()
87    }
88
89    /// Detect kernel version using libc uname.
90    #[cfg(any(target_os = "linux", target_os = "macos"))]
91    fn detect_uname() -> Option<Self> {
92        // SAFETY: uname is a safe syscall that fills a buffer
93        unsafe {
94            let mut info: libc::utsname = std::mem::zeroed();
95            if libc::uname(&raw mut info) != 0 {
96                return None;
97            }
98
99            // Convert release field to string
100            let release = std::ffi::CStr::from_ptr(info.release.as_ptr())
101                .to_str()
102                .ok()?;
103
104            Self::parse_version_string(release)
105        }
106    }
107
108    /// Parse `/proc/version` content.
109    ///
110    /// Format: "Linux version 5.15.0-91-generic ..."
111    #[allow(dead_code)]
112    fn parse_proc_version(content: &str) -> Option<Self> {
113        // Find "Linux version X.Y.Z"
114        let parts: Vec<&str> = content.split_whitespace().collect();
115        for (i, part) in parts.iter().enumerate() {
116            if *part == "version" {
117                if let Some(version_str) = parts.get(i + 1) {
118                    return Self::parse_version_string(version_str);
119                }
120            }
121        }
122        None
123    }
124
125    /// Parse a version string like "5.15.0-91-generic" or "5.15.0".
126    #[allow(dead_code)]
127    fn parse_version_string(s: &str) -> Option<Self> {
128        // Take only the numeric prefix (before any dash or other suffix)
129        let version_part = s.split(|c: char| !c.is_ascii_digit() && c != '.').next()?;
130
131        let mut parts = version_part.split('.');
132        let major: u32 = parts.next()?.parse().ok()?;
133        let minor: u32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
134        let patch: u32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
135
136        Some(Self {
137            major,
138            minor,
139            patch,
140        })
141    }
142
143    // ===== io_uring Feature Checks =====
144
145    /// Check if basic `io_uring` is supported (Linux 5.1+).
146    #[must_use]
147    pub fn supports_io_uring(&self) -> bool {
148        *self >= Self::new(5, 1, 0)
149    }
150
151    /// Check if `io_uring` with fixed files is supported (Linux 5.1+).
152    #[must_use]
153    pub fn supports_io_uring_fixed_files(&self) -> bool {
154        *self >= Self::new(5, 1, 0)
155    }
156
157    /// Check if `io_uring` with registered buffers is supported (Linux 5.1+).
158    #[must_use]
159    pub fn supports_io_uring_registered_buffers(&self) -> bool {
160        *self >= Self::new(5, 1, 0)
161    }
162
163    /// Check if `io_uring` SQPOLL mode is supported (Linux 5.11+).
164    ///
165    /// SQPOLL uses a dedicated kernel thread to poll the submission queue,
166    /// eliminating syscalls under load.
167    #[must_use]
168    pub fn supports_io_uring_sqpoll(&self) -> bool {
169        *self >= Self::new(5, 11, 0)
170    }
171
172    /// Check if `io_uring` IOPOLL mode is supported for `NVMe` (Linux 5.19+).
173    ///
174    /// IOPOLL polls completions directly from the `NVMe` queue without interrupts.
175    #[must_use]
176    pub fn supports_io_uring_iopoll(&self) -> bool {
177        *self >= Self::new(5, 19, 0)
178    }
179
180    /// Check if `io_uring` multishot operations are supported (Linux 5.19+).
181    #[must_use]
182    pub fn supports_io_uring_multishot(&self) -> bool {
183        *self >= Self::new(5, 19, 0)
184    }
185
186    /// Check if `io_uring` `COOP_TASKRUN` is supported (Linux 5.19+).
187    #[must_use]
188    pub fn supports_io_uring_coop_taskrun(&self) -> bool {
189        *self >= Self::new(5, 19, 0)
190    }
191
192    /// Check if `io_uring` `SINGLE_ISSUER` is supported (Linux 6.0+).
193    #[must_use]
194    pub fn supports_io_uring_single_issuer(&self) -> bool {
195        *self >= Self::new(6, 0, 0)
196    }
197
198    // ===== XDP/eBPF Feature Checks =====
199
200    /// Check if XDP is supported (Linux 4.8+).
201    #[must_use]
202    pub fn supports_xdp(&self) -> bool {
203        *self >= Self::new(4, 8, 0)
204    }
205
206    /// Check if XDP generic mode (SKB) is supported (Linux 4.12+).
207    #[must_use]
208    pub fn supports_xdp_generic(&self) -> bool {
209        *self >= Self::new(4, 12, 0)
210    }
211
212    /// Check if XDP native mode is widely supported (Linux 5.3+).
213    #[must_use]
214    pub fn supports_xdp_native(&self) -> bool {
215        *self >= Self::new(5, 3, 0)
216    }
217
218    /// Check if XDP redirect to CPU map is supported (Linux 4.15+).
219    #[must_use]
220    pub fn supports_xdp_cpumap(&self) -> bool {
221        *self >= Self::new(4, 15, 0)
222    }
223
224    // ===== Other Feature Checks =====
225
226    /// Check if transparent huge pages are available (Linux 2.6.38+).
227    #[must_use]
228    pub fn supports_thp(&self) -> bool {
229        *self >= Self::new(2, 6, 38)
230    }
231
232    /// Check if madvise `MADV_HUGEPAGE` is supported (Linux 2.6.38+).
233    #[must_use]
234    pub fn supports_madv_hugepage(&self) -> bool {
235        *self >= Self::new(2, 6, 38)
236    }
237
238    /// Check if CPU affinity syscalls are supported (Linux 2.5.8+).
239    #[must_use]
240    pub fn supports_cpu_affinity(&self) -> bool {
241        *self >= Self::new(2, 5, 8)
242    }
243
244    /// Check if NUMA memory policy is supported (Linux 2.6.7+).
245    #[must_use]
246    pub fn supports_numa_policy(&self) -> bool {
247        *self >= Self::new(2, 6, 7)
248    }
249
250    /// Returns the minimum kernel version for basic `LaminarDB` operation.
251    #[must_use]
252    pub const fn minimum_recommended() -> Self {
253        Self::new(5, 10, 0)
254    }
255
256    /// Returns the recommended kernel version for optimal performance.
257    #[must_use]
258    pub const fn optimal_recommended() -> Self {
259        Self::new(5, 19, 0)
260    }
261
262    /// Check if this version meets the minimum requirements for `LaminarDB`.
263    #[must_use]
264    pub fn meets_minimum(&self) -> bool {
265        *self >= Self::minimum_recommended()
266    }
267
268    /// Check if this version is optimal for `LaminarDB` performance.
269    #[must_use]
270    pub fn is_optimal(&self) -> bool {
271        *self >= Self::optimal_recommended()
272    }
273}
274
275impl PartialOrd for KernelVersion {
276    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
277        Some(self.cmp(other))
278    }
279}
280
281impl Ord for KernelVersion {
282    fn cmp(&self, other: &Self) -> Ordering {
283        match self.major.cmp(&other.major) {
284            Ordering::Equal => match self.minor.cmp(&other.minor) {
285                Ordering::Equal => self.patch.cmp(&other.patch),
286                ord => ord,
287            },
288            ord => ord,
289        }
290    }
291}
292
293impl fmt::Display for KernelVersion {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
296    }
297}
298
299impl From<(u32, u32, u32)> for KernelVersion {
300    fn from((major, minor, patch): (u32, u32, u32)) -> Self {
301        Self::new(major, minor, patch)
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308
309    #[test]
310    fn test_kernel_version_new() {
311        let v = KernelVersion::new(5, 15, 0);
312        assert_eq!(v.major, 5);
313        assert_eq!(v.minor, 15);
314        assert_eq!(v.patch, 0);
315    }
316
317    #[test]
318    fn test_kernel_version_from_tuple() {
319        let v: KernelVersion = (5, 15, 0).into();
320        assert_eq!(v, KernelVersion::new(5, 15, 0));
321    }
322
323    #[test]
324    fn test_kernel_version_display() {
325        let v = KernelVersion::new(5, 15, 91);
326        assert_eq!(format!("{v}"), "5.15.91");
327    }
328
329    #[test]
330    fn test_kernel_version_ordering() {
331        let v4 = KernelVersion::new(4, 15, 0);
332        let v5_10 = KernelVersion::new(5, 10, 0);
333        let v5_15 = KernelVersion::new(5, 15, 0);
334        let v5_15_1 = KernelVersion::new(5, 15, 1);
335        let v6 = KernelVersion::new(6, 0, 0);
336
337        assert!(v4 < v5_10);
338        assert!(v5_10 < v5_15);
339        assert!(v5_15 < v5_15_1);
340        assert!(v5_15_1 < v6);
341
342        assert!(v5_15 == KernelVersion::new(5, 15, 0));
343    }
344
345    #[test]
346    fn test_parse_version_string() {
347        assert_eq!(
348            KernelVersion::parse_version_string("5.15.0"),
349            Some(KernelVersion::new(5, 15, 0))
350        );
351
352        assert_eq!(
353            KernelVersion::parse_version_string("5.15.0-91-generic"),
354            Some(KernelVersion::new(5, 15, 0))
355        );
356
357        assert_eq!(
358            KernelVersion::parse_version_string("6.2.0-39-generic"),
359            Some(KernelVersion::new(6, 2, 0))
360        );
361
362        assert_eq!(
363            KernelVersion::parse_version_string("5.4"),
364            Some(KernelVersion::new(5, 4, 0))
365        );
366
367        assert_eq!(
368            KernelVersion::parse_version_string("5"),
369            Some(KernelVersion::new(5, 0, 0))
370        );
371    }
372
373    #[test]
374    fn test_parse_proc_version() {
375        let content = "Linux version 5.15.0-91-generic (buildd@lcy02-amd64-047) \
376                       (gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, GNU ld (GNU Binutils for Ubuntu) 2.38) \
377                       #101-Ubuntu SMP Tue Nov 14 13:30:08 UTC 2023";
378
379        let version = KernelVersion::parse_proc_version(content).unwrap();
380        assert_eq!(version, KernelVersion::new(5, 15, 0));
381    }
382
383    #[test]
384    fn test_supports_io_uring() {
385        let v4 = KernelVersion::new(4, 19, 0);
386        let v5_1 = KernelVersion::new(5, 1, 0);
387        let v5_11 = KernelVersion::new(5, 11, 0);
388        let v5_19 = KernelVersion::new(5, 19, 0);
389        let v6 = KernelVersion::new(6, 0, 0);
390
391        // Basic io_uring
392        assert!(!v4.supports_io_uring());
393        assert!(v5_1.supports_io_uring());
394        assert!(v5_11.supports_io_uring());
395
396        // SQPOLL
397        assert!(!v4.supports_io_uring_sqpoll());
398        assert!(!v5_1.supports_io_uring_sqpoll());
399        assert!(v5_11.supports_io_uring_sqpoll());
400
401        // IOPOLL
402        assert!(!v5_11.supports_io_uring_iopoll());
403        assert!(v5_19.supports_io_uring_iopoll());
404
405        // SINGLE_ISSUER
406        assert!(!v5_19.supports_io_uring_single_issuer());
407        assert!(v6.supports_io_uring_single_issuer());
408    }
409
410    #[test]
411    fn test_supports_xdp() {
412        let v4_8 = KernelVersion::new(4, 8, 0);
413        let v4_12 = KernelVersion::new(4, 12, 0);
414        let v4_15 = KernelVersion::new(4, 15, 0);
415        let v5_3 = KernelVersion::new(5, 3, 0);
416
417        assert!(v4_8.supports_xdp());
418        assert!(!v4_8.supports_xdp_generic());
419
420        assert!(v4_12.supports_xdp_generic());
421        assert!(!v4_12.supports_xdp_cpumap());
422
423        assert!(v4_15.supports_xdp_cpumap());
424        assert!(!v4_15.supports_xdp_native());
425
426        assert!(v5_3.supports_xdp_native());
427    }
428
429    #[test]
430    fn test_minimum_recommended() {
431        let min = KernelVersion::minimum_recommended();
432        assert_eq!(min, KernelVersion::new(5, 10, 0));
433
434        let v4 = KernelVersion::new(4, 19, 0);
435        let v5_15 = KernelVersion::new(5, 15, 0);
436
437        assert!(!v4.meets_minimum());
438        assert!(v5_15.meets_minimum());
439    }
440
441    #[test]
442    fn test_optimal_recommended() {
443        let opt = KernelVersion::optimal_recommended();
444        assert_eq!(opt, KernelVersion::new(5, 19, 0));
445
446        let v5_15 = KernelVersion::new(5, 15, 0);
447        let v5_19 = KernelVersion::new(5, 19, 0);
448        let v6 = KernelVersion::new(6, 2, 0);
449
450        assert!(!v5_15.is_optimal());
451        assert!(v5_19.is_optimal());
452        assert!(v6.is_optimal());
453    }
454
455    #[test]
456    fn test_detect() {
457        // This test always passes - detection may or may not succeed
458        // depending on the platform
459        let version = KernelVersion::detect();
460
461        // On Linux, we should get a version
462        #[cfg(target_os = "linux")]
463        {
464            assert!(version.is_some(), "Failed to detect Linux kernel version");
465            let v = version.unwrap();
466            assert!(v.major >= 2, "Kernel version too old: {v}");
467        }
468
469        // On macOS, we should get a Darwin version
470        #[cfg(target_os = "macos")]
471        {
472            assert!(version.is_some(), "Failed to detect macOS kernel version");
473            let v = version.unwrap();
474            assert!(v.major >= 10, "Darwin version too old: {v}");
475        }
476
477        // On Windows, we get None
478        #[cfg(target_os = "windows")]
479        {
480            assert!(version.is_none());
481        }
482    }
483}