1use anyhow::{Result, anyhow};
24use libc::{CLOCK_MONOTONIC, CLOCK_REALTIME, clock_gettime, timespec};
25use serde::Serialize;
26use std::{fmt::Display, io::Error, mem::MaybeUninit};
27pub use zbus::{Connection, zvariant::OwnedObjectPath};
28use zbus_systemd::systemd1::ManagerProxy;
29
30use crate::traits::*;
31
32#[derive(Debug, Serialize, Clone)]
34pub struct SystemdServices {
35 pub timestamps: BootTimestamps,
36 pub units: Vec<ServiceInfo>,
37}
38
39impl SystemdServices {
40 pub async fn new_from_connection(conn: &Connection) -> Result<Self> {
41 let mgr = ManagerProxy::new(conn).await?;
42 let mut units = vec![];
43 for unit in mgr.list_units().await? {
44 units.push(ServiceInfo::from(unit));
45 }
46 let timestamps = BootTimestamps::get().await?;
47 Ok(Self { timestamps, 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
64#[derive(Debug, Clone, Copy, Serialize, Default)]
65pub struct BootTimestamps {
66 pub firmware: u64,
67 pub loader: u64,
68 pub kernel: u64,
69 pub initrd_timestamp_mono: u64,
70 pub userspace: u64,
71 pub finish_timestamp_mono: u64,
72 pub total: u64,
73}
74
75impl BootTimestamps {
76 pub async fn get<'a>() -> Result<Self> {
77 let conn = zbus::Connection::system().await?;
78 let mgr = ManagerProxy::new(&conn).await?;
79 Ok(Self {
80 firmware: mgr.cached_firmware_timestamp_monotonic()?.unwrap_or(0),
81 loader: mgr.loader_timestamp_monotonic().await?,
82 kernel: mgr.kernel_timestamp().await?,
83 initrd_timestamp_mono: mgr.init_rd_timestamp_monotonic().await?,
84 userspace: mgr.userspace_timestamp_monotonic().await?,
85 finish_timestamp_mono: mgr.finish_timestamp_monotonic().await?,
86 total: 0,
87 })
88 }
89
90 pub fn calc_boot_time(&mut self) -> Result<()> {
91 if self.userspace == 0 || self.finish_timestamp_mono == 0 {
92 return Err(anyhow!("Failed to get system load time: not enough data"));
93 }
94 let userspace_usec = self.finish_timestamp_mono.saturating_sub(self.userspace);
95 let kernel_usec = if self.kernel > 0 {
96 let now_rt = get_clock_time(CLOCK_REALTIME)?;
97 let now_mono = get_clock_time(CLOCK_MONOTONIC)?;
98 let offset = now_rt.saturating_sub(now_mono);
99
100 let kernel_timestamp_mono = self.kernel.saturating_sub(offset);
101 self.userspace.saturating_sub(kernel_timestamp_mono)
102 } else {
103 0
104 };
105 let loader_usec = if self.loader > 0 {
112 if self.initrd_timestamp_mono > 0 {
113 self.initrd_timestamp_mono.saturating_sub(self.loader)
114 } else {
115 self.userspace.saturating_sub(self.loader)
116 }
117 } else {
118 0
119 };
120 let firmware_usec = if self.loader > 0 {
121 self.loader.saturating_sub(self.firmware)
122 } else {
123 0
124 };
125
126 self.firmware = firmware_usec;
127 self.loader = loader_usec;
128 self.kernel = kernel_usec;
129 self.userspace = userspace_usec;
130
131 self.total = firmware_usec + loader_usec + kernel_usec + userspace_usec;
132 Ok(())
133 }
134}
135
136fn get_clock_time(clock_id: i32) -> Result<u64> {
137 let mut tp = MaybeUninit::<timespec>::uninit();
138 let res = unsafe { clock_gettime(clock_id, tp.as_mut_ptr()) };
139 if res == 0 {
140 let tp = unsafe { tp.assume_init() };
141 Ok(tp.tv_sec as u64 * 1_000_000 + (tp.tv_nsec as u64 / 1_000))
142 } else {
143 Err(anyhow!(
144 "Failed to get clock_time: {}",
145 Error::last_os_error()
146 ))
147 }
148}
149
150fn unescape(s: &str) -> String {
151 s.replace("\\x20", " ")
152 .replace("\\x5c", "\\")
153 .replace("\\x2f", "/")
154 .replace("\\x2d", "-")
155}
156
157type ServiceTuple = (
158 String,
159 String,
160 String,
161 String,
162 String,
163 String,
164 OwnedObjectPath,
165 u32,
166 String,
167 OwnedObjectPath,
168);
169
170#[derive(Debug, Serialize, Clone)]
171pub struct ServiceInfo {
172 pub name: String,
174
175 pub description: String,
177
178 pub load_state: LoadState,
180
181 pub active_state: ActiveState,
183
184 pub work_state: WorkState,
186
187 pub daemon_path: String,
189
190 pub job_id: u32,
192
193 pub unit_type: UnitType,
195}
196
197impl ToPlainText for ServiceInfo {
198 fn to_plain(&self) -> String {
199 let mut s = format!("\nService \"{}\"\n", &self.name);
200 s += &print_val("Description", &self.description);
201 s += &print_val("Load state", &self.load_state);
202 s += &print_val("Active state", &self.active_state);
203 s += &print_val("Work state", &self.work_state);
204 s += &print_val("Daemon path", &self.daemon_path);
205 s += &print_val("Job ID", &self.job_id);
206 s += &print_val("Unit type", &self.unit_type);
207
208 s
209 }
210}
211
212impl ToJson for ServiceInfo {}
213
214impl From<ServiceTuple> for ServiceInfo {
215 fn from(value: ServiceTuple) -> Self {
216 Self {
217 name: unescape(&value.0),
218 description: unescape(&value.1),
219 load_state: LoadState::from(&value.2),
220 active_state: ActiveState::from(&value.3),
221 work_state: WorkState::from(&value.4),
222 daemon_path: unescape(&value.5),
223 job_id: value.7,
224 unit_type: UnitType::from(&value.8),
225 }
226 }
227}
228
229#[derive(Debug, Serialize, Clone)]
230pub enum LoadState {
231 Loaded,
232 Stub,
233 Masked,
234 NotFound,
235 Unknown(String),
236}
237
238impl Display for LoadState {
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 write!(
241 f,
242 "{}",
243 match self {
244 Self::Loaded => "Loaded",
245 Self::Stub => "Stub",
246 Self::Masked => "Masked",
247 Self::NotFound => "Not found",
248 _ => "Unknown",
249 }
250 )
251 }
252}
253
254impl From<&String> for LoadState {
255 fn from(value: &String) -> Self {
256 match value as &str {
257 "loaded" => Self::Loaded,
258 "stub" => Self::Stub,
259 "masked" => Self::Masked,
260 "not-found" => Self::NotFound,
261 _ => Self::Unknown(value.to_string()),
262 }
263 }
264}
265
266#[derive(Debug, Serialize, Clone)]
267pub enum ActiveState {
268 Active,
269 Inactive,
270 Activating,
271 Deactivating,
272 Failed,
273 Unknown(String),
274}
275
276impl Display for ActiveState {
277 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278 write!(
279 f,
280 "{}",
281 match self {
282 Self::Active => "Active",
283 Self::Inactive => "Inactive",
284 Self::Activating => "Activating",
285 Self::Deactivating => "Deactivating",
286 Self::Failed => "Failed",
287 _ => "Unknown",
288 }
289 )
290 }
291}
292
293impl From<&String> for ActiveState {
294 fn from(value: &String) -> Self {
295 match value as &str {
296 "active" => Self::Active,
297 "inactive" => Self::Inactive,
298 "activating" => Self::Activating,
299 "deactivating" => Self::Deactivating,
300 "failed" => Self::Failed,
301 _ => Self::Unknown(value.to_string()),
302 }
303 }
304}
305
306#[derive(Debug, Serialize, Clone)]
307pub enum WorkState {
308 Active,
309 Running,
310 Exited,
311 Dead,
312 Mounted,
313 Mounting,
314 Plugged,
315 Listening,
316 Waiting,
317 Failed,
318 Unknown(String),
319}
320
321impl Display for WorkState {
322 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323 write!(
324 f,
325 "{}",
326 match self {
327 Self::Active => "Active",
328 Self::Running => "Running",
329 Self::Exited => "Exited",
330 Self::Dead => "Dead",
331 Self::Mounted => "Mounted",
332 Self::Mounting => "Mounting",
333 Self::Plugged => "Plugged",
334 Self::Listening => "Listening",
335 Self::Waiting => "Waiting",
336 Self::Failed => "Failed",
337 _ => "Unknown",
338 }
339 )
340 }
341}
342
343impl From<&String> for WorkState {
344 fn from(value: &String) -> Self {
345 match value as &str {
346 "active" => Self::Active,
347 "running" => Self::Running,
348 "exited" => Self::Exited,
349 "dead" => Self::Dead,
350 "mounted" => Self::Mounted,
351 "mounting" => Self::Mounting,
352 "plugged" => Self::Plugged,
353 "listening" => Self::Listening,
354 "waiting" => Self::Waiting,
355 "failed" => Self::Failed,
356 _ => Self::Unknown(value.to_string()),
357 }
358 }
359}
360
361#[derive(Debug, Serialize, Clone)]
362pub enum UnitType {
363 Target,
364 Service,
365 Mount,
366 Swap,
367 None,
368 Unknown(String),
369}
370
371impl Display for UnitType {
372 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373 write!(
374 f,
375 "{}",
376 match self {
377 Self::Target => "Target",
378 Self::Service => "Service",
379 Self::Mount => "Mount",
380 Self::Swap => "Swap",
381 Self::None => "None-type",
382 _ => "Unknown",
383 }
384 )
385 }
386}
387
388impl From<&String> for UnitType {
389 fn from(value: &String) -> Self {
390 match value as &str {
391 "target" => Self::Target,
392 "service" => Self::Service,
393 "mount" => Self::Mount,
394 "swap" => Self::Swap,
395 "" => Self::None,
396 _ => Self::Unknown(value.to_string()),
397 }
398 }
399}