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