Skip to main content

zbus/address/transport/
unixexec.rs

1use std::{
2    borrow::BorrowMut, ffi::OsString, fmt::Display, os::unix::ffi::OsStrExt, path::PathBuf,
3    process::Stdio, sync::Arc,
4};
5
6use crate::{Address, 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(
71        &self,
72        address: &Address,
73    ) -> crate::Result<crate::connection::socket::Command> {
74        let mut child = Command::for_unixexec(self)
75            .stdin(Stdio::piped())
76            .stdout(Stdio::piped())
77            .stderr(Stdio::inherit())
78            .spawn()
79            .map_err(|e| crate::Error::Connection(Arc::new(e), address.clone()))?;
80
81        child.borrow_mut().try_into()
82    }
83}
84
85impl Display for Unixexec {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        f.write_str("unixexec:path=")?;
88        encode_percents(f, self.path.as_os_str().as_bytes())?;
89
90        if let Some(arg0) = self.arg0.as_ref() {
91            f.write_str(",argv0=")?;
92            encode_percents(f, arg0.as_bytes())?;
93        }
94
95        for (index, arg) in self.args.iter().enumerate() {
96            write!(f, ",argv{}=", index + 1)?;
97            encode_percents(f, arg.as_bytes())?;
98        }
99
100        Ok(())
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use crate::address::{Address, transport::Transport};
107
108    #[test]
109    fn connect() {
110        let addr: Address = "unixexec:path=echo,argv1=hello,argv2=world"
111            .try_into()
112            .unwrap();
113        let unixexec = match addr.transport() {
114            Transport::Unixexec(unixexec) => unixexec,
115            _ => unreachable!(),
116        };
117        crate::utils::block_on(unixexec.connect(&addr)).unwrap();
118    }
119}