1use std::io::{Read, Write};
20use std::os::unix::net::UnixStream;
21use std::path::Path;
22use std::time::{Duration, Instant};
23
24pub const MANAGER_PATH: &str = "/org/freedesktop/systemd1";
26
27pub const MANAGER_INTERFACE: &str = "org.freedesktop.systemd1.Manager";
29
30pub const EXCHANGE_WAIT: Duration = Duration::from_secs(20);
33
34const BUS_NAME: &str = "org.freedesktop.DBus";
36const BUS_PATH: &str = "/org/freedesktop/DBus";
37
38pub const MANAGER_NAME: &str = "org.freedesktop.systemd1";
40
41const MAX_REPLY_BYTES: usize = 1 << 20;
43
44const METHOD_CALL: u8 = 1;
46
47const METHOD_RETURN: u8 = 2;
49
50const ERROR: u8 = 3;
52
53const FIELD_REPLY_SERIAL: u8 = 5;
55
56const FIELD_PATH: u8 = 1;
58const FIELD_INTERFACE: u8 = 2;
59const FIELD_MEMBER: u8 = 3;
60const FIELD_ERROR_NAME: u8 = 4;
61const FIELD_DESTINATION: u8 = 6;
62const FIELD_SIGNATURE: u8 = 8;
63
64#[derive(Clone, Debug, Eq, PartialEq)]
66pub enum CallError {
67 Connection(String),
69 Authentication(String),
71 Manager {
73 name: String,
75 text: String,
77 },
78 Malformed(String),
80}
81
82struct Writer {
84 bytes: Vec<u8>,
85}
86
87impl Writer {
88 fn align(&mut self, boundary: usize) {
89 while self.bytes.len() % boundary != 0 {
90 self.bytes.push(0);
91 }
92 }
93
94 fn unsigned(&mut self, value: u32) {
95 self.align(4);
96 self.bytes.extend(value.to_le_bytes());
97 }
98
99 fn text(&mut self, value: &str) {
100 self.unsigned(u32::try_from(value.len()).unwrap_or(u32::MAX));
101 self.bytes.extend(value.as_bytes());
102 self.bytes.push(0);
103 }
104
105 fn signature(&mut self, value: &str) {
106 self.bytes.push(u8::try_from(value.len()).unwrap_or(u8::MAX));
107 self.bytes.extend(value.as_bytes());
108 self.bytes.push(0);
109 }
110
111 fn field(&mut self, code: u8, kind: char, value: &str) {
113 self.align(8);
114 self.bytes.push(code);
115 self.signature(&kind.to_string());
116 if kind == 'g' {
117 self.signature(value);
118 } else {
119 self.text(value);
120 }
121 }
122}
123
124#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126pub struct Target<'a> {
127 pub destination: &'a str,
129 pub path: &'a str,
131 pub interface: &'a str,
133 pub member: &'a str,
135}
136
137pub const HELLO: Target<'static> = Target { destination: BUS_NAME, path: BUS_PATH, interface: BUS_NAME, member: "Hello" };
139
140pub const RELOAD: Target<'static> = Target { destination: MANAGER_NAME, path: MANAGER_PATH, interface: MANAGER_INTERFACE, member: "Reload" };
142
143pub const START_UNIT: Target<'static> = Target { destination: MANAGER_NAME, path: MANAGER_PATH, interface: MANAGER_INTERFACE, member: "StartUnit" };
145
146pub const STOP_UNIT: Target<'static> = Target { destination: MANAGER_NAME, path: MANAGER_PATH, interface: MANAGER_INTERFACE, member: "StopUnit" };
148
149pub const RESET_FAILED_UNIT: Target<'static> =
151 Target { destination: MANAGER_NAME, path: MANAGER_PATH, interface: MANAGER_INTERFACE, member: "ResetFailedUnit" };
152
153pub const GET_UNIT: Target<'static> = Target { destination: MANAGER_NAME, path: MANAGER_PATH, interface: MANAGER_INTERFACE, member: "GetUnit" };
155
156const PROPERTIES_INTERFACE: &str = "org.freedesktop.DBus.Properties";
158
159const UNIT_INTERFACE: &str = "org.freedesktop.systemd1.Unit";
161
162pub const NO_SUCH_UNIT: &str = "org.freedesktop.systemd1.NoSuchUnit";
164
165const UNKNOWN_OBJECT: &str = "org.freedesktop.DBus.Error.UnknownObject";
167
168pub fn encode_call(serial: u32, target: &Target<'_>, arguments: &[&str]) -> Vec<u8> {
178 let mut body: Writer = Writer { bytes: Vec::new() };
179 for argument in arguments {
180 body.text(argument);
181 }
182 let mut fields: Writer = Writer { bytes: vec![0; 16] };
183 fields.field(FIELD_PATH, 'o', target.path);
184 fields.field(FIELD_DESTINATION, 's', target.destination);
185 fields.field(FIELD_INTERFACE, 's', target.interface);
186 fields.field(FIELD_MEMBER, 's', target.member);
187 if !arguments.is_empty() {
188 fields.field(FIELD_SIGNATURE, 'g', &"s".repeat(arguments.len()));
189 }
190 let array_length: u32 = u32::try_from(fields.bytes.len() - 16).unwrap_or(u32::MAX);
192 fields.align(8);
193 let mut message: Vec<u8> = fields.bytes;
194 message[0] = b'l';
195 message[1] = METHOD_CALL;
196 message[2] = 0;
197 message[3] = 1;
198 message[4..8].copy_from_slice(&u32::try_from(body.bytes.len()).unwrap_or(u32::MAX).to_le_bytes());
199 message[8..12].copy_from_slice(&serial.to_le_bytes());
200 message[12..16].copy_from_slice(&array_length.to_le_bytes());
201 message.extend(body.bytes);
202 return message;
203}
204
205#[derive(Clone, Debug, Eq, PartialEq)]
207pub struct Reply {
208 pub kind: u8,
210 pub reply_serial: Option<u32>,
212 pub error_name: Option<String>,
214 pub first_text: Option<String>,
216}
217
218fn read_u32(bytes: &[u8], at: usize) -> Option<u32> {
219 return bytes.get(at..at.checked_add(4)?).map(|slice| { return u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]); });
220}
221
222fn read_text(bytes: &[u8], at: usize) -> Option<(String, usize)> {
223 let length: usize = usize::try_from(read_u32(bytes, at)?).ok()?;
224 let start: usize = at.checked_add(4)?;
225 let text: &[u8] = bytes.get(start..start.checked_add(length)?)?;
226 return Some((String::from_utf8_lossy(text).into_owned(), start.saturating_add(length).saturating_add(1)));
227}
228
229fn read_variant_text(bytes: &[u8], at: usize) -> Option<String> {
231 let length: usize = usize::from(*bytes.get(at)?);
232 let inner_start: usize = at.checked_add(1)?;
233 let inner: &[u8] = bytes.get(inner_start..inner_start.checked_add(length)?)?;
234 if inner != b"s" {
235 return None;
236 }
237 return read_text(bytes, aligned(inner_start.checked_add(length)?.checked_add(1)?, 4)).map(|(text, _)| { return text; });
239}
240
241fn aligned(offset: usize, boundary: usize) -> usize {
242 return offset.checked_add(boundary - 1).map_or(usize::MAX, |sum| { return sum / boundary * boundary; });
243}
244
245pub fn decode_reply(bytes: &[u8]) -> Result<Reply, CallError> {
257 let malformed = |what: &str| { return CallError::Malformed(what.to_string()); };
258 if bytes.len() < 16 || bytes[0] != b'l' {
259 return Err(malformed("the reply is not a little-endian message"));
260 }
261 let kind: u8 = bytes[1];
262 let body_length: usize = usize::try_from(read_u32(bytes, 4).ok_or_else(|| { return malformed("no body length"); })?).unwrap_or(usize::MAX);
263 let fields_length: usize = usize::try_from(read_u32(bytes, 12).ok_or_else(|| { return malformed("no header length"); })?).unwrap_or(usize::MAX);
264 let fields_end: usize = 16usize.saturating_add(fields_length);
265 let body_start: usize = aligned(fields_end, 8);
266 if bytes.len() < body_start.saturating_add(body_length) || fields_end > bytes.len() {
267 return Err(malformed("the reply is shorter than it says"));
268 }
269 let mut error_name: Option<String> = None;
270 let mut reply_serial: Option<u32> = None;
271 let mut signature: String = String::new();
272 let mut at: usize = 16;
273 while at < fields_end {
274 at = aligned(at, 8);
275 let (Some(&code), Some(&signature_length)) = (bytes.get(at), bytes.get(at.saturating_add(1))) else { break };
276 let kind_at: usize = at.saturating_add(2);
277 let Some(&value_kind) = bytes.get(kind_at) else { break };
278 let value_at: usize = kind_at.saturating_add(usize::from(signature_length)).saturating_add(1);
279 match (value_kind, code) {
280 (b's' | b'o', _) => {
281 let Some((text, next)) = read_text(bytes, aligned(value_at, 4)) else { break };
282 if code == FIELD_ERROR_NAME {
283 error_name = Some(text);
284 }
285 at = next;
286 }
287 (b'g', _) => {
288 let length: usize = usize::from(*bytes.get(value_at).unwrap_or(&0));
289 if code == FIELD_SIGNATURE {
290 let start: usize = value_at.saturating_add(1);
291 signature = String::from_utf8_lossy(bytes.get(start..start.saturating_add(length)).unwrap_or_default()).into_owned();
292 }
293 at = value_at.saturating_add(length).saturating_add(2);
294 }
295 (b'u', _) => {
296 let number_at: usize = aligned(value_at, 4);
297 if code == FIELD_REPLY_SERIAL {
298 reply_serial = read_u32(bytes, number_at);
299 }
300 at = number_at.saturating_add(4);
301 }
302 _ => break,
303 }
304 }
305 let first_text: Option<String> = if signature.starts_with('s') || signature.starts_with('o') {
307 read_text(bytes, aligned(body_start, 4)).map(|(text, _)| { return text; })
308 } else if signature == "v" {
309 read_variant_text(bytes, body_start)
310 } else {
311 None
312 };
313 return Ok(Reply { kind, reply_serial, error_name, first_text });
314}
315
316fn external_identity() -> String {
318 let user: u32 = unsafe { libc::geteuid() };
320 return user.to_string().bytes().map(|byte| { return format!("{byte:02x}"); }).collect();
321}
322
323fn remaining(deadline: Instant) -> Result<Duration, CallError> {
325 let left: Duration = deadline.saturating_duration_since(Instant::now());
326 return if left.is_zero() { Err(CallError::Connection(String::from("the bus did not answer in time"))) } else { Ok(left) };
327}
328
329fn read_line(stream: &mut UnixStream, deadline: Instant) -> Result<String, CallError> {
330 let mut line: Vec<u8> = Vec::new();
331 let mut one: [u8; 1] = [0];
332 while line.len() < 4096 {
333 stream.set_read_timeout(Some(remaining(deadline)?)).map_err(|error| { return CallError::Connection(error.to_string()); })?;
334 stream.read_exact(&mut one).map_err(|error| { return CallError::Authentication(error.to_string()); })?;
335 if one[0] == b'\n' {
336 return Ok(String::from_utf8_lossy(line.as_slice()).trim_end_matches('\r').to_string());
337 }
338 line.push(one[0]);
339 }
340 return Err(CallError::Authentication(String::from("the answer is too long")));
341}
342
343fn read_message(stream: &mut UnixStream, deadline: Instant) -> Result<Vec<u8>, CallError> {
345 let mut header: Vec<u8> = vec![0; 16];
346 stream.set_read_timeout(Some(remaining(deadline)?)).map_err(|error| { return CallError::Connection(error.to_string()); })?;
347 stream.read_exact(header.as_mut_slice()).map_err(|error| { return CallError::Connection(error.to_string()); })?;
348 let body_length: usize = usize::try_from(read_u32(header.as_slice(), 4).unwrap_or(0)).unwrap_or(usize::MAX);
349 let fields_length: usize = usize::try_from(read_u32(header.as_slice(), 12).unwrap_or(0)).unwrap_or(usize::MAX);
350 let rest: usize = aligned(16usize.saturating_add(fields_length), 8).saturating_add(body_length).saturating_sub(16);
351 if rest > MAX_REPLY_BYTES {
352 return Err(CallError::Malformed(String::from("the reply is larger than this client reads")));
353 }
354 let mut remainder: Vec<u8> = vec![0; rest];
355 stream.set_read_timeout(Some(remaining(deadline)?)).map_err(|error| { return CallError::Connection(error.to_string()); })?;
356 stream.read_exact(remainder.as_mut_slice()).map_err(|error| { return CallError::Connection(error.to_string()); })?;
357 header.extend(remainder);
358 return Ok(header);
359}
360
361pub fn session_bus_path(address: Option<&str>, runtime_dir: Option<&Path>) -> Option<std::path::PathBuf> {
363 let from_address: Option<std::path::PathBuf> = address.and_then(|list| {
364 return list.split(';').find_map(|entry| {
365 let rest: &str = entry.strip_prefix("unix:")?;
366 let path: &str = rest.split(',').find_map(|option| { return option.strip_prefix("path="); })?;
367 return if path.contains('%') || path.is_empty() { None } else { Some(std::path::PathBuf::from(path)) };
369 });
370 });
371 return from_address.or_else(|| { return runtime_dir.map(|directory| { return directory.join("bus"); }); });
372}
373
374pub struct Bus {
376 stream: UnixStream,
377 deadline: Instant,
378 serial: u32,
379}
380
381impl Bus {
383 pub fn connect(socket: &Path, wait: Duration) -> Result<Bus, CallError> {
393 let deadline: Instant = Instant::now() + wait;
394 let mut stream: UnixStream = UnixStream::connect(socket).map_err(|error| { return CallError::Connection(error.to_string()); })?;
395 stream.set_write_timeout(Some(wait)).map_err(|error| { return CallError::Connection(error.to_string()); })?;
396 let mut hello: Vec<u8> = vec![0];
397 hello.extend(format!("AUTH EXTERNAL {}\r\n", external_identity()).as_bytes());
398 stream.write_all(hello.as_slice()).map_err(|error| { return CallError::Authentication(error.to_string()); })?;
399 let answer: String = read_line(&mut stream, deadline)?;
400 if !answer.starts_with("OK ") {
401 return Err(CallError::Authentication(answer));
402 }
403 stream.write_all(b"BEGIN\r\n").map_err(|error| { return CallError::Authentication(error.to_string()); })?;
404 let mut bus: Bus = Bus { stream, deadline, serial: 0 };
405 bus.call(&HELLO, &[])?;
406 return Ok(bus);
407 }
408
409 pub fn call(&mut self, target: &Target<'_>, arguments: &[&str]) -> Result<Reply, CallError> {
420 self.serial += 1;
421 let serial: u32 = self.serial;
422 self.stream.write_all(encode_call(serial, target, arguments).as_slice()).map_err(|error| { return CallError::Connection(error.to_string()); })?;
423 loop {
424 let reply: Reply = decode_reply(read_message(&mut self.stream, self.deadline)?.as_slice())?;
425 if reply.reply_serial != Some(serial) || !matches!(reply.kind, METHOD_RETURN | ERROR) {
426 continue;
427 }
428 return match (reply.kind, reply.error_name.clone()) {
429 (METHOD_RETURN, _) => Ok(reply),
430 (_, Some(name)) => Err(CallError::Manager { name, text: reply.first_text.unwrap_or_default() }),
431 _ => Err(CallError::Malformed(String::from("an error reply without a name"))),
432 };
433 }
434 }
435
436 pub fn reload(&mut self) -> Result<(), CallError> {
438 return self.call(&RELOAD, &[]).map(drop);
439 }
440
441 pub fn reset_failed(&mut self, name: &str) -> Result<(), CallError> {
443 return self.call(&RESET_FAILED_UNIT, &[name]).map(drop);
444 }
445
446 pub fn stop_unit(&mut self, name: &str) -> Result<(), CallError> {
448 return self.call(&STOP_UNIT, &[name, "replace"]).map(drop);
449 }
450
451 pub fn active_state(&mut self, name: &str) -> Result<Option<String>, CallError> {
464 let path: String = match self.call(&GET_UNIT, &[name]) {
465 Ok(reply) => reply.first_text.ok_or_else(|| { return CallError::Malformed(String::from("GetUnit answered without a path")); })?,
466 Err(CallError::Manager { name: error, .. }) if error == NO_SUCH_UNIT => return Ok(None),
467 Err(error) => return Err(error),
468 };
469 let property: Target<'_> = Target { destination: MANAGER_NAME, path: path.as_str(), interface: PROPERTIES_INTERFACE, member: "Get" };
470 return match self.call(&property, &[UNIT_INTERFACE, "ActiveState"]) {
471 Ok(reply) => reply
472 .first_text
473 .map(Some)
474 .ok_or_else(|| { return CallError::Malformed(String::from("the state property answered without text")); }),
475 Err(CallError::Manager { name: error, .. }) if error == NO_SUCH_UNIT || error == UNKNOWN_OBJECT => Ok(None),
476 Err(error) => Err(error),
477 };
478 }
479
480 pub fn start_unit(&mut self, name: &str) -> Result<(), CallError> {
482 return self.call(&START_UNIT, &[name, "replace"]).map(drop);
483 }
484}
485
486#[cfg(test)]
488#[path = "systemd_dbus_tests.rs"]
489mod tests;