1use std::sync::OnceLock;
24
25use rustix::system::uname;
26use thiserror::Error;
27
28use crate::landlock;
29use crate::seccomp;
30
31#[derive(Debug, Clone)]
33pub struct SystemInfo {
34 pub kernel_version: (u32, u32, u32),
35 pub landlock_abi: u32,
36 pub seccomp_enabled: bool,
37}
38
39#[derive(Debug, Clone, Error)]
41pub enum CheckError {
42 #[error("kernel version {}.{}.{} is too old, need at least {}.{}.{}", .found.0, .found.1, .found.2, .required.0, .required.1, .required.2)]
43 KernelTooOld {
44 required: (u32, u32, u32),
45 found: (u32, u32, u32),
46 },
47
48 #[error("landlock is not available")]
49 LandlockNotAvailable,
50
51 #[error("landlock ABI {found} is too old, need at least ABI {required}")]
52 LandlockAbiTooOld { required: u32, found: u32 },
53
54 #[error("seccomp is not available")]
55 SeccompNotAvailable,
56
57 #[error("failed to read kernel version")]
58 KernelVersionReadFailed,
59}
60
61const MIN_KERNEL_VERSION: (u32, u32, u32) = (6, 12, 0);
63const MIN_LANDLOCK_ABI: u32 = 5;
64
65static SYSTEM_INFO: OnceLock<Result<SystemInfo, CheckError>> = OnceLock::new();
66
67pub fn check() -> Result<&'static SystemInfo, &'static CheckError> {
72 SYSTEM_INFO.get_or_init(check_impl).as_ref()
73}
74
75fn check_impl() -> Result<SystemInfo, CheckError> {
76 let kernel_version = get_kernel_version()?;
77 if kernel_version < MIN_KERNEL_VERSION {
78 return Err(CheckError::KernelTooOld {
79 required: MIN_KERNEL_VERSION,
80 found: kernel_version,
81 });
82 }
83
84 let landlock_abi = landlock::landlock_abi_version().unwrap_or(0);
85 if landlock_abi == 0 {
86 return Err(CheckError::LandlockNotAvailable);
87 }
88 if landlock_abi < MIN_LANDLOCK_ABI {
89 return Err(CheckError::LandlockAbiTooOld {
90 required: MIN_LANDLOCK_ABI,
91 found: landlock_abi,
92 });
93 }
94
95 let seccomp_enabled = seccomp::seccomp_available();
96 if !seccomp_enabled {
97 return Err(CheckError::SeccompNotAvailable);
98 }
99
100 Ok(SystemInfo {
101 kernel_version,
102 landlock_abi,
103 seccomp_enabled,
104 })
105}
106
107fn get_kernel_version() -> Result<(u32, u32, u32), CheckError> {
108 let uts = uname();
109 let release = uts
110 .release()
111 .to_str()
112 .map_err(|_| CheckError::KernelVersionReadFailed)?;
113 parse_kernel_version(release)
114}
115
116fn parse_kernel_version(release: &str) -> Result<(u32, u32, u32), CheckError> {
117 let parts: Vec<&str> = release.split('.').collect();
118 if parts.len() < 2 {
119 return Err(CheckError::KernelVersionReadFailed);
120 }
121
122 let major = parts[0]
123 .parse::<u32>()
124 .map_err(|_| CheckError::KernelVersionReadFailed)?;
125
126 let minor = parts[1]
127 .parse::<u32>()
128 .map_err(|_| CheckError::KernelVersionReadFailed)?;
129
130 let patch = parts
132 .get(2)
133 .and_then(|p| p.split('-').next())
134 .and_then(|p| p.parse::<u32>().ok())
135 .unwrap_or(0);
136
137 Ok((major, minor, patch))
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_parse_kernel_version() {
146 assert_eq!(parse_kernel_version("5.15.0").unwrap(), (5, 15, 0));
147 assert_eq!(parse_kernel_version("6.1.0-generic").unwrap(), (6, 1, 0));
148 assert_eq!(
149 parse_kernel_version("5.4.0-150-generic").unwrap(),
150 (5, 4, 0)
151 );
152 assert_eq!(parse_kernel_version("6.12.0").unwrap(), (6, 12, 0));
153 }
154
155 #[test]
156 fn test_check() {
157 match check() {
158 Ok(info) => {
159 println!("Kernel version: {:?}", info.kernel_version);
160 println!("Landlock ABI: {}", info.landlock_abi);
161 println!("Seccomp enabled: {}", info.seccomp_enabled);
162 }
163 Err(e) => {
164 println!("System check failed: {e}");
165 }
166 }
167 }
168}