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 TimedOut,
73 Manager {
75 name: String,
77 text: String,
79 },
80 Malformed(String),
82}
83
84struct Writer {
86 bytes: Vec<u8>,
87}
88
89impl Writer {
90 fn align(&mut self, boundary: usize) {
91 while self.bytes.len() % boundary != 0 {
92 self.bytes.push(0);
93 }
94 }
95
96 fn unsigned(&mut self, value: u32) {
97 self.align(4);
98 self.bytes.extend(value.to_le_bytes());
99 }
100
101 fn text(&mut self, value: &str) {
102 self.unsigned(u32::try_from(value.len()).unwrap_or(u32::MAX));
103 self.bytes.extend(value.as_bytes());
104 self.bytes.push(0);
105 }
106
107 fn signature(&mut self, value: &str) {
108 self.bytes.push(u8::try_from(value.len()).unwrap_or(u8::MAX));
109 self.bytes.extend(value.as_bytes());
110 self.bytes.push(0);
111 }
112
113 fn field(&mut self, code: u8, kind: char, value: &str) {
115 self.align(8);
116 self.bytes.push(code);
117 self.signature(&kind.to_string());
118 if kind == 'g' {
119 self.signature(value);
120 } else {
121 self.text(value);
122 }
123 }
124}
125
126#[derive(Clone, Copy, Debug, Eq, PartialEq)]
128pub struct Target<'a> {
129 pub destination: &'a str,
131 pub path: &'a str,
133 pub interface: &'a str,
135 pub member: &'a str,
137}
138
139pub const HELLO: Target<'static> = Target { destination: BUS_NAME, path: BUS_PATH, interface: BUS_NAME, member: "Hello" };
141
142pub const RELOAD: Target<'static> = Target { destination: MANAGER_NAME, path: MANAGER_PATH, interface: MANAGER_INTERFACE, member: "Reload" };
144
145pub const START_UNIT: Target<'static> = Target { destination: MANAGER_NAME, path: MANAGER_PATH, interface: MANAGER_INTERFACE, member: "StartUnit" };
147
148pub const STOP_UNIT: Target<'static> = Target { destination: MANAGER_NAME, path: MANAGER_PATH, interface: MANAGER_INTERFACE, member: "StopUnit" };
150
151pub const RESET_FAILED_UNIT: Target<'static> =
153 Target { destination: MANAGER_NAME, path: MANAGER_PATH, interface: MANAGER_INTERFACE, member: "ResetFailedUnit" };
154
155pub const GET_UNIT: Target<'static> = Target { destination: MANAGER_NAME, path: MANAGER_PATH, interface: MANAGER_INTERFACE, member: "GetUnit" };
157
158const PROPERTIES_INTERFACE: &str = "org.freedesktop.DBus.Properties";
160
161const UNIT_INTERFACE: &str = "org.freedesktop.systemd1.Unit";
163
164pub const NO_SUCH_UNIT: &str = "org.freedesktop.systemd1.NoSuchUnit";
166
167const UNKNOWN_OBJECT: &str = "org.freedesktop.DBus.Error.UnknownObject";
169
170pub fn encode_call(serial: u32, target: &Target<'_>, arguments: &[&str]) -> Vec<u8> {
180 let mut body: Writer = Writer { bytes: Vec::new() };
181 for argument in arguments {
182 body.text(argument);
183 }
184 let mut fields: Writer = Writer { bytes: vec![0; 16] };
185 fields.field(FIELD_PATH, 'o', target.path);
186 fields.field(FIELD_DESTINATION, 's', target.destination);
187 fields.field(FIELD_INTERFACE, 's', target.interface);
188 fields.field(FIELD_MEMBER, 's', target.member);
189 if !arguments.is_empty() {
190 fields.field(FIELD_SIGNATURE, 'g', &"s".repeat(arguments.len()));
191 }
192 let array_length: u32 = u32::try_from(fields.bytes.len() - 16).unwrap_or(u32::MAX);
194 fields.align(8);
195 let mut message: Vec<u8> = fields.bytes;
196 message[0] = b'l';
197 message[1] = METHOD_CALL;
198 message[2] = 0;
199 message[3] = 1;
200 message[4..8].copy_from_slice(&u32::try_from(body.bytes.len()).unwrap_or(u32::MAX).to_le_bytes());
201 message[8..12].copy_from_slice(&serial.to_le_bytes());
202 message[12..16].copy_from_slice(&array_length.to_le_bytes());
203 message.extend(body.bytes);
204 return message;
205}
206
207#[derive(Clone, Debug, Eq, PartialEq)]
209pub struct Reply {
210 pub kind: u8,
212 pub reply_serial: Option<u32>,
214 pub error_name: Option<String>,
216 pub first_text: Option<String>,
218}
219
220fn read_u32(bytes: &[u8], at: usize) -> Option<u32> {
221 return bytes.get(at..at.checked_add(4)?).map(|slice| { return u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]); });
222}
223
224fn read_text(bytes: &[u8], at: usize) -> Option<(String, usize)> {
228 let length: usize = usize::try_from(read_u32(bytes, at)?).ok()?;
229 let start: usize = at.checked_add(4)?;
230 let end: usize = start.checked_add(length)?;
231 let text: &[u8] = bytes.get(start..end)?;
232 if *bytes.get(end)? != 0 {
233 return None;
234 }
235 return Some((String::from_utf8_lossy(text).into_owned(), end.saturating_add(1)));
236}
237
238fn read_variant_text(bytes: &[u8], at: usize) -> Option<String> {
241 let length: usize = usize::from(*bytes.get(at)?);
242 let inner_start: usize = at.checked_add(1)?;
243 let inner_end: usize = inner_start.checked_add(length)?;
244 let inner: &[u8] = bytes.get(inner_start..inner_end)?;
245 if inner != b"s" || *bytes.get(inner_end)? != 0 {
246 return None;
247 }
248 return read_text(bytes, aligned(inner_end.checked_add(1)?, 4)).map(|(text, _)| { return text; });
250}
251
252fn aligned(offset: usize, boundary: usize) -> usize {
253 return offset.checked_add(boundary - 1).map_or(usize::MAX, |sum| { return sum / boundary * boundary; });
254}
255
256pub fn decode_reply(bytes: &[u8]) -> Result<Reply, CallError> {
268 let malformed = |what: &str| { return CallError::Malformed(what.to_string()); };
269 if bytes.len() < 16 || bytes[0] != b'l' {
270 return Err(malformed("the reply is not a little-endian message"));
271 }
272 let kind: u8 = bytes[1];
273 let body_length: usize = usize::try_from(read_u32(bytes, 4).ok_or_else(|| { return malformed("no body length"); })?).unwrap_or(usize::MAX);
274 let fields_length: usize = usize::try_from(read_u32(bytes, 12).ok_or_else(|| { return malformed("no header length"); })?).unwrap_or(usize::MAX);
275 let fields_end: usize = 16usize.saturating_add(fields_length);
276 let body_start: usize = aligned(fields_end, 8);
277 if bytes.len() < body_start.saturating_add(body_length) || fields_end > bytes.len() {
278 return Err(malformed("the reply is shorter than it says"));
279 }
280 let mut error_name: Option<String> = None;
281 let mut reply_serial: Option<u32> = None;
282 let mut signature: String = String::new();
283 let header: &[u8] = bytes.get(..fields_end).unwrap_or_default();
285 let body: &[u8] = bytes.get(..body_start.saturating_add(body_length)).unwrap_or_default();
286 let mut at: usize = 16;
287 while at < fields_end {
288 at = aligned(at, 8);
289 let (Some(&code), Some(&signature_length)) = (header.get(at), header.get(at.saturating_add(1))) else { break };
290 let kind_at: usize = at.saturating_add(2);
291 let Some(&value_kind) = header.get(kind_at) else { break };
292 let value_at: usize = kind_at.saturating_add(usize::from(signature_length)).saturating_add(1);
293 match (value_kind, code) {
294 (b's' | b'o', _) => {
295 let Some((text, next)) = read_text(header, aligned(value_at, 4)) else { break };
296 if code == FIELD_ERROR_NAME {
297 error_name = Some(text);
298 }
299 at = next;
300 }
301 (b'g', _) => {
302 let length: usize = usize::from(*header.get(value_at).unwrap_or(&0));
303 if code == FIELD_SIGNATURE {
304 let start: usize = value_at.saturating_add(1);
305 if header.get(start.saturating_add(length)) == Some(&0) {
307 signature = String::from_utf8_lossy(header.get(start..start.saturating_add(length)).unwrap_or_default()).into_owned();
308 }
309 }
310 at = value_at.saturating_add(length).saturating_add(2);
311 }
312 (b'u', _) => {
313 let number_at: usize = aligned(value_at, 4);
314 if code == FIELD_REPLY_SERIAL {
315 reply_serial = read_u32(header, number_at);
316 }
317 at = number_at.saturating_add(4);
318 }
319 _ => break,
320 }
321 }
322 let first_text: Option<String> = if signature.starts_with('s') || signature.starts_with('o') {
324 read_text(body, aligned(body_start, 4)).map(|(text, _)| { return text; })
325 } else if signature == "v" {
326 read_variant_text(body, body_start)
327 } else {
328 None
329 };
330 return Ok(Reply { kind, reply_serial, error_name, first_text });
331}
332
333fn external_identity() -> String {
335 let user: u32 = unsafe { libc::geteuid() };
337 return user.to_string().bytes().map(|byte| { return format!("{byte:02x}"); }).collect();
338}
339
340fn remaining(deadline: Instant) -> Result<Duration, CallError> {
342 let left: Duration = deadline.saturating_duration_since(Instant::now());
343 return if left.is_zero() { Err(CallError::TimedOut) } else { Ok(left) };
344}
345
346fn socket_failure(error: &std::io::Error) -> CallError {
348 return match error.kind() {
349 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut => CallError::TimedOut,
350 _ => CallError::Connection(error.to_string()),
351 };
352}
353
354fn read_line(stream: &mut UnixStream, deadline: Instant) -> Result<String, CallError> {
355 let mut line: Vec<u8> = Vec::new();
356 let mut one: [u8; 1] = [0];
357 while line.len() < 4096 {
358 stream.set_read_timeout(Some(remaining(deadline)?)).map_err(|error| { return CallError::Connection(error.to_string()); })?;
359 stream.read_exact(&mut one).map_err(|error| {
360 return match socket_failure(&error) {
361 CallError::TimedOut => CallError::TimedOut,
362 _ => CallError::Authentication(error.to_string()),
363 };
364 })?;
365 if one[0] == b'\n' {
366 return Ok(String::from_utf8_lossy(line.as_slice()).trim_end_matches('\r').to_string());
367 }
368 line.push(one[0]);
369 }
370 return Err(CallError::Authentication(String::from("the answer is too long")));
371}
372
373fn read_message(stream: &mut UnixStream, deadline: Instant) -> Result<Vec<u8>, CallError> {
375 let mut header: Vec<u8> = vec![0; 16];
376 stream.set_read_timeout(Some(remaining(deadline)?)).map_err(|error| { return CallError::Connection(error.to_string()); })?;
377 stream.read_exact(header.as_mut_slice()).map_err(|error| { return socket_failure(&error); })?;
378 let body_length: usize = usize::try_from(read_u32(header.as_slice(), 4).unwrap_or(0)).unwrap_or(usize::MAX);
379 let fields_length: usize = usize::try_from(read_u32(header.as_slice(), 12).unwrap_or(0)).unwrap_or(usize::MAX);
380 let rest: usize = aligned(16usize.saturating_add(fields_length), 8).saturating_add(body_length).saturating_sub(16);
381 if rest > MAX_REPLY_BYTES {
382 return Err(CallError::Malformed(String::from("the reply is larger than this client reads")));
383 }
384 let mut remainder: Vec<u8> = vec![0; rest];
385 stream.set_read_timeout(Some(remaining(deadline)?)).map_err(|error| { return CallError::Connection(error.to_string()); })?;
386 stream.read_exact(remainder.as_mut_slice()).map_err(|error| { return socket_failure(&error); })?;
387 header.extend(remainder);
388 return Ok(header);
389}
390
391pub fn session_bus_path(address: Option<&str>, runtime_dir: Option<&Path>) -> Option<std::path::PathBuf> {
393 let from_address: Option<std::path::PathBuf> = address.and_then(|list| {
394 return list.split(';').find_map(|entry| {
395 let rest: &str = entry.strip_prefix("unix:")?;
396 let path: &str = rest.split(',').find_map(|option| { return option.strip_prefix("path="); })?;
397 return if path.contains('%') || path.is_empty() { None } else { Some(std::path::PathBuf::from(path)) };
399 });
400 });
401 return from_address.or_else(|| { return runtime_dir.map(|directory| { return directory.join("bus"); }); });
402}
403
404pub struct Bus {
406 stream: UnixStream,
407 deadline: Instant,
408 serial: u32,
409}
410
411impl Bus {
413 pub fn connect(socket: &Path, wait: Duration) -> Result<Bus, CallError> {
423 let deadline: Instant = Instant::now() + wait;
424 let mut stream: UnixStream = UnixStream::connect(socket).map_err(|error| { return CallError::Connection(error.to_string()); })?;
425 stream.set_write_timeout(Some(wait)).map_err(|error| { return CallError::Connection(error.to_string()); })?;
426 let mut hello: Vec<u8> = vec![0];
427 hello.extend(format!("AUTH EXTERNAL {}\r\n", external_identity()).as_bytes());
428 stream.write_all(hello.as_slice()).map_err(|error| { return CallError::Authentication(error.to_string()); })?;
429 let answer: String = read_line(&mut stream, deadline)?;
430 if !answer.starts_with("OK ") {
431 return Err(CallError::Authentication(answer));
432 }
433 stream.write_all(b"BEGIN\r\n").map_err(|error| { return CallError::Authentication(error.to_string()); })?;
434 let mut bus: Bus = Bus { stream, deadline, serial: 0 };
435 bus.call(&HELLO, &[])?;
436 return Ok(bus);
437 }
438
439 pub fn within(&mut self, wait: Duration) {
451 self.deadline = Instant::now() + wait;
452 }
453
454 pub fn call(&mut self, target: &Target<'_>, arguments: &[&str]) -> Result<Reply, CallError> {
465 self.serial += 1;
466 let serial: u32 = self.serial;
467 self.stream.write_all(encode_call(serial, target, arguments).as_slice()).map_err(|error| { return socket_failure(&error); })?;
468 loop {
469 let reply: Reply = decode_reply(read_message(&mut self.stream, self.deadline)?.as_slice())?;
470 if reply.reply_serial != Some(serial) || !matches!(reply.kind, METHOD_RETURN | ERROR) {
471 continue;
472 }
473 return match (reply.kind, reply.error_name.clone()) {
474 (METHOD_RETURN, _) => Ok(reply),
475 (_, Some(name)) => Err(CallError::Manager { name, text: reply.first_text.unwrap_or_default() }),
476 _ => Err(CallError::Malformed(String::from("an error reply without a name"))),
477 };
478 }
479 }
480
481 pub fn reload(&mut self) -> Result<(), CallError> {
483 return self.call(&RELOAD, &[]).map(drop);
484 }
485
486 pub fn reset_failed(&mut self, name: &str) -> Result<(), CallError> {
488 return self.call(&RESET_FAILED_UNIT, &[name]).map(drop);
489 }
490
491 pub fn stop_unit(&mut self, name: &str) -> Result<(), CallError> {
493 return self.call(&STOP_UNIT, &[name, "replace"]).map(drop);
494 }
495
496 pub fn active_state(&mut self, name: &str) -> Result<Option<String>, CallError> {
509 let path: String = match self.call(&GET_UNIT, &[name]) {
510 Ok(reply) => reply.first_text.ok_or_else(|| { return CallError::Malformed(String::from("GetUnit answered without a path")); })?,
511 Err(CallError::Manager { name: error, .. }) if error == NO_SUCH_UNIT => return Ok(None),
512 Err(error) => return Err(error),
513 };
514 let property: Target<'_> = Target { destination: MANAGER_NAME, path: path.as_str(), interface: PROPERTIES_INTERFACE, member: "Get" };
515 return match self.call(&property, &[UNIT_INTERFACE, "ActiveState"]) {
516 Ok(reply) => reply
517 .first_text
518 .map(Some)
519 .ok_or_else(|| { return CallError::Malformed(String::from("the state property answered without text")); }),
520 Err(CallError::Manager { name: error, .. }) if error == NO_SUCH_UNIT || error == UNKNOWN_OBJECT => Ok(None),
521 Err(error) => Err(error),
522 };
523 }
524
525 pub fn start_unit(&mut self, name: &str) -> Result<(), CallError> {
527 return self.call(&START_UNIT, &[name, "replace"]).map(drop);
528 }
529}
530
531#[cfg(test)]
533#[path = "systemd_dbus_tests.rs"]
534mod tests;