zbus/address/transport/
unixexec.rs

1use std::{
2    borrow::BorrowMut, ffi::OsString, fmt::Display, os::unix::ffi::OsStrExt, path::PathBuf,
3    process::Stdio,
4};
5
6use crate::process::Command;
7
8use super::encode_percents;
9
10/// `unixexec:` D-Bus transport.
11///
12/// <https://dbus.freedesktop.org/doc/dbus-specification.html#transports-exec>
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct Unixexec {
15    path: PathBuf,
16    arg0: Option<OsString>,
17    args: Vec<OsString>,
18}
19
20impl Unixexec {
21    /// Create a new unixexec transport with the given path and arguments.
22    pub fn new(path: PathBuf, arg0: Option<OsString>, args: Vec<OsString>) -> Self {
23        Self { path, arg0, args }
24    }
25
26    pub(super) fn from_options(opts: std::collections::HashMap<&str, &str>) -> crate::Result<Self> {
27        let Some(path) = opts.get("path") else {
28            return Err(crate::Error::Address(
29                "unixexec address is missing `path`".to_owned(),
30            ));
31        };
32
33        let arg0 = opts.get("argv0").map(OsString::from);
34
35        let mut args: Vec<OsString> = Vec::new();
36        let mut arg_index = 1;
37        while let Some(arg) = opts.get(format!("argv{arg_index}").as_str()) {
38            args.push(OsString::from(arg));
39            arg_index += 1;
40        }
41
42        Ok(Self::new(PathBuf::from(path), arg0, args))
43    }
44
45    /// Binary to execute.
46    ///
47    /// Path of the binary to execute, either an absolute path or a binary name that is searched for
48    /// in the default search path of the OS. This corresponds to the first argument of execlp().
49    /// This key is mandatory.
50    pub fn path(&self) -> &PathBuf {
51        &self.path
52    }
53
54    /// The executable argument.
55    ///
56    /// The program name to use when executing the binary. If omitted the same
57    /// value as specified for path will be used. This corresponds to the
58    /// second argument of execlp().
59    pub fn arg0(&self) -> Option<&OsString> {
60        self.arg0.as_ref()
61    }
62
63    /// Arguments.
64    ///
65    /// Arguments to pass to the binary.
66    pub fn args(&self) -> &[OsString] {
67        self.args.as_ref()
68    }
69
70    pub(super) async fn connect(&self) -> crate::Result<crate::connection::socket::Command> {
71        Command::for_unixexec(self)
72            .stdin(Stdio::piped())
73            .stdout(Stdio::piped())
74            .stderr(Stdio::inherit())
75            .spawn()?
76            .borrow_mut()
77            .try_into()
78    }
79}
80
81impl Display for Unixexec {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        f.write_str("unixexec:path=")?;
84        encode_percents(f, self.path.as_os_str().as_bytes())?;
85
86        if let Some(arg0) = self.arg0.as_ref() {
87            f.write_str(",argv0=")?;
88            encode_percents(f, arg0.as_bytes())?;
89        }
90
91        for (index, arg) in self.args.iter().enumerate() {
92            write!(f, ",argv{}=", index + 1)?;
93            encode_percents(f, arg.as_bytes())?;
94        }
95
96        Ok(())
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use crate::address::{transport::Transport, Address};
103
104    #[test]
105    fn connect() {
106        let addr: Address = "unixexec:path=echo,argv1=hello,argv2=world"
107            .try_into()
108            .unwrap();
109        let unixexec = match addr.transport() {
110            Transport::Unixexec(unixexec) => unixexec,
111            _ => unreachable!(),
112        };
113        crate::utils::block_on(unixexec.connect()).unwrap();
114    }
115}