1use crate::utils::read_to_string;
24use crate::{traits::*, utils::Size};
25use anyhow::{Result, anyhow};
26use serde::{Deserialize, Serialize};
27use std::env::{var, vars};
28
29#[derive(Debug, Serialize)]
32pub struct Sys {
33 pub machine_id: Option<String>,
35
36 pub timezone: Option<String>,
38
39 pub env_vars: Vec<(String, String)>,
41
42 pub uptime: Uptime,
44
45 pub loadavg: LoadAVG,
47
48 pub shells: Shells,
50
51 pub hostname: Option<HostName>,
53 }
56
57impl Sys {
58 pub fn new() -> Result<Self> {
59 Ok(Self {
60 machine_id: read_to_string("/etc/machine-id").ok(),
61 timezone: read_to_string("/etc/timezone").ok(),
62 env_vars: get_env_vars(),
63 uptime: Uptime::new()?,
64 loadavg: LoadAVG::new()?,
65 shells: get_shells()?,
66 hostname: get_hostname(),
67 })
69 }
70
71 pub fn update(&mut self) -> Result<()> {
72 self.uptime = Uptime::new()?;
73 self.loadavg = LoadAVG::new()?;
74 Ok(())
75 }
76}
77
78impl ToJson for Sys {}
79
80#[derive(Debug, Serialize, Clone)]
82pub struct Kernel {
83 pub uname: Option<String>, pub cmdline: Option<String>, pub arch: Option<String>, pub version: Option<String>, pub build_info: Option<String>, pub pid_max: u32, pub threads_max: u32, pub user_events_max: Option<u32>, pub enthropy_avail: Option<u16>, }
110
111impl Kernel {
112 pub fn new() -> Result<Self> {
113 Ok(Self {
114 uname: read_to_string("/proc/version").ok(),
115 cmdline: read_to_string("/proc/cmdline").ok(),
116 arch: read_to_string("/proc/sys/kernel/arch").ok(),
117 version: read_to_string("/proc/sys/kernel/osrelease").ok(),
118 build_info: read_to_string("/proc/sys/kernel/version").ok(),
119 pid_max: read_to_string("/proc/sys/kernel/pid_max")?.parse()?,
120 threads_max: read_to_string("/proc/sys/kernel/threads-max")?.parse()?,
121 user_events_max: match read_to_string("/proc/sys/kernel/user_events_max").ok() {
122 Some(uem) => uem.parse().ok(),
123 None => None,
124 },
125 enthropy_avail: match read_to_string("/proc/sys/kernel/random/entropy_avail").ok() {
126 Some(ea) => ea.parse().ok(),
127 None => None,
128 },
129 })
130 }
131}
132
133impl ToJson for Kernel {}
134
135#[derive(Debug, Serialize, Default, Clone)]
139pub struct OsRelease {
140 pub name: String,
144
145 pub id: Option<String>,
148
149 pub id_like: Option<String>,
152
153 pub pretty_name: Option<String>,
157
158 pub cpe_name: Option<String>,
160
161 pub variant: Option<String>,
164
165 pub variant_id: Option<String>,
168
169 pub version: Option<String>,
173
174 pub version_id: Option<String>,
177
178 pub version_codename: Option<String>,
181
182 pub build_id: Option<String>,
185
186 pub image_id: Option<String>,
191
192 pub image_version: Option<String>,
196
197 pub home_url: Option<String>,
199
200 pub documentation_url: Option<String>,
202
203 pub support_url: Option<String>,
205
206 pub bug_report_url: Option<String>,
208
209 pub privacy_policy_url: Option<String>,
211
212 pub logo: Option<String>,
215
216 pub default_hostname: Option<String>,
219
220 pub sysext_level: Option<String>,
225}
226
227impl OsRelease {
228 pub fn new() -> Result<Self> {
229 let chunks = get_chunks_osrelease(read_to_string("/etc/os-release")?);
230 let mut osr = Self::default();
231 for chunk in chunks {
232 parse_osrelease(&mut osr, chunk);
233 }
234 Ok(osr)
235 }
236}
237
238impl ToJson for OsRelease {}
239
240fn get_chunks_osrelease(contents: String) -> Vec<(Option<String>, Option<String>)> {
241 contents
242 .lines()
243 .map(|item| {
244 let mut items = item.split('=').map(sanitize_str);
245 (items.next(), items.next())
246 })
247 .collect::<Vec<_>>()
248}
249
250fn parse_osrelease(osr: &mut OsRelease, chunk: (Option<String>, Option<String>)) {
251 match chunk {
252 (Some(key), Some(val)) => {
253 let key = &key as &str;
254 match key {
255 "NAME" => osr.name = val.to_string(),
256 "ID" => osr.id = Some(val.to_string()),
257 "ID_LIKE" => osr.id_like = Some(val.to_string()),
258 "PRETTY_NAME" => osr.pretty_name = Some(val.to_string()),
259 "CPE_NAME" => osr.cpe_name = Some(val.to_string()),
260 "VARIANT" => osr.variant = Some(val.to_string()),
261 "VARIANT_ID" => osr.variant_id = Some(val.to_string()),
262 "VERSION" => osr.version = Some(val.to_string()),
263 "VERSION_CODENAME" => osr.version_codename = Some(val.to_string()),
264 "VERSION_ID" => osr.version_id = Some(val.to_string()),
265 "BUILD_ID" => osr.build_id = Some(val.to_string()),
266 "IMAGE_ID" => osr.image_id = Some(val.to_string()),
267 "IMAGE_VERSION" => osr.image_version = Some(val.to_string()),
268 "HOME_URL" => osr.home_url = Some(val.to_string()),
269 "DOCUMENTATION_URL" => osr.documentation_url = Some(val.to_string()),
270 "SUPPORT_URL" => osr.support_url = Some(val.to_string()),
271 "BUG_REPORT_URL" => osr.bug_report_url = Some(val.to_string()),
272 "PRIVACY_POLICY_URL" => osr.privacy_policy_url = Some(val.to_string()),
273 "LOGO" => osr.logo = Some(val.to_string()),
274 "DEFAULT_HOSTNAME" => osr.default_hostname = Some(val.to_string()),
275 "SYSEXT_LEVEL" => osr.sysext_level = Some(val.to_string()),
276 _ => {}
277 }
278 }
279 _ => {}
280 }
281}
282
283#[derive(Debug, Serialize, Clone)]
285pub struct Uptime(
286 pub f32,
288 pub f32,
290);
291
292impl Uptime {
293 pub fn new() -> Result<Self> {
294 let data = read_to_string("/proc/uptime")?;
295 let mut chunks = data.split_whitespace();
296 match (chunks.next(), chunks.next()) {
297 (Some(a), Some(b)) => Ok(Self(a.parse()?, b.parse()?)),
298 _ => Err(anyhow!("`/proc/uptime` file format is incorrect!")),
299 }
300 }
301}
302
303impl ToPlainText for Uptime {
304 fn to_plain(&self) -> String {
305 format!(
306 "\nUptime: {} seconds; downtime: {} seconds\n",
307 self.0, self.1
308 )
309 }
310}
311
312#[derive(Debug, Serialize, Clone)]
314pub struct LoadAVG(
315 pub f32,
317 pub f32,
319 pub f32,
321);
322
323impl LoadAVG {
324 pub fn new() -> Result<Self> {
325 let data = read_to_string("/proc/loadavg")?;
326 let mut chunks = data.split_whitespace();
327 match (chunks.next(), chunks.next(), chunks.next()) {
328 (Some(a), Some(b), Some(c)) => Ok(Self(a.parse()?, b.parse()?, c.parse()?)),
329 _ => Err(anyhow!("`/proc/loadavg` file format is incorrect!")),
330 }
331 }
332}
333
334impl ToPlainText for LoadAVG {
335 fn to_plain(&self) -> String {
336 let mut s = format!("\nAverage system load:\n");
337 s += &print_val("1 minute", &self.0);
338 s += &print_val("5 minutes", &self.1);
339 s += &print_val("15 minutes", &self.2);
340
341 s
342 }
343}
344
345#[derive(Debug, Serialize, Clone)]
347pub struct Users {
348 pub users: Vec<User>,
349}
350
351impl ToJson for Users {}
352
353impl Users {
354 pub fn new() -> Result<Self> {
355 let mut users = vec![];
356 for user in read_to_string("/etc/passwd")?.lines() {
357 match User::try_from(user) {
358 Ok(user) => users.push(user),
359 Err(_) => continue,
360 }
361 }
362
363 Ok(Self { users })
364 }
365}
366
367#[derive(Debug, Serialize, Clone)]
369pub struct User {
370 pub name: String,
372
373 pub uid: u32,
382
383 pub gid: u32,
386
387 pub gecos: Option<String>,
396
397 pub home_dir: String,
399
400 pub login_shell: String,
404}
405
406impl TryFrom<&str> for User {
407 type Error = anyhow::Error;
408
409 fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
410 let chunks = value
411 .trim()
412 .split(':')
413 .map(sanitize_str)
414 .collect::<Vec<_>>();
415 if chunks.len() != 7 {
416 return Err(anyhow!("Field \"{value}\" is incorrect user entry"));
417 }
418
419 Ok(Self {
420 name: sanitize_str(&chunks[0]),
421 uid: chunks[2].parse()?,
422 gid: chunks[3].parse()?,
423 gecos: match chunks[4].is_empty() {
424 true => None,
425 false => Some(sanitize_str(&chunks[4])),
426 },
427 home_dir: sanitize_str(&chunks[5]),
428 login_shell: sanitize_str(&chunks[6]),
429 })
430 }
431}
432
433impl ToPlainText for User {
434 fn to_plain(&self) -> String {
435 let mut s = format!("\nUser '{}':\n", &self.name);
436 s += &print_val("User ID", &self.uid);
437 s += &print_val("Group ID", &self.gid);
438 s += &print_opt_val("GECOS", &self.gecos);
439 s += &print_val("Home directory", &self.home_dir);
440 s += &print_val("Login shell", &self.login_shell);
441
442 s
443 }
444}
445
446#[derive(Debug, Serialize, Clone)]
448pub struct Groups {
449 pub groups: Vec<Group>,
450}
451
452impl ToJson for Groups {}
453
454impl Groups {
455 pub fn new() -> Result<Self> {
456 let mut groups = vec![];
457 for group in read_to_string("/etc/group")?.lines() {
458 match Group::try_from(group) {
459 Ok(group) => groups.push(group),
460 Err(_) => continue,
461 }
462 }
463 Ok(Self { groups })
464 }
465}
466
467#[derive(Debug, Serialize, Clone)]
469pub struct Group {
470 pub name: String,
472
473 pub gid: u32,
475
476 pub users: Vec<String>,
479}
480
481impl TryFrom<&str> for Group {
482 type Error = anyhow::Error;
483
484 fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
485 let chunks = value
486 .trim()
487 .split(':')
488 .map(sanitize_str)
489 .collect::<Vec<_>>();
490 if chunks.len() != 4 {
491 return Err(anyhow!("Field \"{value}\" is incorrect group entry"));
492 }
493
494 Ok(Self {
495 name: chunks[0].to_string(),
496 gid: chunks[2].parse()?,
497 users: {
498 let mut users = vec![];
499 for user in chunks[3].split(',') {
500 if !user.is_empty() {
501 users.push(user.to_string());
502 }
503 }
504 users
505 },
506 })
507 }
508}
509
510pub type Shells = Vec<String>;
512
513fn get_shells() -> Result<Shells> {
514 let mut shells = vec![];
515 for shell in read_to_string("/etc/shells")?
516 .lines()
517 .filter(|line| !line.is_empty() && !line.starts_with('#'))
518 {
519 shells.push(shell.to_string());
520 }
521 Ok(shells)
522}
523
524pub type HostName = String;
526
527pub fn get_hostname() -> Option<HostName> {
528 match read_to_string("/etc/hostname") {
529 Ok(s) => Some(sanitize_str(&s)),
530 Err(_) => None,
531 }
532}
533
534#[derive(Debug, Serialize, Clone)]
536pub struct Locale {}
537
538fn sanitize_str(s: &str) -> String {
539 s.trim().replace('"', "").replace('\'', "")
540}
541
542#[derive(Debug, Serialize, Deserialize, Clone)]
544pub struct KModules {
545 pub modules: Vec<Module>,
546}
547
548impl KModules {
549 pub fn new() -> Result<Self> {
550 let contents = read_to_string("/proc/modules")?;
551 let contents = contents.lines();
552 let mut modules = Vec::new();
553
554 for s in contents {
555 modules.push(Module::try_from(s)?);
556 }
557
558 Ok(Self { modules })
559 }
560}
561
562#[derive(Debug, Serialize, Deserialize, Clone)]
563pub struct Module {
564 pub name: String,
566
567 pub size: Size,
569
570 pub instances: usize,
572
573 pub dependencies: String,
576
577 pub state: String,
579
580 pub memory_addrs: String,
584}
585
586impl TryFrom<&str> for Module {
587 type Error = anyhow::Error;
588 fn try_from(value: &str) -> Result<Self> {
589 let mut ch = value.split_whitespace();
590 match (
591 ch.next(),
592 ch.next(),
593 ch.next(),
594 ch.next(),
595 ch.next(),
596 ch.next(),
597 ) {
598 (
599 Some(name),
600 Some(size),
601 Some(instances),
602 Some(dependencies),
603 Some(state),
604 Some(memory_addrs),
605 ) => {
606 let size = size.parse::<usize>().map_err(|err| anyhow!("{err}"))?;
607 let instances = instances.parse::<usize>().map_err(|err| anyhow!("{err}"))?;
608
609 Ok(Self {
610 name: name.to_string(),
611 size: Size::B(size),
612 instances,
613 dependencies: dependencies.to_string(),
614 state: state.to_string(),
615 memory_addrs: memory_addrs.to_string(),
616 })
617 }
618 _ => Err(anyhow!("Unknown field: \"{value}\"")),
619 }
620 }
621}
622
623pub fn get_current_desktop() -> Option<String> {
624 var("XDG_CURRENT_DESKTOP").ok()
625}
626
627pub fn get_lang() -> Option<String> {
628 let lang = var("LANG").ok();
629 let lc_all = var("LC_ALL").ok();
630 if lang.is_some() { lang } else { lc_all }
631}
632
633pub fn get_env_vars() -> Vec<(String, String)> {
634 let mut vars = vars().collect::<Vec<(String, String)>>();
635 vars.sort_by_key(|v| v.0.clone());
636 vars
637}