From b02f508dc4618396800145db29cd5ce9d88daaa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20H=C4=83loiu?= Date: Wed, 4 Sep 2024 20:20:17 +0100 Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20=20zb:=20Fix=20typo=20?= =?UTF-8?q?in=20transport=20module=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zbus/src/address/transport/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zbus/src/address/transport/mod.rs b/zbus/src/address/transport/mod.rs index 43fe78320..da8418ac3 100644 --- a/zbus/src/address/transport/mod.rs +++ b/zbus/src/address/transport/mod.rs @@ -1,6 +1,6 @@ //! D-Bus transport Information module. //! -//! This module provides the trasport information for D-Bus addresses. +//! This module provides the transport information for D-Bus addresses. #[cfg(windows)] use crate::win32::autolaunch_bus_address; From 5b029217c96ac7a074b3381e1a26ad011f9c50df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20H=C4=83loiu?= Date: Thu, 12 Sep 2024 22:06:16 +0100 Subject: [PATCH 2/6] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20zb:=20Enable=20proc?= =?UTF-8?q?ess=20abstraction=20for=20all=20`unix`=20targets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `process` abstraction will be used (on `unix` targets) for spawning child processes when implementing the `unixexec` D-Bus transport. --- zbus/Cargo.toml | 2 +- zbus/src/abstractions/mod.rs | 4 ++-- zbus/src/abstractions/process.rs | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/zbus/Cargo.toml b/zbus/Cargo.toml index ff36e36f9..f039b2834 100644 --- a/zbus/Cargo.toml +++ b/zbus/Cargo.toml @@ -110,7 +110,7 @@ nix = { version = "0.29", default-features = false, features = [ "user", ] } -[target.'cfg(target_os = "macos")'.dependencies] +[target.'cfg(target_family = "unix")'.dependencies] # FIXME: This should only be enabled if async-io feature is enabled but currently # Cargo doesn't provide a way to do that for only specific target OS: https://github.com/rust-lang/cargo/issues/1197. async-process = "2.2.2" diff --git a/zbus/src/abstractions/mod.rs b/zbus/src/abstractions/mod.rs index 06c20eec2..f7009fbdb 100644 --- a/zbus/src/abstractions/mod.rs +++ b/zbus/src/abstractions/mod.rs @@ -8,6 +8,6 @@ mod async_drop; pub(crate) mod async_lock; pub use async_drop::*; -// Not macOS-specific itself but only used on macOS. -#[cfg(target_os = "macos")] +// Not unix-specific itself but only used on unix. +#[cfg(target_family = "unix")] pub(crate) mod process; diff --git a/zbus/src/abstractions/process.rs b/zbus/src/abstractions/process.rs index 53f1380b9..c46dbb36e 100644 --- a/zbus/src/abstractions/process.rs +++ b/zbus/src/abstractions/process.rs @@ -1,6 +1,8 @@ +#[cfg(target_os = "macos")] use std::{ffi::OsStr, io::Error, process::Output}; /// An asynchronous wrapper around running and getting command output +#[cfg(target_os = "macos")] pub async fn run(program: S, args: I) -> Result where I: IntoIterator, From 6b4ebc329d81d9e03c0fb6bdb769fd4c27ebd7fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20H=C4=83loiu?= Date: Mon, 9 Sep 2024 14:35:42 +0100 Subject: [PATCH 3/6] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20zb:=20Add=20thin=20?= =?UTF-8?q?wrapper=20over=20async=20`Command`=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most of the newly introduced `Command` methods are currently unused, but they will be needed when implementing the `unixexec` transport. --- zbus/src/abstractions/process.rs | 91 +++++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 12 deletions(-) diff --git a/zbus/src/abstractions/process.rs b/zbus/src/abstractions/process.rs index c46dbb36e..da4a1ed33 100644 --- a/zbus/src/abstractions/process.rs +++ b/zbus/src/abstractions/process.rs @@ -1,5 +1,82 @@ +#[cfg(not(feature = "tokio"))] +use async_process::{unix::CommandExt, Child}; #[cfg(target_os = "macos")] -use std::{ffi::OsStr, io::Error, process::Output}; +use std::process::Output; +use std::{ffi::OsStr, io::Error, process::Stdio}; +#[cfg(feature = "tokio")] +use tokio::process::Child; + +/// A wrapper around the command API of the underlying async runtime. +pub struct Command( + #[cfg(not(feature = "tokio"))] async_process::Command, + #[cfg(feature = "tokio")] tokio::process::Command, +); + +impl Command { + /// Constructs a new `Command` for launching the program at path `program`. + pub fn new(program: S) -> Self + where + S: AsRef, + { + #[cfg(not(feature = "tokio"))] + return Self(async_process::Command::new(program)); + + #[cfg(feature = "tokio")] + return Self(tokio::process::Command::new(program)); + } + + /// Sets executable argument. + /// + /// Set the first process argument, `argv[0]`, to something other than the + /// default executable path. + pub fn arg0(&mut self, arg: S) -> &mut Self + where + S: AsRef, + { + self.0.arg0(arg); + self + } + + /// Adds multiple arguments to pass to the program. + pub fn args(&mut self, args: I) -> &mut Self + where + I: IntoIterator, + S: AsRef, + { + self.0.args(args); + self + } + + /// Executes the command as a child process, waiting for it to finish and + /// collecting all of its output. + #[cfg(target_os = "macos")] + pub async fn output(&mut self) -> Result { + self.0.output().await + } + + /// Sets configuration for the child process's standard input (stdin) handle. + pub fn stdin>(&mut self, cfg: T) -> &mut Self { + self.0.stdin(cfg); + self + } + + /// Sets configuration for the child process's standard output (stdout) handle. + pub fn stdout>(&mut self, cfg: T) -> &mut Self { + self.0.stdout(cfg); + self + } + + /// Sets configuration for the child process's standard error (stderr) handle. + pub fn stderr>(&mut self, cfg: T) -> &mut Self { + self.0.stderr(cfg); + self + } + + /// Executes the command as a child process, returning a handle to it. + pub fn spawn(&mut self) -> Result { + self.0.spawn() + } +} /// An asynchronous wrapper around running and getting command output #[cfg(target_os = "macos")] @@ -8,15 +85,5 @@ where I: IntoIterator, S: AsRef, { - #[cfg(not(feature = "tokio"))] - return async_process::Command::new(program) - .args(args) - .output() - .await; - - #[cfg(feature = "tokio")] - return tokio::process::Command::new(program) - .args(args) - .output() - .await; + Command::new(program).args(args).output().await } From f4e82da1e851349674b250659938ef6ba58c544a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20H=C4=83loiu?= Date: Fri, 13 Sep 2024 00:35:41 +0100 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=94=8C=20zb:=20Add=20`Command`=20stdi?= =?UTF-8?q?o=20socket=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This socket communicates with a spawned child process via its standard input and output streams. The main use-case of this socket is for communicating with programs such as `systemd-stdio-bridge`[0] which implement a proxy between the `stdin`/`stdout` and a D-Bus bus. [0](https://www.freedesktop.org/software/systemd/man/latest/systemd-stdio-bridge.html) --- zbus/src/connection/socket/command.rs | 137 ++++++++++++++++++++++++++ zbus/src/connection/socket/mod.rs | 4 + 2 files changed, 141 insertions(+) create mode 100644 zbus/src/connection/socket/command.rs diff --git a/zbus/src/connection/socket/command.rs b/zbus/src/connection/socket/command.rs new file mode 100644 index 000000000..0187c1d75 --- /dev/null +++ b/zbus/src/connection/socket/command.rs @@ -0,0 +1,137 @@ +use std::{io, os::fd::BorrowedFd}; + +#[cfg(not(feature = "tokio"))] +use async_process::{Child, ChildStdin, ChildStdout}; + +#[cfg(feature = "tokio")] +use tokio::{ + io::{AsyncReadExt, ReadBuf}, + process::{Child, ChildStdin, ChildStdout}, +}; + +use super::{ReadHalf, RecvmsgResult, Socket, Split, WriteHalf}; + +/// A Command stream socket. +/// +/// This socket communicates with a spawned child process via its standard input +/// and output streams. +#[derive(Debug)] +pub(crate) struct Command { + stdin: ChildStdin, + stdout: ChildStdout, +} + +impl Command { + fn into_split(self) -> (ChildStdout, ChildStdin) { + (self.stdout, self.stdin) + } +} + +impl Socket for Command { + type ReadHalf = ChildStdout; + type WriteHalf = ChildStdin; + + fn split(self) -> Split { + let (read, write) = self.into_split(); + + Split { read, write } + } +} + +impl TryFrom<&mut Child> for Command { + type Error = crate::Error; + + fn try_from(child: &mut Child) -> Result { + let stdin = child + .stdin + .take() + .ok_or(crate::Error::Failure("child stdin not found".into()))?; + + let stdout = child + .stdout + .take() + .ok_or(crate::Error::Failure("child stdout not found".into()))?; + + Ok(Command { stdin, stdout }) + } +} + +#[cfg(not(feature = "tokio"))] +#[async_trait::async_trait] +impl ReadHalf for ChildStdout { + async fn recvmsg(&mut self, buf: &mut [u8]) -> RecvmsgResult { + match futures_util::AsyncReadExt::read(&mut self, buf).await { + Err(e) => Err(e), + Ok(len) => { + #[cfg(unix)] + let ret = (len, vec![]); + #[cfg(not(unix))] + let ret = len; + Ok(ret) + } + } + } +} + +#[cfg(feature = "tokio")] +#[async_trait::async_trait] +impl ReadHalf for ChildStdout { + async fn recvmsg(&mut self, buf: &mut [u8]) -> RecvmsgResult { + let mut read_buf = ReadBuf::new(buf); + self.read_buf(&mut read_buf).await.map(|_| { + let ret = read_buf.filled().len(); + #[cfg(unix)] + let ret = (ret, vec![]); + + ret + }) + } +} + +#[cfg(not(feature = "tokio"))] +#[async_trait::async_trait] +impl WriteHalf for ChildStdin { + async fn sendmsg( + &mut self, + buf: &[u8], + #[cfg(unix)] fds: &[BorrowedFd<'_>], + ) -> io::Result { + #[cfg(unix)] + if !fds.is_empty() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "fds cannot be sent with a command stream", + )); + } + + futures_util::AsyncWriteExt::write(&mut self, buf).await + } + + async fn close(&mut self) -> io::Result<()> { + futures_util::AsyncWriteExt::close(&mut self).await + } +} + +#[cfg(feature = "tokio")] +#[async_trait::async_trait] +impl WriteHalf for ChildStdin { + async fn sendmsg( + &mut self, + buf: &[u8], + #[cfg(unix)] fds: &[BorrowedFd<'_>], + ) -> io::Result { + #[cfg(unix)] + if !fds.is_empty() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "fds cannot be sent with a command stream", + )); + } + + tokio::io::AsyncWriteExt::write(&mut self, buf).await + } + + async fn close(&mut self) -> io::Result<()> { + tokio::io::AsyncWriteExt::shutdown(&mut self).await + } +} diff --git a/zbus/src/connection/socket/mod.rs b/zbus/src/connection/socket/mod.rs index 492b33a17..618c3778c 100644 --- a/zbus/src/connection/socket/mod.rs +++ b/zbus/src/connection/socket/mod.rs @@ -6,6 +6,10 @@ pub use channel::Channel; mod split; pub use split::{BoxedSplit, Split}; +#[cfg(unix)] +pub(crate) mod command; +#[cfg(unix)] +pub(crate) use command::Command; mod tcp; mod unix; mod vsock; From cc05e6e673e235d3d56c0d1b4b3affa15b823f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20H=C4=83loiu?= Date: Fri, 13 Sep 2024 00:36:33 +0100 Subject: [PATCH 5/6] =?UTF-8?q?=E2=9C=A8=20zb:=20Add=20support=20for=20`un?= =?UTF-8?q?ixexec`=20transport?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `unixexec` transport communicates with a spawned process via its standard input and output streams. This transport can be used with out-of-process forwarder programs such as `systemd-stdio-bridge` as the basis for the D-Bus protocol. Links: - https://dbus.freedesktop.org/doc/dbus-specification.html#transports-exec - https://www.freedesktop.org/software/systemd/man/latest/systemd-stdio-bridge.html --- zbus/src/abstractions/process.rs | 14 +++ zbus/src/address/mod.rs | 12 +++ zbus/src/address/transport/mod.rs | 19 ++++ zbus/src/address/transport/unixexec.rs | 115 +++++++++++++++++++++++++ zbus/src/connection/builder.rs | 2 + 5 files changed, 162 insertions(+) create mode 100644 zbus/src/address/transport/unixexec.rs diff --git a/zbus/src/abstractions/process.rs b/zbus/src/abstractions/process.rs index da4a1ed33..292590664 100644 --- a/zbus/src/abstractions/process.rs +++ b/zbus/src/abstractions/process.rs @@ -6,6 +6,8 @@ use std::{ffi::OsStr, io::Error, process::Stdio}; #[cfg(feature = "tokio")] use tokio::process::Child; +use crate::address::transport::Unixexec; + /// A wrapper around the command API of the underlying async runtime. pub struct Command( #[cfg(not(feature = "tokio"))] async_process::Command, @@ -25,6 +27,18 @@ impl Command { return Self(tokio::process::Command::new(program)); } + /// Constructs a new `Command` from a `unixexec` address. + pub fn for_unixexec(unixexec: &Unixexec) -> Self { + let mut command = Self::new(unixexec.path()); + command.args(unixexec.args()); + + if let Some(arg0) = unixexec.arg0() { + command.arg0(arg0); + } + + command + } + /// Sets executable argument. /// /// Set the first process argument, `argv[0]`, to something other than the diff --git a/zbus/src/address/mod.rs b/zbus/src/address/mod.rs index a4bf8095e..a16d5beb2 100644 --- a/zbus/src/address/mod.rs +++ b/zbus/src/address/mod.rs @@ -182,6 +182,8 @@ mod tests { }; #[cfg(target_os = "macos")] use crate::address::transport::Launchd; + #[cfg(unix)] + use crate::address::transport::Unixexec; #[cfg(windows)] use crate::address::transport::{Autolaunch, AutolaunchScope}; use crate::{ @@ -232,6 +234,11 @@ mod tests { } _ => panic!(), } + #[cfg(unix)] + match Address::from_str("unixexec:foo=blah").unwrap_err() { + Error::Address(e) => assert_eq!(e, "unixexec address is missing `path`"), + _ => panic!(), + } assert_eq!( Address::from_str("unix:path=/tmp/dbus-foo").unwrap(), Transport::Unix(Unix::new(UnixSocket::File("/tmp/dbus-foo".into()))).into(), @@ -253,6 +260,11 @@ mod tests { .unwrap(), ); } + #[cfg(unix)] + assert_eq!( + Address::from_str("unixexec:path=/tmp/dbus-foo").unwrap(), + Transport::Unixexec(Unixexec::new("/tmp/dbus-foo".into(), None, Vec::new())).into(), + ); assert_eq!( Address::from_str("tcp:host=localhost,port=4142").unwrap(), Transport::Tcp(Tcp::new("localhost", 4142)).into(), diff --git a/zbus/src/address/transport/mod.rs b/zbus/src/address/transport/mod.rs index da8418ac3..44b95564b 100644 --- a/zbus/src/address/transport/mod.rs +++ b/zbus/src/address/transport/mod.rs @@ -2,6 +2,8 @@ //! //! This module provides the transport information for D-Bus addresses. +#[cfg(unix)] +use crate::connection::socket::Command; #[cfg(windows)] use crate::win32::autolaunch_bus_address; use crate::{Error, Result}; @@ -20,6 +22,10 @@ use tokio_vsock::VsockStream; use uds_windows::UnixStream; #[cfg(all(feature = "vsock", not(feature = "tokio")))] use vsock::VsockStream; +#[cfg(unix)] +mod unixexec; +#[cfg(unix)] +pub use unixexec::Unixexec; use std::{ fmt::{Display, Formatter}, @@ -77,6 +83,9 @@ pub enum Transport { /// The type of `stream` is `vsock::VsockStream` with the `vsock` feature and /// `tokio_vsock::VsockStream` with the `tokio-vsock` feature. Vsock(Vsock), + /// A `unixexec` address. + #[cfg(unix)] + Unixexec(Unixexec), } impl Transport { @@ -136,6 +145,8 @@ impl Transport { } } } + #[cfg(unix)] + Transport::Unixexec(unixexec) => unixexec.connect().await.map(Stream::Unixexec), #[cfg(all(feature = "vsock", not(feature = "tokio")))] Transport::Vsock(addr) => { let stream = VsockStream::connect_with_cid_port(addr.cid(), addr.port())?; @@ -213,6 +224,8 @@ impl Transport { pub(super) fn from_options(transport: &str, options: HashMap<&str, &str>) -> Result { match transport { "unix" => Unix::from_options(options).map(Self::Unix), + #[cfg(unix)] + "unixexec" => Unixexec::from_options(options).map(Self::Unixexec), "tcp" => Tcp::from_options(options, false).map(Self::Tcp), "nonce-tcp" => Tcp::from_options(options, true).map(Self::Tcp), #[cfg(any( @@ -236,6 +249,8 @@ impl Transport { #[derive(Debug)] pub(crate) enum Stream { Unix(Async), + #[cfg(unix)] + Unixexec(Command), Tcp(Async), #[cfg(feature = "vsock")] Vsock(Async), @@ -246,6 +261,8 @@ pub(crate) enum Stream { pub(crate) enum Stream { #[cfg(unix)] Unix(tokio::net::UnixStream), + #[cfg(unix)] + Unixexec(Command), Tcp(TcpStream), #[cfg(feature = "tokio-vsock")] Vsock(VsockStream), @@ -336,6 +353,8 @@ impl Display for Transport { match self { Self::Tcp(tcp) => write!(f, "{}", tcp)?, Self::Unix(unix) => write!(f, "{}", unix)?, + #[cfg(unix)] + Self::Unixexec(unixexec) => write!(f, "{}", unixexec)?, #[cfg(any( all(feature = "vsock", not(feature = "tokio")), feature = "tokio-vsock" diff --git a/zbus/src/address/transport/unixexec.rs b/zbus/src/address/transport/unixexec.rs new file mode 100644 index 000000000..61088c41e --- /dev/null +++ b/zbus/src/address/transport/unixexec.rs @@ -0,0 +1,115 @@ +use std::{ + borrow::BorrowMut, ffi::OsString, fmt::Display, os::unix::ffi::OsStrExt, path::PathBuf, + process::Stdio, +}; + +use crate::process::Command; + +use super::encode_percents; + +/// `unixexec:` D-Bus transport. +/// +/// +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Unixexec { + path: PathBuf, + arg0: Option, + args: Vec, +} + +impl Unixexec { + /// Create a new unixexec transport with the given path and arguments. + pub fn new(path: PathBuf, arg0: Option, args: Vec) -> Self { + Self { path, arg0, args } + } + + pub(super) fn from_options(opts: std::collections::HashMap<&str, &str>) -> crate::Result { + 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 = 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)) + } + + /// Binary to execute. + /// + /// Path of the binary to execute, either an absolute path or a binary name that is searched for + /// in the default search path of the OS. This corresponds to the first argument of execlp(). + /// This key is mandatory. + pub fn path(&self) -> &PathBuf { + &self.path + } + + /// The executable argument. + /// + /// The program name to use when executing the binary. If omitted the same + /// value as specified for path will be used. This corresponds to the + /// second argument of execlp(). + pub fn arg0(&self) -> Option<&OsString> { + self.arg0.as_ref() + } + + /// Arguments. + /// + /// Arguments to pass to the binary. + pub fn args(&self) -> &[OsString] { + self.args.as_ref() + } + + pub(super) async fn connect(&self) -> crate::Result { + Command::for_unixexec(self) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn()? + .borrow_mut() + .try_into() + } +} + +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 crate::address::{transport::Transport, Address}; + + #[test] + fn connect() { + let addr: Address = "unixexec:path=echo,argv1=hello,argv2=world" + .try_into() + .unwrap(); + let unixexec = match addr.transport() { + Transport::Unixexec(unixexec) => unixexec, + _ => unreachable!(), + }; + crate::utils::block_on(unixexec.connect()).unwrap(); + } +} diff --git a/zbus/src/connection/builder.rs b/zbus/src/connection/builder.rs index a78763100..64e1b42c6 100644 --- a/zbus/src/connection/builder.rs +++ b/zbus/src/connection/builder.rs @@ -492,6 +492,8 @@ impl<'a> Builder<'a> { match address.connect().await? { #[cfg(any(unix, not(feature = "tokio")))] address::transport::Stream::Unix(stream) => stream.into(), + #[cfg(unix)] + address::transport::Stream::Unixexec(stream) => stream.into(), address::transport::Stream::Tcp(stream) => stream.into(), #[cfg(any( all(feature = "vsock", not(feature = "tokio")), From 0fd431bc2729f93675f5385914897b912c4f6477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20H=C4=83loiu?= Date: Sat, 28 Sep 2024 12:29:36 +0100 Subject: [PATCH 6/6] =?UTF-8?q?=E2=9C=85=20zb:=20Add=20`unixexec`=20connec?= =?UTF-8?q?tion=20test=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zbus/tests/unixexec.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 zbus/tests/unixexec.rs diff --git a/zbus/tests/unixexec.rs b/zbus/tests/unixexec.rs new file mode 100644 index 000000000..e71442dd2 --- /dev/null +++ b/zbus/tests/unixexec.rs @@ -0,0 +1,34 @@ +use ntest::timeout; +use test_log::test; + +use zbus::{block_on, conn::Builder, Result}; + +#[test] +#[timeout(15000)] +fn unixexec_connection_async() { + block_on(test_unixexec_connection()).unwrap(); +} + +async fn test_unixexec_connection() -> Result<()> { + let connection = Builder::address("unixexec:path=systemd-stdio-bridge")? + .build() + .await?; + + match connection + .call_method( + Some("org.freedesktop.DBus"), + "/org/freedesktop/DBus", + Some("org.freedesktop.DBus"), + "Hello", + &(), + ) + .await + { + Err(zbus::Error::MethodError(_, _, _)) => (), + Err(e) => panic!("{}", e), + + _ => panic!(), + }; + + Ok(()) +}