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 offset = {
95 let now_rt = get_clock_time(CLOCK_REALTIME)?;
96 let now_mono = get_clock_time(CLOCK_MONOTONIC)?;
97 now_rt.saturating_sub(now_mono)
98 };
99
100 let userspace_usec = self.finish_timestamp_mono.saturating_sub(self.userspace);
101 let kernel_usec = if self.kernel > 0 {
102 let kernel_timestamp_mono = self.kernel.saturating_sub(offset);
103 self.userspace.saturating_sub(kernel_timestamp_mono)
104 } else {
105 0
106 };
107 let loader_usec = if self.loader > 0 {
108 self.userspace.saturating_sub(self.loader)
112 } else {
114 0
115 };
116 let firmware_usec = if self.loader > 0 {
117 self.loader.saturating_sub(self.firmware)
118 } else {
119 0
120 };
121
122 self.firmware = firmware_usec;
123 self.loader = loader_usec;
124 self.kernel = kernel_usec;
125 self.userspace = userspace_usec;
126
127 self.total = firmware_usec + loader_usec + kernel_usec + userspace_usec;
128 Ok(())
129 }
130}
131
132fn get_clock_time(clock_id: i32) -> Result<u64> {
133 let mut tp = MaybeUninit::<timespec>::uninit();
134 let res = unsafe { clock_gettime(clock_id, tp.as_mut_ptr()) };
135 if res == 0 {
136 let tp = unsafe { tp.assume_init() };
137 Ok(tp.tv_sec as u64 * 1_000_000 + (tp.tv_nsec as u64 / 1_000))
138 } else {
139 Err(anyhow!(
140 "Failed to get clock_time: {}",
141 Error::last_os_error()
142 ))
143 }
144}
145
146fn unescape(s: &str) -> String {
147 s.replace("\\x20", " ")
148 .replace("\\x5c", "\\")
149 .replace("\\x2f", "/")
150 .replace("\\x2d", "-")
151}
152
153type ServiceTuple = (
154 String,
155 String,
156 String,
157 String,
158 String,
159 String,
160 OwnedObjectPath,
161 u32,
162 String,
163 OwnedObjectPath,
164);
165
166#[derive(Debug, Serialize, Clone)]
167pub struct ServiceInfo {
168 pub name: String,
170
171 pub description: String,
173
174 pub load_state: LoadState,
176
177 pub active_state: ActiveState,
179
180 pub work_state: WorkState,
182
183 pub daemon_path: String,
185
186 pub job_id: u32,
188
189 pub unit_type: UnitType,
191}
192
193impl ToPlainText for ServiceInfo {
194 fn to_plain(&self) -> String {
195 let mut s = format!("\nService \"{}\"\n", &self.name);
196 s += &print_val("Description", &self.description);
197 s += &print_val("Load state", &self.load_state);
198 s += &print_val("Active state", &self.active_state);
199 s += &print_val("Work state", &self.work_state);
200 s += &print_val("Daemon path", &self.daemon_path);
201 s += &print_val("Job ID", &self.job_id);
202 s += &print_val("Unit type", &self.unit_type);
203
204 s
205 }
206}
207
208impl ToJson for ServiceInfo {}
209
210impl From<ServiceTuple> for ServiceInfo {
211 fn from(value: ServiceTuple) -> Self {
212 Self {
213 name: unescape(&value.0),
214 description: unescape(&value.1),
215 load_state: LoadState::from(&value.2),
216 active_state: ActiveState::from(&value.3),
217 work_state: WorkState::from(&value.4),
218 daemon_path: unescape(&value.5),
219 job_id: value.7,
220 unit_type: UnitType::from(&value.8),
221 }
222 }
223}
224
225#[derive(Debug, Serialize, Clone)]
226pub enum LoadState {
227 Loaded,
228 Stub,
229 Masked,
230 NotFound,
231 Unknown(String),
232}
233
234impl Display for LoadState {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 write!(
237 f,
238 "{}",
239 match self {
240 Self::Loaded => "Loaded",
241 Self::Stub => "Stub",
242 Self::Masked => "Masked",
243 Self::NotFound => "Not found",
244 _ => "Unknown",
245 }
246 )
247 }
248}
249
250impl From<&String> for LoadState {
251 fn from(value: &String) -> Self {
252 match value as &str {
253 "loaded" => Self::Loaded,
254 "stub" => Self::Stub,
255 "masked" => Self::Masked,
256 "not-found" => Self::NotFound,
257 _ => Self::Unknown(value.to_string()),
258 }
259 }
260}
261
262#[derive(Debug, Serialize, Clone)]
263pub enum ActiveState {
264 Active,
265 Inactive,
266 Activating,
267 Deactivating,
268 Failed,
269 Unknown(String),
270}
271
272impl Display for ActiveState {
273 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274 write!(
275 f,
276 "{}",
277 match self {
278 Self::Active => "Active",
279 Self::Inactive => "Inactive",
280 Self::Activating => "Activating",
281 Self::Deactivating => "Deactivating",
282 Self::Failed => "Failed",
283 _ => "Unknown",
284 }
285 )
286 }
287}
288
289impl From<&String> for ActiveState {
290 fn from(value: &String) -> Self {
291 match value as &str {
292 "active" => Self::Active,
293 "inactive" => Self::Inactive,
294 "activating" => Self::Activating,
295 "deactivating" => Self::Deactivating,
296 "failed" => Self::Failed,
297 _ => Self::Unknown(value.to_string()),
298 }
299 }
300}
301
302#[derive(Debug, Serialize, Clone)]
303pub enum WorkState {
304 Active,
305 Running,
306 Exited,
307 Dead,
308 Mounted,
309 Mounting,
310 Plugged,
311 Listening,
312 Waiting,
313 Failed,
314 Unknown(String),
315}
316
317impl Display for WorkState {
318 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
319 write!(
320 f,
321 "{}",
322 match self {
323 Self::Active => "Active",
324 Self::Running => "Running",
325 Self::Exited => "Exited",
326 Self::Dead => "Dead",
327 Self::Mounted => "Mounted",
328 Self::Mounting => "Mounting",
329 Self::Plugged => "Plugged",
330 Self::Listening => "Listening",
331 Self::Waiting => "Waiting",
332 Self::Failed => "Failed",
333 _ => "Unknown",
334 }
335 )
336 }
337}
338
339impl From<&String> for WorkState {
340 fn from(value: &String) -> Self {
341 match value as &str {
342 "active" => Self::Active,
343 "running" => Self::Running,
344 "exited" => Self::Exited,
345 "dead" => Self::Dead,
346 "mounted" => Self::Mounted,
347 "mounting" => Self::Mounting,
348 "plugged" => Self::Plugged,
349 "listening" => Self::Listening,
350 "waiting" => Self::Waiting,
351 "failed" => Self::Failed,
352 _ => Self::Unknown(value.to_string()),
353 }
354 }
355}
356
357#[derive(Debug, Serialize, Clone)]
358pub enum UnitType {
359 Target,
360 Service,
361 Mount,
362 Swap,
363 None,
364 Unknown(String),
365}
366
367impl Display for UnitType {
368 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369 write!(
370 f,
371 "{}",
372 match self {
373 Self::Target => "Target",
374 Self::Service => "Service",
375 Self::Mount => "Mount",
376 Self::Swap => "Swap",
377 Self::None => "None-type",
378 _ => "Unknown",
379 }
380 )
381 }
382}
383
384impl From<&String> for UnitType {
385 fn from(value: &String) -> Self {
386 match value as &str {
387 "target" => Self::Target,
388 "service" => Self::Service,
389 "mount" => Self::Mount,
390 "swap" => Self::Swap,
391 "" => Self::None,
392 _ => Self::Unknown(value.to_string()),
393 }
394 }
395}