1use std::io::{Read, Write};
19use std::os::unix::net::UnixStream;
20use std::path::Path;
21use std::time::{Duration, Instant};
22
23pub const MANAGER_PATH: &str = "/org/freedesktop/systemd1";
25
26pub const MANAGER_INTERFACE: &str = "org.freedesktop.systemd1.Manager";
28
29pub const EXCHANGE_WAIT: Duration = Duration::from_secs(20);
32
33const BUS_NAME: &str = "org.freedesktop.DBus";
35const BUS_PATH: &str = "/org/freedesktop/DBus";
36
37pub const MANAGER_NAME: &str = "org.freedesktop.systemd1";
39
40const MAX_REPLY_BYTES: usize = 1 << 20;
42
43const METHOD_CALL: u8 = 1;
45
46const METHOD_RETURN: u8 = 2;
48
49const ERROR: u8 = 3;
51
52const FIELD_REPLY_SERIAL: u8 = 5;
54
55const FIELD_PATH: u8 = 1;
57const FIELD_INTERFACE: u8 = 2;
58const FIELD_MEMBER: u8 = 3;
59const FIELD_ERROR_NAME: u8 = 4;
60const FIELD_DESTINATION: u8 = 6;
61const FIELD_SIGNATURE: u8 = 8;
62
63#[derive(Clone, Debug, Eq, PartialEq)]
65pub enum CallError {
66 Connection(String),
68 Authentication(String),
70 Manager {
72 name: String,
74 text: String,
76 },
77 Malformed(String),
79}
80
81struct Writer {
83 bytes: Vec<u8>,
84}
85
86impl Writer {
87 fn align(&mut self, boundary: usize) {
88 while self.bytes.len() % boundary != 0 {
89 self.bytes.push(0);
90 }
91 }
92
93 fn unsigned(&mut self, value: u32) {
94 self.align(4);
95 self.bytes.extend(value.to_le_bytes());
96 }
97
98 fn text(&mut self, value: &str) {
99 self.unsigned(u32::try_from(value.len()).unwrap_or(u32::MAX));
100 self.bytes.extend(value.as_bytes());
101 self.bytes.push(0);
102 }
103
104 fn signature(&mut self, value: &str) {
105 self.bytes.push(u8::try_from(value.len()).unwrap_or(u8::MAX));
106 self.bytes.extend(value.as_bytes());
107 self.bytes.push(0);
108 }
109
110 fn field(&mut self, code: u8, kind: char, value: &str) {
112 self.align(8);
113 self.bytes.push(code);
114 self.signature(&kind.to_string());
115 if kind == 'g' {
116 self.signature(value);
117 } else {
118 self.text(value);
119 }
120 }
121}
122
123#[derive(Clone, Copy, Debug, Eq, PartialEq)]
125pub struct Target {
126 pub destination: &'static str,
128 pub path: &'static str,
130 pub interface: &'static str,
132 pub member: &'static str,
134}
135
136pub const HELLO: Target = Target { destination: BUS_NAME, path: BUS_PATH, interface: BUS_NAME, member: "Hello" };
138
139pub const RELOAD: Target = Target { destination: MANAGER_NAME, path: MANAGER_PATH, interface: MANAGER_INTERFACE, member: "Reload" };
141
142pub const START_UNIT: Target = Target { destination: MANAGER_NAME, path: MANAGER_PATH, interface: MANAGER_INTERFACE, member: "StartUnit" };
144
145pub fn encode_call(serial: u32, target: &Target, arguments: &[&str]) -> Vec<u8> {
155 let mut body: Writer = Writer { bytes: Vec::new() };
156 for argument in arguments {
157 body.text(argument);
158 }
159 let mut fields: Writer = Writer { bytes: vec![0; 16] };
160 fields.field(FIELD_PATH, 'o', target.path);
161 fields.field(FIELD_DESTINATION, 's', target.destination);
162 fields.field(FIELD_INTERFACE, 's', target.interface);
163 fields.field(FIELD_MEMBER, 's', target.member);
164 if !arguments.is_empty() {
165 fields.field(FIELD_SIGNATURE, 'g', &"s".repeat(arguments.len()));
166 }
167 let array_length: u32 = u32::try_from(fields.bytes.len() - 16).unwrap_or(u32::MAX);
169 fields.align(8);
170 let mut message: Vec<u8> = fields.bytes;
171 message[0] = b'l';
172 message[1] = METHOD_CALL;
173 message[2] = 0;
174 message[3] = 1;
175 message[4..8].copy_from_slice(&u32::try_from(body.bytes.len()).unwrap_or(u32::MAX).to_le_bytes());
176 message[8..12].copy_from_slice(&serial.to_le_bytes());
177 message[12..16].copy_from_slice(&array_length.to_le_bytes());
178 message.extend(body.bytes);
179 return message;
180}
181
182#[derive(Clone, Debug, Eq, PartialEq)]
184pub struct Reply {
185 pub kind: u8,
187 pub reply_serial: Option<u32>,
189 pub error_name: Option<String>,
191 pub first_text: Option<String>,
193}
194
195fn read_u32(bytes: &[u8], at: usize) -> Option<u32> {
196 return bytes.get(at..at.checked_add(4)?).map(|slice| { return u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]); });
197}
198
199fn read_text(bytes: &[u8], at: usize) -> Option<(String, usize)> {
200 let length: usize = usize::try_from(read_u32(bytes, at)?).ok()?;
201 let start: usize = at.checked_add(4)?;
202 let text: &[u8] = bytes.get(start..start.checked_add(length)?)?;
203 return Some((String::from_utf8_lossy(text).into_owned(), start.saturating_add(length).saturating_add(1)));
204}
205
206fn aligned(offset: usize, boundary: usize) -> usize {
207 return offset.checked_add(boundary - 1).map_or(usize::MAX, |sum| { return sum / boundary * boundary; });
208}
209
210pub fn decode_reply(bytes: &[u8]) -> Result<Reply, CallError> {
222 let malformed = |what: &str| { return CallError::Malformed(what.to_string()); };
223 if bytes.len() < 16 || bytes[0] != b'l' {
224 return Err(malformed("the reply is not a little-endian message"));
225 }
226 let kind: u8 = bytes[1];
227 let body_length: usize = usize::try_from(read_u32(bytes, 4).ok_or_else(|| { return malformed("no body length"); })?).unwrap_or(usize::MAX);
228 let fields_length: usize = usize::try_from(read_u32(bytes, 12).ok_or_else(|| { return malformed("no header length"); })?).unwrap_or(usize::MAX);
229 let fields_end: usize = 16usize.saturating_add(fields_length);
230 let body_start: usize = aligned(fields_end, 8);
231 if bytes.len() < body_start.saturating_add(body_length) || fields_end > bytes.len() {
232 return Err(malformed("the reply is shorter than it says"));
233 }
234 let mut error_name: Option<String> = None;
235 let mut reply_serial: Option<u32> = None;
236 let mut signature: String = String::new();
237 let mut at: usize = 16;
238 while at < fields_end {
239 at = aligned(at, 8);
240 let (Some(&code), Some(&signature_length)) = (bytes.get(at), bytes.get(at.saturating_add(1))) else { break };
241 let kind_at: usize = at.saturating_add(2);
242 let Some(&value_kind) = bytes.get(kind_at) else { break };
243 let value_at: usize = kind_at.saturating_add(usize::from(signature_length)).saturating_add(1);
244 match (value_kind, code) {
245 (b's' | b'o', _) => {
246 let Some((text, next)) = read_text(bytes, aligned(value_at, 4)) else { break };
247 if code == FIELD_ERROR_NAME {
248 error_name = Some(text);
249 }
250 at = next;
251 }
252 (b'g', _) => {
253 let length: usize = usize::from(*bytes.get(value_at).unwrap_or(&0));
254 if code == FIELD_SIGNATURE {
255 let start: usize = value_at.saturating_add(1);
256 signature = String::from_utf8_lossy(bytes.get(start..start.saturating_add(length)).unwrap_or_default()).into_owned();
257 }
258 at = value_at.saturating_add(length).saturating_add(2);
259 }
260 (b'u', _) => {
261 let number_at: usize = aligned(value_at, 4);
262 if code == FIELD_REPLY_SERIAL {
263 reply_serial = read_u32(bytes, number_at);
264 }
265 at = number_at.saturating_add(4);
266 }
267 _ => break,
268 }
269 }
270 let first_text: Option<String> = if signature.starts_with('s') {
271 read_text(bytes, aligned(body_start, 4)).map(|(text, _)| { return text; })
272 } else {
273 None
274 };
275 return Ok(Reply { kind, reply_serial, error_name, first_text });
276}
277
278fn external_identity() -> String {
280 let user: u32 = unsafe { libc::geteuid() };
282 return user.to_string().bytes().map(|byte| { return format!("{byte:02x}"); }).collect();
283}
284
285fn remaining(deadline: Instant) -> Result<Duration, CallError> {
287 let left: Duration = deadline.saturating_duration_since(Instant::now());
288 return if left.is_zero() { Err(CallError::Connection(String::from("the bus did not answer in time"))) } else { Ok(left) };
289}
290
291fn read_line(stream: &mut UnixStream, deadline: Instant) -> Result<String, CallError> {
292 let mut line: Vec<u8> = Vec::new();
293 let mut one: [u8; 1] = [0];
294 while line.len() < 4096 {
295 stream.set_read_timeout(Some(remaining(deadline)?)).map_err(|error| { return CallError::Connection(error.to_string()); })?;
296 stream.read_exact(&mut one).map_err(|error| { return CallError::Authentication(error.to_string()); })?;
297 if one[0] == b'\n' {
298 return Ok(String::from_utf8_lossy(line.as_slice()).trim_end_matches('\r').to_string());
299 }
300 line.push(one[0]);
301 }
302 return Err(CallError::Authentication(String::from("the answer is too long")));
303}
304
305fn read_message(stream: &mut UnixStream, deadline: Instant) -> Result<Vec<u8>, CallError> {
307 let mut header: Vec<u8> = vec![0; 16];
308 stream.set_read_timeout(Some(remaining(deadline)?)).map_err(|error| { return CallError::Connection(error.to_string()); })?;
309 stream.read_exact(header.as_mut_slice()).map_err(|error| { return CallError::Connection(error.to_string()); })?;
310 let body_length: usize = usize::try_from(read_u32(header.as_slice(), 4).unwrap_or(0)).unwrap_or(usize::MAX);
311 let fields_length: usize = usize::try_from(read_u32(header.as_slice(), 12).unwrap_or(0)).unwrap_or(usize::MAX);
312 let rest: usize = aligned(16usize.saturating_add(fields_length), 8).saturating_add(body_length).saturating_sub(16);
313 if rest > MAX_REPLY_BYTES {
314 return Err(CallError::Malformed(String::from("the reply is larger than this client reads")));
315 }
316 let mut remainder: Vec<u8> = vec![0; rest];
317 stream.set_read_timeout(Some(remaining(deadline)?)).map_err(|error| { return CallError::Connection(error.to_string()); })?;
318 stream.read_exact(remainder.as_mut_slice()).map_err(|error| { return CallError::Connection(error.to_string()); })?;
319 header.extend(remainder);
320 return Ok(header);
321}
322
323pub fn session_bus_path(address: Option<&str>, runtime_dir: Option<&Path>) -> Option<std::path::PathBuf> {
325 let from_address: Option<std::path::PathBuf> = address.and_then(|list| {
326 return list.split(';').find_map(|entry| {
327 let rest: &str = entry.strip_prefix("unix:")?;
328 let path: &str = rest.split(',').find_map(|option| { return option.strip_prefix("path="); })?;
329 return if path.contains('%') || path.is_empty() { None } else { Some(std::path::PathBuf::from(path)) };
331 });
332 });
333 return from_address.or_else(|| { return runtime_dir.map(|directory| { return directory.join("bus"); }); });
334}
335
336pub struct Bus {
338 stream: UnixStream,
339 deadline: Instant,
340 serial: u32,
341}
342
343impl Bus {
345 pub fn connect(socket: &Path, wait: Duration) -> Result<Bus, CallError> {
355 let deadline: Instant = Instant::now() + wait;
356 let mut stream: UnixStream = UnixStream::connect(socket).map_err(|error| { return CallError::Connection(error.to_string()); })?;
357 stream.set_write_timeout(Some(wait)).map_err(|error| { return CallError::Connection(error.to_string()); })?;
358 let mut hello: Vec<u8> = vec![0];
359 hello.extend(format!("AUTH EXTERNAL {}\r\n", external_identity()).as_bytes());
360 stream.write_all(hello.as_slice()).map_err(|error| { return CallError::Authentication(error.to_string()); })?;
361 let answer: String = read_line(&mut stream, deadline)?;
362 if !answer.starts_with("OK ") {
363 return Err(CallError::Authentication(answer));
364 }
365 stream.write_all(b"BEGIN\r\n").map_err(|error| { return CallError::Authentication(error.to_string()); })?;
366 let mut bus: Bus = Bus { stream, deadline, serial: 0 };
367 bus.call(&HELLO, &[])?;
368 return Ok(bus);
369 }
370
371 pub fn call(&mut self, target: &Target, arguments: &[&str]) -> Result<Reply, CallError> {
382 self.serial += 1;
383 let serial: u32 = self.serial;
384 self.stream.write_all(encode_call(serial, target, arguments).as_slice()).map_err(|error| { return CallError::Connection(error.to_string()); })?;
385 loop {
386 let reply: Reply = decode_reply(read_message(&mut self.stream, self.deadline)?.as_slice())?;
387 if reply.reply_serial != Some(serial) || !matches!(reply.kind, METHOD_RETURN | ERROR) {
388 continue;
389 }
390 return match (reply.kind, reply.error_name.clone()) {
391 (METHOD_RETURN, _) => Ok(reply),
392 (_, Some(name)) => Err(CallError::Manager { name, text: reply.first_text.unwrap_or_default() }),
393 _ => Err(CallError::Malformed(String::from("an error reply without a name"))),
394 };
395 }
396 }
397
398 pub fn reload(&mut self) -> Result<(), CallError> {
400 return self.call(&RELOAD, &[]).map(drop);
401 }
402
403 pub fn start_unit(&mut self, name: &str) -> Result<(), CallError> {
405 return self.call(&START_UNIT, &[name, "replace"]).map(drop);
406 }
407}
408
409#[cfg(test)]
411#[path = "systemd_dbus_tests.rs"]
412mod tests;