Skip to main content

ferrix_app/
dmi.rs

1/* dmi.rs
2 *
3 * Copyright 2025 Michail Krasnov <mskrasnov07@ya.ru>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21//! DMI Service Provider
22
23use anyhow::Result;
24use async_std::task;
25use ferrix_lib::dmi::{Baseboard, Bios, Chassis, Processor};
26use serde::{Deserialize, Serialize};
27use std::{env, path::Path, process::Command, sync::LazyLock};
28
29use crate::load_state::{LoadState, ToLoadState};
30
31static PATH: LazyLock<Vec<String>> = LazyLock::new(|| path());
32
33fn path() -> Vec<String> {
34    env::var("PATH")
35        .ok()
36        .and_then(|pth| Some(pth.split(':').map(|p| p.to_string()).collect::<Vec<_>>()))
37        .unwrap_or(vec!["/usr/bin".to_string()])
38}
39
40fn auth_app() -> Option<String> {
41    let apps = ["pkexec", "gksudo"];
42    let bin_dirs = &PATH;
43
44    for a in apps {
45        for b in bin_dirs.as_slice() {
46            let s = Path::new(b).join(a);
47            if s.exists() {
48                return Some(s.display().to_string());
49            }
50        }
51    }
52
53    None
54}
55
56fn fx_polkit_app() -> Option<String> {
57    let app = "ferrix-polkit";
58    let bin_dirs = &PATH;
59    for b in bin_dirs.as_slice() {
60        let s = Path::new(b).join(app);
61        if s.exists() {
62            return Some(s.display().to_string());
63        }
64    }
65    None
66}
67
68pub async fn get_dmi_data() -> LoadState<DMIData> {
69    let auth_app = match auth_app() {
70        Some(auth_app) => auth_app,
71        None => return LoadState::Error("No authentication software found".to_string()),
72    };
73    let fx_app = match fx_polkit_app() {
74        Some(fx_app) => fx_app,
75        None => return LoadState::Error("No `ferrix-app` program found".to_string()),
76    };
77
78    let output =
79        task::spawn_blocking(move || Command::new(auth_app).arg(fx_app).arg("dmi").output()).await;
80
81    if let Err(why) = output {
82        return LoadState::Error(why.to_string());
83    }
84    let output = output.unwrap();
85    if output.status.code().unwrap_or(0) != 0 {
86        return LoadState::Error(format!(
87            "[ferrix-polkit] Non-zero return code:\n{}",
88            String::from_utf8_lossy(&output.stderr)
89        ));
90    }
91
92    let json_str = String::from_utf8_lossy(&output.stdout);
93    let json_data = DMIData::from_json(&json_str);
94
95    match json_data {
96        Ok(data) => LoadState::Loaded(data),
97        Err(why) => LoadState::Error(why.to_string()),
98    }
99}
100
101#[derive(Debug, Serialize, Deserialize, Clone)]
102pub struct DMIData {
103    pub bios: LoadState<Bios>,
104    pub baseboard: LoadState<Baseboard>,
105    pub chassis: LoadState<Chassis>,
106    pub processor: LoadState<Processor>,
107}
108
109impl DMIData {
110    pub fn new() -> Self {
111        Self {
112            bios: Bios::new().to_load_state(),
113            baseboard: Baseboard::new().to_load_state(),
114            chassis: Chassis::new().to_load_state(),
115            processor: Processor::new().to_load_state(),
116        }
117    }
118
119    pub fn to_json(&self) -> Result<String> {
120        let contents = serde_json::to_string(&self)?;
121        Ok(contents)
122    }
123
124    pub fn from_json(json: &str) -> Result<Self> {
125        Ok(serde_json::from_str(json)?)
126    }
127}