1use std::cmp::Ordering;
19use std::fmt;
20
21#[cfg(any(target_os = "linux", target_os = "macos"))]
22use libc;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub struct KernelVersion {
29 pub major: u32,
31 pub minor: u32,
33 pub patch: u32,
35}
36
37impl KernelVersion {
38 #[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 #[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 #[cfg(target_os = "linux")]
71 fn detect_linux() -> Option<Self> {
72 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 Self::detect_uname()
81 }
82
83 #[cfg(target_os = "macos")]
85 fn detect_macos() -> Option<Self> {
86 Self::detect_uname()
87 }
88
89 #[cfg(any(target_os = "linux", target_os = "macos"))]
91 fn detect_uname() -> Option<Self> {
92 unsafe {
94 let mut info: libc::utsname = std::mem::zeroed();
95 if libc::uname(&raw mut info) != 0 {
96 return None;
97 }
98
99 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 #[allow(dead_code)]
112 fn parse_proc_version(content: &str) -> Option<Self> {
113 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 #[allow(dead_code)]
127 fn parse_version_string(s: &str) -> Option<Self> {
128 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 #[must_use]
147 pub fn supports_io_uring(&self) -> bool {
148 *self >= Self::new(5, 1, 0)
149 }
150
151 #[must_use]
153 pub fn supports_io_uring_fixed_files(&self) -> bool {
154 *self >= Self::new(5, 1, 0)
155 }
156
157 #[must_use]
159 pub fn supports_io_uring_registered_buffers(&self) -> bool {
160 *self >= Self::new(5, 1, 0)
161 }
162
163 #[must_use]
168 pub fn supports_io_uring_sqpoll(&self) -> bool {
169 *self >= Self::new(5, 11, 0)
170 }
171
172 #[must_use]
176 pub fn supports_io_uring_iopoll(&self) -> bool {
177 *self >= Self::new(5, 19, 0)
178 }
179
180 #[must_use]
182 pub fn supports_io_uring_multishot(&self) -> bool {
183 *self >= Self::new(5, 19, 0)
184 }
185
186 #[must_use]
188 pub fn supports_io_uring_coop_taskrun(&self) -> bool {
189 *self >= Self::new(5, 19, 0)
190 }
191
192 #[must_use]
194 pub fn supports_io_uring_single_issuer(&self) -> bool {
195 *self >= Self::new(6, 0, 0)
196 }
197
198 #[must_use]
202 pub fn supports_xdp(&self) -> bool {
203 *self >= Self::new(4, 8, 0)
204 }
205
206 #[must_use]
208 pub fn supports_xdp_generic(&self) -> bool {
209 *self >= Self::new(4, 12, 0)
210 }
211
212 #[must_use]
214 pub fn supports_xdp_native(&self) -> bool {
215 *self >= Self::new(5, 3, 0)
216 }
217
218 #[must_use]
220 pub fn supports_xdp_cpumap(&self) -> bool {
221 *self >= Self::new(4, 15, 0)
222 }
223
224 #[must_use]
228 pub fn supports_thp(&self) -> bool {
229 *self >= Self::new(2, 6, 38)
230 }
231
232 #[must_use]
234 pub fn supports_madv_hugepage(&self) -> bool {
235 *self >= Self::new(2, 6, 38)
236 }
237
238 #[must_use]
240 pub fn supports_cpu_affinity(&self) -> bool {
241 *self >= Self::new(2, 5, 8)
242 }
243
244 #[must_use]
246 pub fn supports_numa_policy(&self) -> bool {
247 *self >= Self::new(2, 6, 7)
248 }
249
250 #[must_use]
252 pub const fn minimum_recommended() -> Self {
253 Self::new(5, 10, 0)
254 }
255
256 #[must_use]
258 pub const fn optimal_recommended() -> Self {
259 Self::new(5, 19, 0)
260 }
261
262 #[must_use]
264 pub fn meets_minimum(&self) -> bool {
265 *self >= Self::minimum_recommended()
266 }
267
268 #[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 assert!(!v4.supports_io_uring());
393 assert!(v5_1.supports_io_uring());
394 assert!(v5_11.supports_io_uring());
395
396 assert!(!v4.supports_io_uring_sqpoll());
398 assert!(!v5_1.supports_io_uring_sqpoll());
399 assert!(v5_11.supports_io_uring_sqpoll());
400
401 assert!(!v5_11.supports_io_uring_iopoll());
403 assert!(v5_19.supports_io_uring_iopoll());
404
405 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 let version = KernelVersion::detect();
460
461 #[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 #[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 #[cfg(target_os = "windows")]
479 {
480 assert!(version.is_none());
481 }
482 }
483}