bare_types/sys/kernel_version.rs
1//! Kernel version type for system information.
2//!
3//! This module provides a type-safe abstraction for kernel versions,
4//! ensuring valid version number parsing and comparison.
5//!
6//! Kernel versions typically follow semantic versioning (major.minor.patch)
7//! with an optional release/build string.
8//!
9//! # Examples
10//!
11//! ```rust
12//! use bare_types::sys::KernelVersion;
13//!
14//! // Parse from string
15//! let version: KernelVersion = "6.8.0-40-generic".parse()?;
16//!
17//! // Access components
18//! assert_eq!(version.major(), 6);
19//! assert_eq!(version.minor(), 8);
20//! assert_eq!(version.patch(), 0);
21//!
22//! // Get release string
23//! assert_eq!(version.release(), Some("40-generic"));
24//!
25//! // Compare versions
26//! assert!(version >= KernelVersion::new(6, 0, 0));
27//! # Ok::<(), bare_types::sys::KernelVersionError>(())
28//! ```
29use core::fmt;
30use core::str::FromStr;
31
32#[cfg(feature = "serde")]
33use serde::{Deserialize, Serialize};
34
35/// Error type for kernel version parsing.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38pub enum KernelVersionError {
39 /// Empty version string
40 Empty,
41 /// Invalid major version number
42 InvalidMajor,
43 /// Invalid minor version number
44 InvalidMinor,
45 /// Invalid patch version number
46 InvalidPatch,
47 /// Too many numeric components (max 3)
48 TooManyComponents,
49 /// Not enough numeric components (need at least 2)
50 NotEnoughComponents,
51 /// Negative version number
52 NegativeVersion,
53}
54
55impl fmt::Display for KernelVersionError {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self {
58 Self::Empty => write!(f, "kernel version string is empty"),
59 Self::InvalidMajor => write!(f, "invalid major kernel version number"),
60 Self::InvalidMinor => write!(f, "invalid minor kernel version number"),
61 Self::InvalidPatch => write!(f, "invalid patch kernel version number"),
62 Self::TooManyComponents => {
63 write!(f, "kernel version has too many numeric components (max 3)")
64 }
65 Self::NotEnoughComponents => {
66 write!(f, "kernel version needs at least 2 numeric components")
67 }
68 Self::NegativeVersion => write!(f, "kernel version numbers cannot be negative"),
69 }
70 }
71}
72
73#[cfg(feature = "std")]
74impl std::error::Error for KernelVersionError {}
75
76/// Kernel version.
77///
78/// This type provides type-safe kernel version numbers with three numeric
79/// components (major, minor, patch) and an optional release string.
80///
81/// # Format
82///
83/// Kernel version format: `major.minor.patch[-release]`
84///
85/// - **Major**: Major version number (e.g., 6 for Linux 6.x)
86/// - **Minor**: Minor version number (e.g., 8 for Linux 6.8)
87/// - **Patch**: Patch level (e.g., 0 for Linux 6.8.0)
88/// - **Release**: Optional release string (e.g., "40-generic" for Ubuntu)
89///
90/// # Invariants
91///
92/// - Major, minor, and patch versions are always present (u16)
93/// - Release string is optional and must not contain leading '-'
94/// - All version numbers are non-negative
95///
96/// # Examples
97///
98/// ```rust
99/// use bare_types::sys::KernelVersion;
100///
101/// // Create from components
102/// let version = KernelVersion::new(6, 8, 0);
103///
104/// // With release string
105/// let version = KernelVersion::with_release(6, 8, 0, "40-generic");
106///
107/// // Parse from string
108/// let version: KernelVersion = "6.8.0-40-generic".parse()?;
109/// assert_eq!(version.major(), 6);
110/// assert_eq!(version.release(), Some("40-generic"));
111/// # Ok::<(), bare_types::sys::KernelVersionError>(())
112/// ```
113#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
114#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
115pub struct KernelVersion {
116 /// The major version number
117 major: u16,
118 /// The minor version number
119 minor: u16,
120 /// The patch version number
121 patch: u16,
122 /// The optional release/build string
123 release: Option<heapless::String<64>>,
124}
125
126impl KernelVersion {
127 /// Creates a new kernel version from components.
128 ///
129 /// # Arguments
130 ///
131 /// * `major` - The major version number
132 /// * `minor` - The minor version number
133 /// * `patch` - The patch version number
134 ///
135 /// To include a release string, use [`KernelVersion::with_release`].
136 ///
137 /// # Examples
138 ///
139 /// ```rust
140 /// use bare_types::sys::KernelVersion;
141 ///
142 /// // Simple version
143 /// let version = KernelVersion::new(6, 8, 0);
144 /// assert_eq!(version.major(), 6);
145 /// assert_eq!(version.minor(), 8);
146 /// assert_eq!(version.patch(), 0);
147 /// assert_eq!(version.release(), None);
148 ///
149 /// // With release string
150 /// let version = KernelVersion::with_release(6, 8, 0, "40-generic");
151 /// assert_eq!(version.release(), Some("40-generic"));
152 /// ```
153 #[must_use]
154 pub const fn new(major: u16, minor: u16, patch: u16) -> Self {
155 Self {
156 major,
157 minor,
158 patch,
159 release: None,
160 }
161 }
162
163 /// Creates a new kernel version with a release string.
164 ///
165 /// This is a convenience method for creating a kernel version with
166 /// a release/build string. For versions without a release string,
167 /// use [`KernelVersion::new`].
168 ///
169 /// # Examples
170 ///
171 /// ```rust
172 /// use bare_types::sys::KernelVersion;
173 ///
174 /// let version = KernelVersion::with_release(6, 8, 0, "40-generic");
175 /// assert_eq!(version.release(), Some("40-generic"));
176 /// ```
177 #[must_use]
178 pub fn with_release(major: u16, minor: u16, patch: u16, release: &str) -> Self {
179 let mut rel_str: heapless::String<64> = heapless::String::new();
180 // Ignore error, truncation is acceptable for release strings
181 let _ = rel_str.push_str(release);
182
183 Self {
184 major,
185 minor,
186 patch,
187 release: Some(rel_str),
188 }
189 }
190
191 /// Returns the major version number.
192 ///
193 /// # Examples
194 ///
195 /// ```rust
196 /// use bare_types::sys::KernelVersion;
197 ///
198 /// let version = KernelVersion::new(6, 8, 0);
199 /// assert_eq!(version.major(), 6);
200 /// ```
201 #[must_use]
202 #[inline]
203 pub const fn major(&self) -> u16 {
204 self.major
205 }
206
207 /// Returns the minor version number.
208 ///
209 /// # Examples
210 ///
211 /// ```rust
212 /// use bare_types::sys::KernelVersion;
213 ///
214 /// let version = KernelVersion::new(6, 8, 0);
215 /// assert_eq!(version.minor(), 8);
216 /// ```
217 #[must_use]
218 #[inline]
219 pub const fn minor(&self) -> u16 {
220 self.minor
221 }
222
223 /// Returns the patch version number.
224 ///
225 /// # Examples
226 ///
227 /// ```rust
228 /// use bare_types::sys::KernelVersion;
229 ///
230 /// let version = KernelVersion::new(6, 8, 0);
231 /// assert_eq!(version.patch(), 0);
232 /// ```
233 #[must_use]
234 #[inline]
235 pub const fn patch(&self) -> u16 {
236 self.patch
237 }
238
239 /// Returns the optional release string.
240 ///
241 /// # Examples
242 ///
243 /// ```rust
244 /// use bare_types::sys::KernelVersion;
245 ///
246 /// let version = KernelVersion::with_release(6, 8, 0, "40-generic");
247 /// assert_eq!(version.release(), Some("40-generic"));
248 ///
249 /// let version = KernelVersion::new(6, 8, 0);
250 /// assert_eq!(version.release(), None);
251 /// ```
252 #[must_use]
253 #[inline]
254 pub fn release(&self) -> Option<&str> {
255 self.release.as_deref()
256 }
257
258 /// Returns `true` if this is a development/pre-release kernel.
259 ///
260 /// A kernel is considered a development kernel if the minor version
261 /// is odd (Linux convention: odd minor = development, even = stable).
262 ///
263 /// # Examples
264 ///
265 /// ```rust
266 /// use bare_types::sys::KernelVersion;
267 ///
268 /// // Development kernels have odd minor versions
269 /// assert!(KernelVersion::new(6, 7, 0).is_development());
270 ///
271 /// // Stable kernels have even minor versions
272 /// assert!(!KernelVersion::new(6, 8, 0).is_development());
273 /// ```
274 #[must_use]
275 pub const fn is_development(&self) -> bool {
276 // Linux convention: odd minor = development, even = stable
277 self.minor % 2 == 1
278 }
279
280 /// Returns `true` if this is a stable kernel.
281 ///
282 /// A kernel is considered stable if the minor version is even
283 /// (Linux convention).
284 ///
285 /// # Examples
286 ///
287 /// ```rust
288 /// use bare_types::sys::KernelVersion;
289 ///
290 /// assert!(KernelVersion::new(6, 8, 0).is_stable());
291 /// assert!(!KernelVersion::new(6, 7, 0).is_stable());
292 /// ```
293 #[must_use]
294 pub const fn is_stable(&self) -> bool {
295 !self.is_development()
296 }
297
298 /// Returns `true` if this is a long-term support (LTS) kernel.
299 ///
300 /// This checks if the release string contains "lts" (case-insensitive).
301 /// Note: This is a heuristic and may not be accurate for all distros.
302 ///
303 /// # Examples
304 ///
305 /// ```rust
306 /// use bare_types::sys::KernelVersion;
307 ///
308 /// let lts = KernelVersion::with_release(6, 1, 0, "lts");
309 /// assert!(lts.is_lts());
310 ///
311 /// let regular = KernelVersion::new(6, 8, 0);
312 /// assert!(!regular.is_lts());
313 /// ```
314 #[must_use]
315 pub fn is_lts(&self) -> bool {
316 self.release
317 .as_ref()
318 .is_some_and(|r| r.to_lowercase().contains("lts"))
319 }
320
321 /// Returns a tuple of (major, minor, patch) for comparison.
322 ///
323 /// # Examples
324 ///
325 /// ```rust
326 /// use bare_types::sys::KernelVersion;
327 ///
328 /// let version = KernelVersion::new(6, 8, 0);
329 /// assert_eq!(version.as_tuple(), (6, 8, 0));
330 /// ```
331 #[must_use]
332 pub const fn as_tuple(&self) -> (u16, u16, u16) {
333 (self.major, self.minor, self.patch)
334 }
335
336 /// Returns a new version with the patch level incremented.
337 ///
338 /// # Examples
339 ///
340 /// ```rust
341 /// use bare_types::sys::KernelVersion;
342 ///
343 /// let version = KernelVersion::new(6, 8, 0);
344 /// let bumped = version.bump_patch();
345 /// assert_eq!(bumped.patch(), 1);
346 /// ```
347 #[must_use]
348 pub const fn bump_patch(&self) -> Self {
349 Self::new(self.major, self.minor, self.patch.saturating_add(1))
350 }
351
352 /// Returns a new version with the minor version incremented and patch reset.
353 ///
354 /// # Examples
355 ///
356 /// ```rust
357 /// use bare_types::sys::KernelVersion;
358 ///
359 /// let version = KernelVersion::new(6, 8, 5);
360 /// let bumped = version.bump_minor();
361 /// assert_eq!(bumped.minor(), 9);
362 /// assert_eq!(bumped.patch(), 0);
363 /// ```
364 #[must_use]
365 pub const fn bump_minor(&self) -> Self {
366 Self::new(self.major, self.minor.saturating_add(1), 0)
367 }
368
369 /// Returns a new version with the major version incremented and others reset.
370 ///
371 /// # Examples
372 ///
373 /// ```rust
374 /// use bare_types::sys::KernelVersion;
375 ///
376 /// let version = KernelVersion::new(6, 8, 5);
377 /// let bumped = version.bump_major();
378 /// assert_eq!(bumped.major(), 7);
379 /// assert_eq!(bumped.minor(), 0);
380 /// assert_eq!(bumped.patch(), 0);
381 /// ```
382 #[must_use]
383 pub const fn bump_major(&self) -> Self {
384 Self::new(self.major.saturating_add(1), 0, 0)
385 }
386
387 /// Returns a version without the release string.
388 ///
389 /// # Examples
390 ///
391 /// ```rust
392 /// use bare_types::sys::KernelVersion;
393 ///
394 /// let version = KernelVersion::with_release(6, 8, 0, "40-generic");
395 /// let without = version.without_release();
396 /// assert_eq!(without.release(), None);
397 /// assert_eq!(without.major(), 6);
398 /// ```
399 #[must_use]
400 pub const fn without_release(&self) -> Self {
401 Self::new(self.major, self.minor, self.patch)
402 }
403}
404
405impl FromStr for KernelVersion {
406 type Err = KernelVersionError;
407
408 fn from_str(s: &str) -> Result<Self, Self::Err> {
409 if s.is_empty() {
410 return Err(KernelVersionError::Empty);
411 }
412
413 // Split on '-' to separate version from release
414 let (version_part, release_part) = s
415 .find('-')
416 .map_or((s, None), |idx| (&s[..idx], Some(&s[idx + 1..])));
417
418 let parts: Vec<&str> = version_part.split('.').collect();
419
420 if parts.len() > 3 {
421 return Err(KernelVersionError::TooManyComponents);
422 }
423
424 if parts.len() < 2 {
425 return Err(KernelVersionError::NotEnoughComponents);
426 }
427
428 let major = parts[0]
429 .parse::<u16>()
430 .map_err(|_| KernelVersionError::InvalidMajor)?;
431
432 let minor = parts[1]
433 .parse::<u16>()
434 .map_err(|_| KernelVersionError::InvalidMinor)?;
435
436 let patch = if parts.len() > 2 {
437 parts[2]
438 .parse::<u16>()
439 .map_err(|_| KernelVersionError::InvalidPatch)?
440 } else {
441 0
442 };
443
444 let release = release_part.map(|r| {
445 let mut rel_str: heapless::String<64> = heapless::String::new();
446 // Truncate if necessary, ignoring errors
447 let _ = rel_str.push_str(r);
448 rel_str
449 });
450
451 Ok(Self {
452 major,
453 minor,
454 patch,
455 release,
456 })
457 }
458}
459
460impl TryFrom<&str> for KernelVersion {
461 type Error = KernelVersionError;
462
463 fn try_from(s: &str) -> Result<Self, Self::Error> {
464 s.parse()
465 }
466}
467
468impl fmt::Display for KernelVersion {
469 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
470 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
471 if let Some(release) = &self.release {
472 write!(f, "-{release}")?;
473 }
474 Ok(())
475 }
476}
477
478#[cfg(feature = "arbitrary")]
479impl<'a> arbitrary::Arbitrary<'a> for KernelVersion {
480 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
481 let major = u16::arbitrary(u)?;
482 let minor = u16::arbitrary(u)?;
483 let patch = u16::arbitrary(u)?;
484 let has_release = bool::arbitrary(u)?;
485
486 let release = if has_release {
487 // Generate a limited release string to avoid excessive allocations
488 let len = u.int_in_range(0..=32usize)?;
489 let mut s: heapless::String<64> = heapless::String::new();
490 for _ in 0..len {
491 let ch: char = char::arbitrary(u)?;
492 if ch.is_ascii_alphanumeric() || ch == '-' || ch == '.' || ch == '_' {
493 let _ = s.push(ch);
494 }
495 }
496 Some(s)
497 } else {
498 None
499 };
500
501 Ok(Self {
502 major,
503 minor,
504 patch,
505 release,
506 })
507 }
508}
509
510#[cfg(test)]
511mod tests {
512 use super::*;
513
514 #[test]
515 fn test_new() {
516 let version = KernelVersion::new(6, 8, 0);
517 assert_eq!(version.major(), 6);
518 assert_eq!(version.minor(), 8);
519 assert_eq!(version.patch(), 0);
520 assert_eq!(version.release(), None);
521 }
522
523 #[test]
524 fn test_with_release() {
525 let version = KernelVersion::with_release(6, 8, 0, "40-generic");
526 assert_eq!(version.major(), 6);
527 assert_eq!(version.minor(), 8);
528 assert_eq!(version.patch(), 0);
529 assert_eq!(version.release(), Some("40-generic"));
530 }
531
532 #[test]
533 fn test_is_development() {
534 // Odd minor = development
535 assert!(KernelVersion::new(6, 7, 0).is_development());
536 assert!(KernelVersion::new(6, 9, 0).is_development());
537
538 // Even minor = stable
539 assert!(!KernelVersion::new(6, 8, 0).is_development());
540 assert!(!KernelVersion::new(6, 10, 0).is_development());
541 }
542
543 #[test]
544 fn test_is_stable() {
545 assert!(KernelVersion::new(6, 8, 0).is_stable());
546 assert!(!KernelVersion::new(6, 7, 0).is_stable());
547 }
548
549 #[test]
550 fn test_is_lts() {
551 let lts = KernelVersion::with_release(6, 1, 0, "lts");
552 assert!(lts.is_lts());
553
554 let lts_upper = KernelVersion::with_release(6, 1, 0, "LTS");
555 assert!(lts_upper.is_lts());
556
557 let regular = KernelVersion::new(6, 8, 0);
558 assert!(!regular.is_lts());
559
560 let generic = KernelVersion::with_release(6, 8, 0, "40-generic");
561 assert!(!generic.is_lts());
562 }
563
564 #[test]
565 fn test_as_tuple() {
566 let version = KernelVersion::new(6, 8, 5);
567 assert_eq!(version.as_tuple(), (6, 8, 5));
568 }
569
570 #[test]
571 fn test_bump_patch() {
572 let version = KernelVersion::new(6, 8, 0);
573 let bumped = version.bump_patch();
574 assert_eq!(bumped.patch(), 1);
575 assert_eq!(bumped.major(), 6);
576 assert_eq!(bumped.minor(), 8);
577 }
578
579 #[test]
580 fn test_bump_minor() {
581 let version = KernelVersion::new(6, 8, 5);
582 let bumped = version.bump_minor();
583 assert_eq!(bumped.major(), 6);
584 assert_eq!(bumped.minor(), 9);
585 assert_eq!(bumped.patch(), 0);
586 }
587
588 #[test]
589 fn test_bump_major() {
590 let version = KernelVersion::new(6, 8, 5);
591 let bumped = version.bump_major();
592 assert_eq!(bumped.major(), 7);
593 assert_eq!(bumped.minor(), 0);
594 assert_eq!(bumped.patch(), 0);
595 }
596
597 #[test]
598 fn test_without_release() {
599 let version = KernelVersion::with_release(6, 8, 0, "40-generic");
600 let without = version.without_release();
601 assert_eq!(without.release(), None);
602 assert_eq!(without.major(), 6);
603 assert_eq!(without.minor(), 8);
604 assert_eq!(without.patch(), 0);
605 }
606
607 #[test]
608 fn test_from_str_simple() {
609 let version: KernelVersion = "6.8.0".parse().unwrap();
610 assert_eq!(version.major(), 6);
611 assert_eq!(version.minor(), 8);
612 assert_eq!(version.patch(), 0);
613 assert_eq!(version.release(), None);
614 }
615
616 #[test]
617 fn test_from_str_with_release() {
618 let version: KernelVersion = "6.8.0-40-generic".parse().unwrap();
619 assert_eq!(version.major(), 6);
620 assert_eq!(version.minor(), 8);
621 assert_eq!(version.patch(), 0);
622 assert_eq!(version.release(), Some("40-generic"));
623 }
624
625 #[test]
626 fn test_from_str_two_components() {
627 let version: KernelVersion = "6.8".parse().unwrap();
628 assert_eq!(version.major(), 6);
629 assert_eq!(version.minor(), 8);
630 assert_eq!(version.patch(), 0);
631 }
632
633 #[test]
634 fn test_from_str_long_release() {
635 let version: KernelVersion = "5.15.0-1052-aws".parse().unwrap();
636 assert_eq!(version.major(), 5);
637 assert_eq!(version.minor(), 15);
638 assert_eq!(version.patch(), 0);
639 assert_eq!(version.release(), Some("1052-aws"));
640 }
641
642 #[test]
643 fn test_from_str_errors() {
644 // Empty
645 assert!(matches!(
646 "".parse::<KernelVersion>(),
647 Err(KernelVersionError::Empty)
648 ));
649
650 // Too many numeric components
651 assert!(matches!(
652 "1.2.3.4".parse::<KernelVersion>(),
653 Err(KernelVersionError::TooManyComponents)
654 ));
655
656 // Not enough components
657 assert!(matches!(
658 "6".parse::<KernelVersion>(),
659 Err(KernelVersionError::NotEnoughComponents)
660 ));
661
662 // Invalid numbers
663 assert!("abc.def".parse::<KernelVersion>().is_err());
664 assert!("6.abc".parse::<KernelVersion>().is_err());
665 assert!("6.8.abc".parse::<KernelVersion>().is_err());
666 }
667
668 #[test]
669 fn test_display() {
670 let version = KernelVersion::new(6, 8, 0);
671 assert_eq!(format!("{}", version), "6.8.0");
672
673 let version = KernelVersion::with_release(6, 8, 0, "40-generic");
674 assert_eq!(format!("{}", version), "6.8.0-40-generic");
675 }
676
677 #[test]
678 fn test_equality() {
679 let v1 = KernelVersion::new(6, 8, 0);
680 let v2 = KernelVersion::new(6, 8, 0);
681 let v3 = KernelVersion::with_release(6, 8, 0, "40-generic");
682
683 assert_eq!(v1, v2);
684 assert_ne!(v1, v3);
685 }
686
687 #[test]
688 fn test_ordering() {
689 let v1 = KernelVersion::new(6, 8, 0);
690 let v2 = KernelVersion::new(6, 8, 1);
691 let v3 = KernelVersion::new(6, 9, 0);
692 let v4 = KernelVersion::new(7, 0, 0);
693
694 assert!(v1 < v2);
695 assert!(v2 < v3);
696 assert!(v3 < v4);
697
698 // Release string affects ordering because KernelVersion derives PartialOrd
699 // Two versions with different release strings are not equal
700 let with_release = KernelVersion::with_release(6, 8, 0, "40-generic");
701 let without_release = KernelVersion::new(6, 8, 0);
702 // Version numbers are the same, but release strings differ
703 assert_ne!(with_release, without_release);
704 }
705
706 #[test]
707 fn test_clone() {
708 let version = KernelVersion::with_release(6, 8, 0, "40-generic");
709 let version2 = version.clone();
710 assert_eq!(version, version2);
711 }
712
713 #[test]
714 fn test_common_kernel_versions() {
715 // Linux kernel versions
716 let linux_6_8: KernelVersion = "6.8.0".parse().unwrap();
717 assert_eq!(linux_6_8.major(), 6);
718 assert!(linux_6_8.is_stable());
719
720 let linux_5_15: KernelVersion = "5.15.0".parse().unwrap();
721 assert_eq!(linux_5_15.major(), 5);
722
723 // Ubuntu kernel
724 let ubuntu: KernelVersion = "6.8.0-40-generic".parse().unwrap();
725 assert_eq!(ubuntu.major(), 6);
726 assert_eq!(ubuntu.release(), Some("40-generic"));
727
728 // AWS kernel
729 let aws: KernelVersion = "5.15.0-1052-aws".parse().unwrap();
730 assert_eq!(aws.major(), 5);
731 assert_eq!(aws.release(), Some("1052-aws"));
732 }
733}