use std::{
ffi::OsString,
fmt::Display,
os::unix::{ffi::OsStrExt, process::CommandExt},
path::PathBuf,
process::Command,
sync::Arc,
};
use super::encode_percents;
use crate::{
Address, Result,
connection::socket::BoxedSplit,
runtime::{Runtime, process},
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Unixexec {
path: PathBuf,
arg0: Option<OsString>,
args: Vec<OsString>,
}
impl Unixexec {
pub fn new(path: PathBuf, arg0: Option<OsString>, args: Vec<OsString>) -> Self {
Self { path, arg0, args }
}
pub(super) fn from_options(opts: std::collections::HashMap<&str, &str>) -> crate::Result<Self> {
let Some(path) = opts.get("path") else {
return Err(crate::Error::Address(
"unixexec address is missing `path`".to_owned(),
));
};
let arg0 = opts.get("argv0").map(OsString::from);
let mut args: Vec<OsString> = Vec::new();
let mut arg_index = 1;
while let Some(arg) = opts.get(format!("argv{arg_index}").as_str()) {
args.push(OsString::from(arg));
arg_index += 1;
}
Ok(Self::new(PathBuf::from(path), arg0, args))
}
pub fn path(&self) -> &PathBuf {
&self.path
}
pub fn arg0(&self) -> Option<&OsString> {
self.arg0.as_ref()
}
pub fn args(&self) -> &[OsString] {
self.args.as_ref()
}
pub(super) async fn connect(&self, address: &Address, runtime: &Runtime) -> Result<BoxedSplit> {
process::spawn(runtime, self.command())
.map(process::Child::into_split)
.map_err(|e| crate::Error::Connection(Arc::new(e), Box::new(address.clone())))
}
fn command(&self) -> Command {
let mut command = Command::new(&self.path);
command.args(&self.args);
if let Some(arg0) = &self.arg0 {
command.arg0(arg0);
}
command
}
}
impl Display for Unixexec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("unixexec:path=")?;
encode_percents(f, self.path.as_os_str().as_bytes())?;
if let Some(arg0) = self.arg0.as_ref() {
f.write_str(",argv0=")?;
encode_percents(f, arg0.as_bytes())?;
}
for (index, arg) in self.args.iter().enumerate() {
write!(f, ",argv{}=", index + 1)?;
encode_percents(f, arg.as_bytes())?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use ntest::timeout;
use crate::{
address::{Address, transport::Transport},
runtime::test_runtime::under_every_runtime,
};
#[test]
#[timeout(15000)]
fn connect() {
under_every_runtime(|runtime| async move {
let address: Address = "unixexec:path=echo,argv1=hello,argv2=world"
.try_into()
.unwrap();
let Transport::Unixexec(unixexec) = address.transport() else {
unreachable!("the address names a unixexec transport")
};
unixexec.connect(&address, &runtime).await.unwrap();
});
}
#[test]
#[timeout(15000)]
fn a_bus_connection_over_a_helper_process() {
use crate::{
Error, conn::Builder, connection::Connection, fdo, runtime::test_runtime::TestRuntime,
};
let built = futures_lite::future::block_on(
Builder::address("unixexec:path=systemd-stdio-bridge")
.runtime(TestRuntime::new())
.build(),
);
let connection: Connection = match built {
Ok(connection) => connection,
Err(Error::Connection(e, _)) if e.kind() == std::io::ErrorKind::NotFound => return,
Err(e) => panic!("{e}"),
};
let called = futures_lite::future::block_on(connection.call_method(
Some("org.freedesktop.DBus"),
"/org/freedesktop/DBus",
Some("org.freedesktop.DBus"),
"Hello",
&(),
));
let Err(Error::FDO(e)) = called else {
panic!("got {called:?}");
};
let fdo::Error::Failed(_) = *e else {
panic!("got {e:?}");
};
}
}