1use std::cmp;
7use std::collections::HashSet;
8use std::str::FromStr;
9
10use bitflags::bitflags;
11
12use crate::{ProcError, ProcResult};
13
14#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
16pub struct Version {
17 pub major: u8,
18 pub minor: u8,
19 pub patch: u16,
20}
21
22impl Version {
23 pub fn new(major: u8, minor: u8, patch: u16) -> Version {
24 Version { major, minor, patch }
25 }
26
27 #[allow(clippy::should_implement_trait)]
41 pub fn from_str(s: &str) -> Result<Self, &'static str> {
42 let pos = s.find(|c: char| c != '.' && !c.is_ascii_digit());
43 let kernel = if let Some(pos) = pos {
44 let (s, _) = s.split_at(pos);
45 s
46 } else {
47 s
48 };
49 let mut kernel_split = kernel.split('.');
50
51 let major = kernel_split.next().ok_or("Missing major version component")?;
52 let minor = kernel_split.next().ok_or("Missing minor version component")?;
53 let patch = kernel_split.next().ok_or("Missing patch version component")?;
54
55 let major = major.parse().map_err(|_| "Failed to parse major version")?;
56 let minor = minor.parse().map_err(|_| "Failed to parse minor version")?;
57 let patch = patch.parse().map_err(|_| "Failed to parse patch version")?;
58
59 Ok(Version { major, minor, patch })
60 }
61}
62
63impl FromStr for Version {
64 type Err = &'static str;
65
66 fn from_str(s: &str) -> Result<Self, Self::Err> {
80 Version::from_str(s)
81 }
82}
83
84impl cmp::Ord for Version {
85 fn cmp(&self, other: &Self) -> cmp::Ordering {
86 match self.major.cmp(&other.major) {
87 cmp::Ordering::Equal => match self.minor.cmp(&other.minor) {
88 cmp::Ordering::Equal => self.patch.cmp(&other.patch),
89 x => x,
90 },
91 x => x,
92 }
93 }
94}
95
96impl cmp::PartialOrd for Version {
97 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
98 Some(self.cmp(other))
99 }
100}
101
102#[derive(Debug, Clone, Eq, PartialEq)]
104pub struct Type {
105 pub sysname: String,
106}
107
108impl Type {
109 pub fn new(sysname: String) -> Type {
110 Type { sysname }
111 }
112}
113
114impl FromStr for Type {
115 type Err = &'static str;
116
117 fn from_str(s: &str) -> Result<Self, Self::Err> {
121 Ok(Type::new(s.to_string()))
122 }
123}
124
125#[derive(Debug, Clone, Eq, PartialEq)]
127pub struct BuildInfo {
128 pub version: String,
129 pub flags: HashSet<String>,
130 pub extra: String,
134}
135
136impl BuildInfo {
137 pub fn new(version: &str, flags: HashSet<String>, extra: String) -> BuildInfo {
138 BuildInfo {
139 version: version.to_string(),
140 flags,
141 extra,
142 }
143 }
144
145 pub fn smp(&self) -> bool {
147 self.flags.contains("SMP")
148 }
149
150 pub fn preempt(&self) -> bool {
152 self.flags.contains("PREEMPT")
153 }
154
155 pub fn preemptrt(&self) -> bool {
157 self.flags.contains("PREEMPTRT")
158 }
159
160 pub fn version_number(&self) -> ProcResult<u32> {
164 let mut version_str = String::new();
165 for c in self.version.chars() {
166 if c.is_ascii_digit() {
167 version_str.push(c);
168 } else {
169 break;
170 }
171 }
172 let version_number: u32 = version_str.parse().map_err(|_| "Failed to parse version number")?;
173 Ok(version_number)
174 }
175
176 #[cfg(feature = "chrono")]
180 pub fn extra_date(&self) -> ProcResult<chrono::DateTime<chrono::Local>> {
181 if let Ok(dt) =
182 chrono::DateTime::parse_from_str(&format!("{} +0000", &self.extra), "%a %b %d %H:%M:%S UTC %Y %z")
183 {
184 return Ok(dt.with_timezone(&chrono::Local));
185 }
186 if let Ok(dt) = chrono::DateTime::parse_from_str(&self.extra, "%a, %d %b %Y %H:%M:%S %z") {
187 return Ok(dt.with_timezone(&chrono::Local));
188 }
189 Err(ProcError::Other("Failed to parse extra field to date".to_string()))
190 }
191}
192
193impl FromStr for BuildInfo {
194 type Err = &'static str;
195
196 fn from_str(s: &str) -> Result<Self, Self::Err> {
198 let mut version = String::new();
199 let mut flags: HashSet<String> = HashSet::new();
200 let mut extra: String = String::new();
201
202 let mut splited = s.split(' ');
203 let version_str = splited.next();
204 if let Some(version_str) = version_str {
205 if let Some(stripped) = version_str.strip_prefix('#') {
206 version.push_str(stripped);
207 } else {
208 return Err("Failed to parse kernel build version");
209 }
210 } else {
211 return Err("Failed to parse kernel build version");
212 }
213
214 for s in &mut splited {
215 if s.chars().all(char::is_uppercase) {
216 flags.insert(s.to_string());
217 } else {
218 extra.push_str(s);
219 extra.push(' ');
220 break;
221 }
222 }
223 let remains: Vec<&str> = splited.collect();
224 extra.push_str(&remains.join(" "));
225
226 Ok(BuildInfo { version, flags, extra })
227 }
228}
229
230#[derive(Debug, PartialEq, Eq, Copy, Clone)]
231pub struct SemaphoreLimits {
233 pub semmsl: u64,
235 pub semmns: u64,
237 pub semopm: u64,
239 pub semmni: u64,
241}
242
243impl SemaphoreLimits {
244 fn from_str(s: &str) -> Result<Self, &'static str> {
245 let mut s = s.split_ascii_whitespace();
246
247 let semmsl = s.next().ok_or("Missing SEMMSL")?;
248 let semmns = s.next().ok_or("Missing SEMMNS")?;
249 let semopm = s.next().ok_or("Missing SEMOPM")?;
250 let semmni = s.next().ok_or("Missing SEMMNI")?;
251
252 let semmsl = semmsl.parse().map_err(|_| "Failed to parse SEMMSL")?;
253 let semmns = semmns.parse().map_err(|_| "Failed to parse SEMMNS")?;
254 let semopm = semopm.parse().map_err(|_| "Failed to parse SEMOPM")?;
255 let semmni = semmni.parse().map_err(|_| "Failed to parse SEMMNI")?;
256
257 Ok(SemaphoreLimits {
258 semmsl,
259 semmns,
260 semopm,
261 semmni,
262 })
263 }
264}
265
266impl FromStr for SemaphoreLimits {
267 type Err = &'static str;
268
269 fn from_str(s: &str) -> Result<Self, Self::Err> {
270 SemaphoreLimits::from_str(s)
271 }
272}
273
274bitflags! {
275 #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
277 pub struct AllowedFunctions : u16 {
278 const ENABLE_CONTROL_LOG_LEVEL = 2;
280 const ENABLE_CONTROL_KEYBOARD = 4;
282 const ENABLE_DEBUGGING_DUMPS = 8;
284 const ENABLE_SYNC_COMMAND = 16;
286 const ENABLE_REMOUNT_READ_ONLY = 32;
288 const ENABLE_SIGNALING_PROCESSES = 64;
290 const ALLOW_REBOOT_POWEROFF = 128;
292 const ALLOW_NICING_REAL_TIME_TASKS = 256;
294 }
295}
296
297#[derive(Copy, Clone, Debug)]
299pub enum SysRq {
300 Disable,
302 Enable,
304 AllowedFunctions(AllowedFunctions),
306}
307
308impl SysRq {
309 pub fn to_number(self) -> u16 {
310 match self {
311 SysRq::Disable => 0,
312 SysRq::Enable => 1,
313 SysRq::AllowedFunctions(allowed) => allowed.bits(),
314 }
315 }
316
317 fn from_str(s: &str) -> ProcResult<Self> {
318 match s.parse::<u16>()? {
319 0 => Ok(SysRq::Disable),
320 1 => Ok(SysRq::Enable),
321 x => match AllowedFunctions::from_bits(x) {
322 Some(allowed) => Ok(SysRq::AllowedFunctions(allowed)),
323 None => Err("Invalid value".into()),
324 },
325 }
326 }
327}
328
329impl FromStr for SysRq {
330 type Err = ProcError;
331
332 fn from_str(s: &str) -> Result<Self, Self::Err> {
333 SysRq::from_str(s)
334 }
335}
336
337pub const THREADS_MIN: u32 = 20;
339pub const THREADS_MAX: u32 = 0x3fff_ffff;
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[test]
347 fn test_version() {
348 let a = Version::from_str("3.16.0-6-amd64").unwrap();
349 let b = Version::new(3, 16, 0);
350 assert_eq!(a, b);
351
352 let a = Version::from_str("3.16.0").unwrap();
353 let b = Version::new(3, 16, 0);
354 assert_eq!(a, b);
355
356 let a = Version::from_str("3.16.0_1").unwrap();
357 let b = Version::new(3, 16, 0);
358 assert_eq!(a, b);
359 }
360
361 #[test]
362 fn test_type() {
363 let a = Type::from_str("Linux").unwrap();
364 assert_eq!(a.sysname, "Linux");
365 }
366
367 #[test]
368 fn test_build_info() {
369 let a = BuildInfo::from_str("#1 SMP PREEMPT Thu Sep 30 15:29:01 UTC 2021").unwrap();
371 let mut flags: HashSet<String> = HashSet::new();
372 flags.insert("SMP".to_string());
373 flags.insert("PREEMPT".to_string());
374 assert_eq!(a.version, "1");
375 assert_eq!(a.version_number().unwrap(), 1);
376 assert_eq!(a.flags, flags);
377 assert!(a.smp());
378 assert!(a.preempt());
379 assert!(!a.preemptrt());
380 assert_eq!(a.extra, "Thu Sep 30 15:29:01 UTC 2021");
381 #[cfg(feature = "chrono")]
382 let _ = a.extra_date().unwrap();
383
384 let b = BuildInfo::from_str("#1 SMP PREEMPT Fri, 12 Nov 2021 19:22:10 +0000").unwrap();
386 assert_eq!(b.version, "1");
387 assert_eq!(b.version_number().unwrap(), 1);
388 assert_eq!(b.flags, flags);
389 assert_eq!(b.extra, "Fri, 12 Nov 2021 19:22:10 +0000");
390 assert!(b.smp());
391 assert!(b.preempt());
392 assert!(!b.preemptrt());
393 #[cfg(feature = "chrono")]
394 let _ = b.extra_date().unwrap();
395
396 let c = BuildInfo::from_str("#1 SMP Debian 5.10.46-4 (2021-08-03)").unwrap();
398 let mut flags: HashSet<String> = HashSet::new();
399 flags.insert("SMP".to_string());
400 assert_eq!(c.version, "1");
401 assert_eq!(c.version_number().unwrap(), 1);
402 assert_eq!(c.flags, flags);
403 assert_eq!(c.extra, "Debian 5.10.46-4 (2021-08-03)");
404 assert!(c.smp());
405 assert!(!c.preempt());
406 assert!(!c.preemptrt());
407 }
409
410 #[test]
411 fn test_semaphore_limits() {
412 let a = SemaphoreLimits::from_str("32000 1024000000 500 32000").unwrap();
414 let b = SemaphoreLimits {
415 semmsl: 32_000,
416 semmns: 1_024_000_000,
417 semopm: 500,
418 semmni: 32_000,
419 };
420 assert_eq!(a, b);
421
422 let a = SemaphoreLimits::from_str("1");
423 assert!(a.is_err() && a.err().unwrap() == "Missing SEMMNS");
424
425 let a = SemaphoreLimits::from_str("1 string 500 3200");
426 assert!(a.is_err() && a.err().unwrap() == "Failed to parse SEMMNS");
427 }
428}