systemd_connector/
notify.rs1use std::{fmt, io, sync::Arc};
4
5use camino::Utf8PathBuf;
6use thiserror::Error;
7use tokio::net::UnixDatagram;
8
9use crate::socket::SocketError;
10
11const NOTIFY_SOCKET: &str = "NOTIFY_SOCKET";
14
15#[derive(Debug, Error)]
17pub enum NotifyError {
18 #[error("{}", .0)]
20 IO(#[from] io::Error),
21
22 #[error("Missing ${0} variable")]
24 MissingVar(&'static str),
25
26 #[error("Invalid ${0}={1}")]
28 InvalidVar(&'static str, String),
29}
30
31impl From<SocketError> for NotifyError {
32 fn from(value: SocketError) -> Self {
33 match value {
34 SocketError::IO(err) => NotifyError::IO(err),
35 SocketError::MissingVar(var) => NotifyError::MissingVar(var),
36 SocketError::InvalidVar(var, value) => NotifyError::InvalidVar(var, value),
37 err => panic!("Unexpected error for Notify: {err}"),
38 }
39 }
40}
41
42#[derive(Debug, Clone)]
44pub struct CustomVariable {
45 key: String,
46 value: String,
47}
48
49impl fmt::Display for CustomVariable {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 write!(f, "X-{}={}", self.key, self.value)
52 }
53}
54
55#[derive(Debug, Clone)]
57pub enum Notification {
58 Ready,
60
61 Reloading,
63
64 Stopping,
66
67 Status(String),
69
70 Errno(i32),
72
73 WatchdogOk,
75
76 WatchdogTrigger,
78
79 Custom(CustomVariable),
81}
82
83impl fmt::Display for Notification {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match self {
86 Notification::Ready => f.write_str("READY=1"),
87 Notification::Reloading => f.write_str("RELOADING=1"),
88 Notification::Stopping => f.write_str("STOPPING=1"),
89 Notification::Status(status) => write!(f, "STATUS={status}"),
90 Notification::Errno(errno) => write!(f, "ERRNO={errno}"),
91 Notification::WatchdogOk => f.write_str("WATCHDOG=1"),
92 Notification::WatchdogTrigger => f.write_str("WATCHDOG=trigger"),
93 Notification::Custom(variable) => write!(f, "{variable}"),
94 }
95 }
96}
97
98#[derive(Debug, Clone, Default)]
101pub struct Message {
102 variables: Vec<Notification>,
103}
104
105impl Message {
106 pub fn new() -> Self {
108 Self {
109 variables: Vec::new(),
110 }
111 }
112
113 pub fn push(&mut self, notification: Notification) {
115 self.variables.push(notification)
116 }
117}
118
119impl From<Notification> for Message {
120 fn from(value: Notification) -> Self {
121 Message {
122 variables: vec![value],
123 }
124 }
125}
126
127impl FromIterator<Notification> for Message {
128 fn from_iter<I: IntoIterator<Item = Notification>>(iter: I) -> Self {
129 let variables = iter.into_iter().collect();
130 Message { variables }
131 }
132}
133
134impl fmt::Display for Message {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 for variable in &self.variables {
137 writeln!(f, "{variable}")?;
138 }
139 Ok(())
140 }
141}
142
143#[derive(Debug, Clone)]
147pub struct SystemDNotify {
148 socket: Arc<UnixDatagram>,
149 address: Utf8PathBuf,
150}
151
152impl SystemDNotify {
153 pub fn from_environment() -> Result<Self, NotifyError> {
155 let address = crate::socket::var(NOTIFY_SOCKET)?.into();
156 let socket = UnixDatagram::unbound()?;
157
158 Ok(SystemDNotify {
159 socket: Arc::new(socket),
160 address,
161 })
162 }
163
164 pub async fn send<M: Into<Message>>(&self, message: M) -> Result<(), NotifyError> {
166 let message = message.into().to_string();
167 self.socket
168 .send_to(message.as_bytes(), &self.address)
169 .await?;
170 Ok(())
171 }
172}
173
174pub async fn ready() {
179 if let Ok(notify) = SystemDNotify::from_environment() {
180 if let Err(err) = notify.send(Notification::Ready).await {
181 tracing::warn!("Failed to notify systemd: {err}");
182 }
183 }
184}