ferrix_lib/
init.rs

1/* init.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//! Get information about `systemd` services
22
23use std::fmt::Display;
24
25use anyhow::Result;
26use serde::Serialize;
27pub use zbus::{Connection, zvariant::OwnedObjectPath};
28use zbus_systemd::systemd1::ManagerProxy;
29
30use crate::traits::*;
31
32/// A structure containing information about `systemd` services
33#[derive(Debug, Serialize, Clone)]
34pub struct SystemdServices {
35    pub units: Vec<ServiceInfo>,
36}
37
38impl SystemdServices {
39    pub async fn new_from_connection(conn: &Connection) -> Result<Self> {
40        let mgr = ManagerProxy::new(conn).await?;
41        let mut units = vec![];
42
43        for unit in mgr.list_units().await? {
44            units.push(ServiceInfo::from(unit));
45        }
46
47        Ok(Self { units })
48    }
49}
50
51impl ToJson for SystemdServices {}
52
53impl ToPlainText for SystemdServices {
54    fn to_plain(&self) -> String {
55        let mut s = format!("\nSystemd services list:");
56        for service in &self.units {
57            s += &service.to_plain();
58        }
59
60        s
61    }
62}
63
64fn unescape(s: &str) -> String {
65    s.replace("\\x20", " ")
66        .replace("\\x5c", "\\")
67        .replace("\\x2f", "/")
68        .replace("\\x2d", "-")
69}
70
71type ServiceTuple = (
72    String,
73    String,
74    String,
75    String,
76    String,
77    String,
78    OwnedObjectPath,
79    u32,
80    String,
81    OwnedObjectPath,
82);
83
84#[derive(Debug, Serialize, Clone)]
85pub struct ServiceInfo {
86    /// Unit name (e.g. `hibernate.target`)
87    pub name: String,
88
89    /// Unit description (e.g. `System Hibernation`)
90    pub description: String,
91
92    /// Load state
93    pub load_state: LoadState,
94
95    /// Active state
96    pub active_state: ActiveState,
97
98    /// Work state
99    pub work_state: WorkState,
100
101    /// Daemon path
102    pub daemon_path: String,
103
104    /// Job ID
105    pub job_id: u32,
106
107    /// Unit type
108    pub unit_type: UnitType,
109}
110
111impl ToPlainText for ServiceInfo {
112    fn to_plain(&self) -> String {
113        let mut s = format!("\nService \"{}\"\n", &self.name);
114        s += &print_val("Description", &self.description);
115        s += &print_val("Load state", &self.load_state);
116        s += &print_val("Active state", &self.active_state);
117        s += &print_val("Work state", &self.work_state);
118        s += &print_val("Daemon path", &self.daemon_path);
119        s += &print_val("Job ID", &self.job_id);
120        s += &print_val("Unit type", &self.unit_type);
121
122        s
123    }
124}
125
126impl ToJson for ServiceInfo {}
127
128impl From<ServiceTuple> for ServiceInfo {
129    fn from(value: ServiceTuple) -> Self {
130        Self {
131            name: unescape(&value.0),
132            description: unescape(&value.1),
133            load_state: LoadState::from(&value.2),
134            active_state: ActiveState::from(&value.3),
135            work_state: WorkState::from(&value.4),
136            daemon_path: unescape(&value.5),
137            job_id: value.7,
138            unit_type: UnitType::from(&value.8),
139        }
140    }
141}
142
143#[derive(Debug, Serialize, Clone)]
144pub enum LoadState {
145    Loaded,
146    Stub,
147    Masked,
148    NotFound,
149    Unknown(String),
150}
151
152impl Display for LoadState {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        write!(
155            f,
156            "{}",
157            match self {
158                Self::Loaded => "Loaded",
159                Self::Stub => "Stub",
160                Self::Masked => "Masked",
161                Self::NotFound => "Not found",
162                _ => "Unknown",
163            }
164        )
165    }
166}
167
168impl From<&String> for LoadState {
169    fn from(value: &String) -> Self {
170        match value as &str {
171            "loaded" => Self::Loaded,
172            "stub" => Self::Stub,
173            "masked" => Self::Masked,
174            "not-found" => Self::NotFound,
175            _ => Self::Unknown(value.to_string()),
176        }
177    }
178}
179
180#[derive(Debug, Serialize, Clone)]
181pub enum ActiveState {
182    Active,
183    Inactive,
184    Activating,
185    Deactivating,
186    Failed,
187    Unknown(String),
188}
189
190impl Display for ActiveState {
191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192        write!(
193            f,
194            "{}",
195            match self {
196                Self::Active => "Active",
197                Self::Inactive => "Inactive",
198                Self::Activating => "Activating",
199                Self::Deactivating => "Deactivating",
200                Self::Failed => "Failed",
201                _ => "Unknown",
202            }
203        )
204    }
205}
206
207impl From<&String> for ActiveState {
208    fn from(value: &String) -> Self {
209        match value as &str {
210            "active" => Self::Active,
211            "inactive" => Self::Inactive,
212            "activating" => Self::Activating,
213            "deactivating" => Self::Deactivating,
214            "failed" => Self::Failed,
215            _ => Self::Unknown(value.to_string()),
216        }
217    }
218}
219
220#[derive(Debug, Serialize, Clone)]
221pub enum WorkState {
222    Active,
223    Running,
224    Exited,
225    Dead,
226    Mounted,
227    Mounting,
228    Plugged,
229    Listening,
230    Waiting,
231    Failed,
232    Unknown(String),
233}
234
235impl Display for WorkState {
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        write!(
238            f,
239            "{}",
240            match self {
241                Self::Active => "Active",
242                Self::Running => "Running",
243                Self::Exited => "Exited",
244                Self::Dead => "Dead",
245                Self::Mounted => "Mounted",
246                Self::Mounting => "Mounting",
247                Self::Plugged => "Plugged",
248                Self::Listening => "Listening",
249                Self::Waiting => "Waiting",
250                Self::Failed => "Failed",
251                _ => "Unknown",
252            }
253        )
254    }
255}
256
257impl From<&String> for WorkState {
258    fn from(value: &String) -> Self {
259        match value as &str {
260            "active" => Self::Active,
261            "running" => Self::Running,
262            "exited" => Self::Exited,
263            "dead" => Self::Dead,
264            "mounted" => Self::Mounted,
265            "mounting" => Self::Mounting,
266            "plugged" => Self::Plugged,
267            "listening" => Self::Listening,
268            "waiting" => Self::Waiting,
269            "failed" => Self::Failed,
270            _ => Self::Unknown(value.to_string()),
271        }
272    }
273}
274
275#[derive(Debug, Serialize, Clone)]
276pub enum UnitType {
277    Target,
278    Service,
279    Mount,
280    Swap,
281    None,
282    Unknown(String),
283}
284
285impl Display for UnitType {
286    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287        write!(
288            f,
289            "{}",
290            match self {
291                Self::Target => "Target",
292                Self::Service => "Service",
293                Self::Mount => "Mount",
294                Self::Swap => "Swap",
295                Self::None => "None-type",
296                _ => "Unknown",
297            }
298        )
299    }
300}
301
302impl From<&String> for UnitType {
303    fn from(value: &String) -> Self {
304        match value as &str {
305            "target" => Self::Target,
306            "service" => Self::Service,
307            "mount" => Self::Mount,
308            "swap" => Self::Swap,
309            "" => Self::None,
310            _ => Self::Unknown(value.to_string()),
311        }
312    }
313}