systemd_transient/
manager.rs1use std::time::Duration;
2
3use futures_util::StreamExt;
4use tracing::warn;
5use zbus::{
6 Connection,
7 zvariant::{ObjectPath, OwnedObjectPath, Value},
8};
9
10use crate::{
11 TransientUnitHandle,
12 dbus::manager::{JobRemovedStream, ManagerProxy},
13 error::SystemdTransientError,
14 props::{UnitProperty, Usec, get_property},
15};
16
17const JOB_MODE_RPELACE: &str = "replace";
18const ADDITIONAL_TIMEOUT_BUFFER: Duration = Duration::from_secs(5);
19
20#[derive(Clone)]
21pub struct SystemdTransientManager {
22 conn: Connection,
23}
24
25impl SystemdTransientManager {
26 pub async fn connect() -> Result<Self, SystemdTransientError> {
27 let conn = Connection::session().await?;
28 Ok(Self { conn })
29 }
30
31 pub async fn start(
32 &self,
33 name: String,
34 props: Vec<UnitProperty>,
35 ) -> Result<TransientUnitHandle, SystemdTransientError> {
36 let proxy = ManagerProxy::new(&self.conn).await?;
37
38 let start_timeout = if let Some(timeout) = get_property(&props, |prop| {
39 if let UnitProperty::TimeoutStartUSec(usec) = prop {
40 Some(Duration::from(*usec))
41 } else {
42 None
43 }
44 }) {
45 timeout
46 } else {
47 Usec(proxy.default_timeout_start_usec().await?).into()
48 } + ADDITIONAL_TIMEOUT_BUFFER;
49
50 let stop_timeout = if let Some(timeout) = get_property(&props, |prop| {
51 if let UnitProperty::TimeoutStopUSec(usec) = prop {
52 Some(Duration::from(*usec))
53 } else {
54 None
55 }
56 }) {
57 timeout
58 } else {
59 Usec(proxy.default_timeout_start_usec().await?).into()
60 } + ADDITIONAL_TIMEOUT_BUFFER;
61
62 let pairs: Vec<_> = props.into_iter().map(UnitProperty::into_pair).collect();
63 let tuples: Vec<(&str, &Value<'_>)> = pairs.iter().map(|(k, v)| (k.as_ref(), v)).collect();
64 let refs: Vec<&(&str, &Value<'_>)> = tuples.iter().collect();
65
66 let action = async |proxy: &ManagerProxy<'_>| {
67 proxy
68 .start_transient_unit(&name, JOB_MODE_RPELACE, &refs, &[])
69 .await
70 };
71
72 run_job(&proxy, action, start_timeout).await?;
73
74 Ok(TransientUnitHandle {
75 name,
76 stop_timeout,
77 manager: self.clone(),
78 })
79 }
80
81 pub(crate) async fn stop(
82 &self,
83 name: &str,
84 timeout: Duration,
85 ) -> Result<(), SystemdTransientError> {
86 let proxy = ManagerProxy::new(&self.conn).await?;
87 let action = async |proxy: &ManagerProxy<'_>| proxy.stop_unit(name, JOB_MODE_RPELACE).await;
88 run_job(&proxy, action, timeout).await
89 }
90}
91
92async fn run_job<F>(
93 proxy: &ManagerProxy<'_>,
94 action: F,
95 timeout: Duration,
96) -> Result<(), SystemdTransientError>
97where
98 F: AsyncFnOnce(&ManagerProxy<'_>) -> zbus::Result<OwnedObjectPath>,
99{
100 let stream = proxy.receive_job_removed().await?;
101 let job = action(&proxy).await?;
102
103 tokio::time::timeout(timeout, wait_for_job(stream, job.as_ref()))
104 .await
105 .map_err(|_| SystemdTransientError::JobTimedOut {
106 job: job.clone(),
107 timeout,
108 })?
109}
110
111async fn wait_for_job(
112 mut stream: JobRemovedStream,
113 job_path: ObjectPath<'_>,
114) -> Result<(), SystemdTransientError> {
115 while let Some(signal) = stream.next().await {
116 let args = match signal.args() {
117 Ok(args) => args,
118 Err(err) => {
119 warn!("unparsable JobRemoved signal: {}", err);
120 continue;
121 }
122 };
123
124 if *args.job() != job_path {
125 continue;
126 }
127
128 return match *args.result() {
129 "done" => Ok(()),
130 r => Err(SystemdTransientError::JobWaitFailed {
131 unit: args.unit().to_string(),
132 reason: r.to_owned(),
133 }),
134 };
135 }
136
137 Err(SystemdTransientError::JobStreamEnded)
138}