Skip to main content

isr_dl_linux/
banner.rs

1use std::{path::PathBuf, sync::LazyLock};
2
3use regex::Regex;
4
5use super::DownloaderError;
6
7/// Distribution-specific version signature extracted from a kernel banner.
8#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub enum LinuxVersionSignature {
10    /// Ubuntu signature.
11    Ubuntu(UbuntuVersionSignature),
12}
13
14impl LinuxVersionSignature {
15    /// Returns the relative subdirectory used to cache artifacts for this signature.
16    pub fn subdirectory(&self) -> PathBuf {
17        match self {
18            Self::Ubuntu(signature) => signature.subdirectory(),
19        }
20    }
21}
22
23/// Ubuntu kernel version signature, as embedded in the UTS version string.
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
25pub struct UbuntuVersionSignature {
26    /// Upstream kernel release, e.g. `6.8.0`.
27    pub release: String,
28
29    /// Ubuntu revision, e.g. `40.40~22.04.3`.
30    pub revision: String,
31
32    /// Kernel flavour, e.g. `generic`, `lowlatency`.
33    pub kernel_flavour: String,
34
35    /// Mainline kernel version the Ubuntu kernel is based on, e.g. `6.8.12`.
36    pub mainline_kernel_version: String,
37}
38
39// Build the Ubuntu kernel package name and version string.
40// Example:
41//     UbuntuVersionSignature {
42//         release: "6.8.0",
43//         revision: "40.40~22.04.3",
44//         kernel_flavour: "generic",
45//         mainline_kernel_version: "6.8.12",
46//     }
47//
48// ... results in:
49//     revision_short   :   "40"
50//     kernel_version   :   "6.8.0-40.40~22.04.3"
51//     kernel_release   :   "6.8.0-40-generic"
52//     subdirectory     :   "6.8.0-40.40~22.04.3-generic"
53//
54// See https://ubuntu.com/kernel for more information.
55
56impl UbuntuVersionSignature {
57    /// Short revision, the portion before the first `.`.
58    pub fn revision_short(&self) -> &str {
59        match self.revision.split_once('.') {
60            Some((revision_short, _)) => revision_short,
61            None => &self.revision,
62        }
63    }
64
65    /// `{release}-{revision_short}-{kernel_flavour}`, e.g. `6.8.0-40-generic`.
66    pub fn kernel_release(&self) -> String {
67        format!(
68            "{}-{}-{}",
69            self.release,
70            self.revision_short(),
71            self.kernel_flavour
72        )
73    }
74
75    /// `{release}-{revision}`, e.g. `6.8.0-40.40~22.04.3`.
76    pub fn kernel_version(&self) -> String {
77        format!("{}-{}", self.release, self.revision)
78    }
79
80    /// Subdirectory `{kernel_version}-{kernel_flavour}`.
81    pub fn subdirectory(&self) -> PathBuf {
82        PathBuf::from(format!("{}-{}", self.kernel_version(), self.kernel_flavour))
83    }
84}
85
86/// Linux banner.
87#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
88pub struct LinuxBanner {
89    /// `UTS_RELEASE`, e.g. `6.8.0-40-generic`.
90    pub uts_release: String,
91
92    /// User part of the compile host, before the `@`.
93    pub linux_compile_by: String,
94
95    /// Host part of the compile host, after the `@`.
96    pub linux_compile_host: String,
97
98    /// Compiler string, e.g. `x86_64-linux-gnu-gcc-12 ... 12.3.0`.
99    pub linux_compiler: String,
100
101    /// `UTS_VERSION`, the portion after `#`.
102    pub uts_version: String,
103
104    /// Distribution-specific signature parsed from `uts_version`, if recognized.
105    pub version_signature: Option<LinuxVersionSignature>,
106}
107
108// root/debian/rules.d/2-binary-arch.mk (ubuntu CONFIG_VERSION_SIGNATURE)
109
110impl std::str::FromStr for LinuxBanner {
111    type Err = DownloaderError;
112
113    fn from_str(banner: &str) -> Result<Self, Self::Err> {
114        //
115        // Linux version 6.8.0-40-generic
116        // (buildd@lcy02-amd64-078)
117        // (x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0, GNU ld (GNU Binutils for Ubuntu) 2.38)
118        // #40~22.04.3-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 30 17:30:19 UTC 2 (Ubuntu 6.8.0-40.40~22.04.3-generic 6.8.12)
119        //
120
121        static LINUX_VERSION_REGEX: LazyLock<Regex> = LazyLock::new(|| {
122            Regex::new(concat!(
123                r"Linux version (?<UTS_RELEASE>[0-9]+\.[0-9]+\.[0-9]+[^ ]*) ",
124                r"\((?<LINUX_COMPILE_BY>[^@]*)@(?<LINUX_COMPILE_HOST>[^)]*)\) ",
125                r"\((?<LINUX_COMPILER>.*)\) ",
126                r"#(?<UTS_VERSION>.*)"
127            ))
128            .unwrap()
129        });
130
131        let captures = LINUX_VERSION_REGEX
132            .captures(banner)
133            .ok_or(DownloaderError::InvalidBanner)?;
134
135        let version_signature = try_parse_ubuntu_signature(&captures["UTS_VERSION"]);
136
137        Ok(Self {
138            uts_release: captures["UTS_RELEASE"].to_string(),
139            linux_compile_by: captures["LINUX_COMPILE_BY"].to_string(),
140            linux_compile_host: captures["LINUX_COMPILE_HOST"].to_string(),
141            linux_compiler: captures["LINUX_COMPILER"].to_string(),
142            uts_version: captures["UTS_VERSION"].to_string(),
143            version_signature,
144        })
145    }
146}
147
148fn try_parse_ubuntu_signature(uts_version: &str) -> Option<LinuxVersionSignature> {
149    //
150    // (Ubuntu 6.8.0-40.40~22.04.3-generic 6.8.12)
151    //
152
153    static UBUNTU_VERSION_REGEX: LazyLock<Regex> = LazyLock::new(|| {
154        Regex::new(concat!(
155            r"\(Ubuntu ",
156            r"(?<UBUNTU_RELEASE>.*)-(?<UBUNTU_REVISION>.*)-(?<UBUNTU_KERNEL_FLAVOUR>.*) ",
157            r"(?<UBUNTU_MAINLINE_KERNEL_VERSION>.*)\)"
158        ))
159        .unwrap()
160    });
161
162    let captures = UBUNTU_VERSION_REGEX.captures(uts_version)?;
163
164    Some(LinuxVersionSignature::Ubuntu(UbuntuVersionSignature {
165        release: captures["UBUNTU_RELEASE"].into(),
166        revision: captures["UBUNTU_REVISION"].into(),
167        kernel_flavour: captures["UBUNTU_KERNEL_FLAVOUR"].into(),
168        mainline_kernel_version: captures["UBUNTU_MAINLINE_KERNEL_VERSION"].into(),
169    }))
170}