Skip to main content

edera_check/checkers/postinstall/
kernel.rs

1use crate::helpers::{
2    CheckGroup, CheckGroupCategory, CheckGroupResult, CheckResult,
3    CheckResultValue::{Errored, Failed, Passed},
4    host_executor::HostNamespaceExecutor,
5    kernel as khelper,
6};
7
8use async_trait::async_trait;
9use futures::{FutureExt, future::join_all};
10use procfs::sys::kernel;
11
12const GROUP_IDENTIFIER: &str = "kernel";
13const NAME: &str = "Postinstall Kernel Checks";
14
15// Stuff we expect to be available under an Edera host kernel,
16// or a supported BYO kernel.
17const REQUIRED_MODULES: &[&str] = &[
18    "nf_tables",
19    "xen_evtchn",
20    "xen-privcmd",
21    "xen-netback",
22    "xen-pciback",
23    "xen-blkback",
24    "xen-gntdev",
25    "xen-gntalloc",
26];
27
28const KVER_FLOOR_PATCH: u16 = 0;
29const KVER_FLOOR_MINOR: u8 = 15;
30const KVER_FLOOR_MAJOR: u8 = 5;
31
32pub struct PostinstallKernelChecks {
33    host_executor: HostNamespaceExecutor,
34}
35
36impl PostinstallKernelChecks {
37    pub fn new(host_executor: HostNamespaceExecutor) -> Self {
38        PostinstallKernelChecks { host_executor }
39    }
40
41    /// Run all the checkers asynchronously, then
42    /// join and collect the results.
43    pub async fn run_all(&self) -> CheckGroupResult {
44        let results = join_all([self.has_modules().boxed(), self.version_is_good().boxed()]).await;
45
46        let mut group_result = Passed;
47        for res in results.iter() {
48            // Set group result to Failed if we failed and aren't already in an Errored state
49            if !matches!(group_result, Errored(_)) && matches!(res.result, Failed(_)) {
50                group_result = Failed("".into());
51            }
52
53            if matches!(res.result, Errored(_)) {
54                group_result = Errored("".into());
55            }
56        }
57
58        CheckGroupResult {
59            name: NAME.to_string(),
60            result: group_result,
61            results,
62        }
63    }
64
65    /// Checks that the running kernel is at least version 5.15.0.
66    ///
67    /// Manual equivalent:
68    /// ```sh
69    /// uname -r  # must be >= 5.15.0
70    /// ```
71    pub async fn version_is_good(&self) -> CheckResult {
72        let name = String::from("Host Kernel Version Is Good");
73        let floor = kernel::Version::new(KVER_FLOOR_MAJOR, KVER_FLOOR_MINOR, KVER_FLOOR_PATCH);
74
75        let passed = khelper::host_kver_above_floor(&self.host_executor, floor).await;
76
77        match passed {
78            Err(e) => CheckResult::new(&name, Errored(e.to_string())),
79            Ok(true) => CheckResult::new(&name, Passed),
80            Ok(false) => CheckResult::new(
81                &name,
82                Failed(String::from("current kernel version is unsupported")),
83            ),
84        }
85    }
86
87    /// Checks that all required Xen and networking modules are available as built-in,
88    /// currently loaded, or loadable (present in `modules.dep`) for the running kernel.
89    ///
90    /// Manual equivalent:
91    /// ```sh
92    /// KV=$(uname -r)
93    /// for mod in nf_tables xen_evtchn xen-privcmd xen-netback xen-pciback xen-blkback xen-gntdev xen-gntalloc; do
94    ///   grep -q "$mod" /lib/modules/$KV/modules.builtin \
95    ///     || grep -q "^${mod} " /proc/modules \
96    ///     || grep -q "$mod" /lib/modules/$KV/modules.dep \
97    ///     && echo "$mod: OK" || echo "$mod: MISSING"
98    /// done
99    /// ```
100    pub async fn has_modules(&self) -> CheckResult {
101        let name = String::from("Host Has Necessary Modules");
102
103        let required_modules: Vec<String> =
104            REQUIRED_MODULES.iter().map(|s| s.to_string()).collect();
105
106        // Search builtin modules
107        let remaining = match khelper::find_builtins(&self.host_executor, &required_modules).await {
108            Ok(r) => r,
109            Err(e) => {
110                return CheckResult::new(&name, Errored(format!("getting kernel builtins {e}")));
111            }
112        };
113
114        // Search loaded modules
115        let remaining = match khelper::find_loaded(&self.host_executor, &remaining).await {
116            Ok(r) => r,
117            Err(e) => {
118                return CheckResult::new(&name, Errored(format!("getting kernel modules {e}")));
119            }
120        };
121
122        // Search loadable modules
123        let remaining = match khelper::find_loadable(&self.host_executor, &remaining).await {
124            Ok(r) => r,
125            Err(e) => {
126                return CheckResult::new(&name, Errored(format!("getting kernel modules {e}")));
127            }
128        };
129        if !remaining.is_empty() {
130            return CheckResult::new(&name, Failed(format!("missing {:?}", remaining)));
131        }
132
133        CheckResult::new(&name, Passed)
134    }
135}
136
137#[async_trait]
138impl CheckGroup for PostinstallKernelChecks {
139    fn id(&self) -> &str {
140        GROUP_IDENTIFIER
141    }
142
143    fn name(&self) -> &str {
144        NAME
145    }
146
147    fn description(&self) -> &str {
148        "Postinstall kernel validation checks"
149    }
150
151    async fn run(&self) -> CheckGroupResult {
152        self.run_all().await
153    }
154
155    fn category(&self) -> CheckGroupCategory {
156        CheckGroupCategory::Required
157    }
158}