systemd.rs
1 #[derive(Debug)] 2 pub struct ProcessState { 3 pub span: tracing::Span, 4 } 5 6 impl ProcessState { 7 pub fn set_starting(&self) { 8 tracing::debug!(parent: &self.span, status = "starting", "Setting service status"); 9 if let Err(error) = notify(&[NotifyState::Status("starting")]) { 10 tracing::error!(parent: &self.span, ?error, "Failed to notify systemd of state change"); 11 } else { 12 tracing::info!( 13 parent: &self.span, 14 status = "starting", 15 "Successfully notified systemd of service status" 16 ); 17 } 18 } 19 20 pub fn set_running(&self) { 21 tracing::debug!(parent: &self.span, status = "ready", "Setting service status"); 22 if let Err(error) = notify(&[NotifyState::Ready]) { 23 tracing::error!(parent: &self.span, ?error, "Failed to notify systemd of state change"); 24 } else { 25 tracing::info!(parent: &self.span, 26 status = "ready", 27 "Successfully notified systemd of service status" 28 ); 29 } 30 } 31 32 pub fn set_failed(&self) { 33 tracing::debug!(parent: &self.span, status = "failed,stopping", "Setting service status"); 34 if let Err(error) = notify(&[NotifyState::Status("failed"), NotifyState::Stopping]) { 35 tracing::error!(parent: &self.span, ?error, "Failed to notify systemd of state change"); 36 } else { 37 tracing::info!( 38 parent: &self.span, 39 status = "failed,stopping", 40 "Successfully notified systemd of service status" 41 ); 42 } 43 } 44 45 pub fn set_cancelled(&self) { 46 tracing::debug!(parent: &self.span, status = "ECANCELED", "Setting service status"); 47 // verified highly scientificly by looking up https://docs.rs/nix/latest/nix/type.Error.html#variant.ECANCELED 48 let ecanceled = 125; 49 50 if let Err(error) = notify(&[NotifyState::Errno(ecanceled)]) { 51 tracing::error!(parent: &self.span, ?error, "Failed to notify systemd of state change"); 52 } else { 53 tracing::info!( 54 parent: &self.span, 55 status = "ECANCELED", 56 "Successfully notified systemd of service status" 57 ); 58 } 59 } 60 61 pub fn set_finished(&self) { 62 tracing::debug!(parent: &self.span, status = "stopping", "Setting service status"); 63 if let Err(error) = notify(&[NotifyState::Stopping]) { 64 tracing::error!(parent: &self.span, ?error, "Failed to notify systemd of state change"); 65 } else { 66 tracing::info!( 67 parent: &self.span, 68 status = "stopping", 69 "Successfully notified systemd of service status" 70 ); 71 } 72 } 73 } 74 75 /// Daemon notification for the service manager. 76 #[derive(Clone, Debug)] 77 #[allow(dead_code)] // TODO: Delete unused variants? 78 enum NotifyState<'a> { 79 /// Service startup is finished. 80 Ready, 81 82 /// Service is reloading its configuration. 83 /// 84 /// On systemd v253 and newer, this message MUST be followed by a 85 /// [`NotifyState::MonotonicUsec`] notification, or the reload will fail 86 /// and the service will be terminated. 87 Reloading, 88 89 /// Service is stopping. 90 Stopping, 91 92 /// Free-form status message for the service manager. 93 Status(&'a str), 94 95 /// Service has failed with an `errno`-style error code, e.g. `2` for `ENOENT`. 96 Errno(u32), 97 98 /// Service has failed with a D-Bus-style error code, e.g. `org.freedesktop.DBus.Error.TimedOut`. 99 BusError(&'a str), 100 101 /// Main process ID (PID) of the service, in case it wasn't started directly by the service manager. 102 MainPid(u32), 103 104 /// Tells the service manager to update the watchdog timestamp. 105 Watchdog, 106 107 /// Tells the service manager to trigger a watchdog failure. 108 WatchdogTrigger, 109 110 /// Resets the configured watchdog value. 111 WatchdogUsec(u32), 112 113 /// Tells the service manager to extend the service timeout. 114 ExtendTimeoutUsec(u32), 115 116 /// Tells the service manager to store attached file descriptors. 117 FdStore, 118 119 /// Tells the service manager to remove stored file descriptors. 120 FdStoreRemove, 121 122 /// Tells the service manager to use this name for the attached file descriptor. 123 FdName(&'a str), 124 125 /// Notify systemd of the current monotonic time in microseconds. 126 /// You can construct this value by calling [`NotifyState::monotonic_usec_now()`]. 127 MonotonicUsec(i128), 128 129 /// Custom state. 130 Custom(&'a str), 131 } 132 133 impl std::fmt::Display for NotifyState<'_> { 134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 135 match self { 136 NotifyState::Ready => write!(f, "READY=1"), 137 NotifyState::Reloading => write!(f, "RELOADING=1"), 138 NotifyState::Stopping => write!(f, "STOPPING=1"), 139 NotifyState::Status(msg) => write!(f, "STATUS={msg}"), 140 NotifyState::Errno(err) => write!(f, "ERRNO={err}"), 141 NotifyState::BusError(addr) => write!(f, "BUSERROR={addr}"), 142 NotifyState::MainPid(pid) => write!(f, "MAINPID={pid}"), 143 NotifyState::Watchdog => write!(f, "WATCHDOG=1"), 144 NotifyState::WatchdogTrigger => write!(f, "WATCHDOG=trigger"), 145 NotifyState::WatchdogUsec(usec) => write!(f, "WATCHDOG_USEC={usec}"), 146 NotifyState::ExtendTimeoutUsec(usec) => write!(f, "EXTEND_TIMEOUT_USEC={usec}"), 147 NotifyState::FdStore => write!(f, "FDSTORE=1"), 148 NotifyState::FdStoreRemove => write!(f, "FDSTOREREMOVE=1"), 149 NotifyState::FdName(name) => write!(f, "FDNAME={name}"), 150 NotifyState::MonotonicUsec(usec) => write!(f, "MONOTONIC_USEC={usec}"), 151 NotifyState::Custom(state) => write!(f, "{state}"), 152 } 153 } 154 } 155 156 fn connect_notify_socket() -> std::io::Result<Option<std::os::unix::net::UnixDatagram>> { 157 let Some(socket_path) = std::env::var_os("NOTIFY_SOCKET") else { 158 return Ok(None); 159 }; 160 161 let sock = std::os::unix::net::UnixDatagram::unbound()?; 162 163 sock.connect(socket_path)?; 164 165 Ok(Some(sock)) 166 } 167 168 fn notify(state: &[NotifyState]) -> std::io::Result<()> { 169 use std::fmt::Write; 170 171 let mut msg = String::new(); 172 173 let Some(sock) = connect_notify_socket()? else { 174 return Ok(()); 175 }; 176 177 for s in state { 178 let _ = writeln!(msg, "{s}"); 179 } 180 181 let len = sock.send(msg.as_bytes())?; 182 183 if len != msg.len() { 184 Err(std::io::Error::new( 185 std::io::ErrorKind::WriteZero, 186 "incomplete write", 187 )) 188 } else { 189 Ok(()) 190 } 191 }