Skip to content

Commit

Permalink
✨ zb: Add support for unixexec transport
Browse files Browse the repository at this point in the history
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
  • Loading branch information
vially committed Sep 13, 2024
1 parent 3dcdeeb commit 07818ff
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 0 deletions.
19 changes: 19 additions & 0 deletions zbus/src/address/transport/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -52,6 +54,10 @@ use std::os::linux::net::SocketAddrExt;
feature = "tokio-vsock"
))]
pub use vsock_transport::Vsock;
#[cfg(unix)]
mod unixexec;
#[cfg(unix)]
pub use unixexec::UnixExec;

/// The transport properties of a D-Bus address.
#[derive(Clone, Debug, PartialEq, Eq)]
Expand All @@ -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 {
Expand Down Expand Up @@ -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())?;
Expand Down Expand Up @@ -211,6 +222,8 @@ impl Transport {
pub(super) fn from_options(transport: &str, options: HashMap<&str, &str>) -> Result<Self> {
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(
Expand All @@ -234,6 +247,8 @@ impl Transport {
#[derive(Debug)]
pub(crate) enum Stream {
Unix(Async<UnixStream>),
#[cfg(unix)]
UnixExec(Command),
Tcp(Async<TcpStream>),
#[cfg(feature = "vsock")]
Vsock(Async<VsockStream>),
Expand All @@ -244,6 +259,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),
Expand Down Expand Up @@ -334,6 +351,8 @@ impl Display for Transport {
match self {
Self::Tcp(tcp) => write!(f, "{}", tcp)?,
Self::Unix(unix) => write!(f, "{}", unix)?,
#[cfg(unix)]
Self::UnixExec(unix) => write!(f, "{}", unix)?,
#[cfg(any(
all(feature = "vsock", not(feature = "tokio")),
feature = "tokio-vsock"
Expand Down
72 changes: 72 additions & 0 deletions zbus/src/address/transport/unixexec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use std::{
borrow::BorrowMut, ffi::OsString, fmt::Display, os::unix::ffi::OsStrExt, path::PathBuf,
process::Stdio,
};

use crate::process::Command;

use super::encode_percents;

/// A unixexec domain socket transport in a D-Bus address.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnixExec {
pub(super) path: PathBuf,
pub(super) arg0: Option<OsString>,
pub(super) args: Vec<String>,
}

impl UnixExec {
/// Create a new unixexec transport with the given path and arguments.
pub fn new(path: PathBuf, arg0: Option<OsString>, args: Vec<String>) -> 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<String> = Vec::new();
let mut arg_index = 1;
while let Some(arg) = opts.get(format!("argv{arg_index}").as_str()) {
args.push(arg.to_string());
arg_index += 1;
}

Ok(Self::new(PathBuf::from(path), arg0, args))
}

pub(super) async fn connect(self) -> crate::Result<crate::connection::socket::Command> {
Command::from(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:")?;
encode_percents(f, self.path.as_os_str().as_bytes())
}
}

impl From<UnixExec> for Command {
fn from(unixexec: UnixExec) -> Self {
let mut command = Command::new(unixexec.path);
command.args(unixexec.args);

if let Some(arg0) = unixexec.arg0.as_ref() {
command.arg0(arg0);
}

command
}
}
2 changes: 2 additions & 0 deletions zbus/src/connection/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,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")),
Expand Down
2 changes: 2 additions & 0 deletions zbus/src/connection/socket/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub use split::{BoxedSplit, Split};

#[cfg(unix)]
mod command;
#[cfg(unix)]
pub(crate) use command::Command;
mod tcp;
mod unix;
mod vsock;
Expand Down

0 comments on commit 07818ff

Please sign in to comment.