D-Bus - IPC on Linux
Introduction
D-Bus is a message bus system, a simple way for applications to talk to one another. In addition to interprocess communication, D-Bus helps coordinate process lifecycle; it makes it simple and reliable to code a “single instance” application or daemon, and to launch applications and daemons on demand when their services are needed.
D-Bus supplies both a system daemon (for events such as “new hardware device added” or “printer queue changed”) and a per-user-login-session daemon (for general IPC needs among user applications). Also, the message bus is built on top of a general one-to-one message passing framework, which can be used by any two apps to communicate directly (without going through the message bus daemon).
– What is D-Bus? freedesktop.org
Are you looking for a IPC framework for your Linux application that integrates well with existing desktops? look no further, D-Bus is an IPC mechanism available by default on all major Linux systems. In my opinion, D-Bus is an option for you if you need a better DX than sockets but do not need the throughput it provides. D-Bus is the perfect option if you are developing an application that needs to integrate with other Linux services tightly.
🚌 📢 - All abord the bus!
There are 2 busses available:
System Bus - is started when the system first boots up. essential services required for critical functionality are started on the system bus. (eg: systemd, bluetooth)
Session Bus - is started upon user login. services that are specific to the user (and work in tandem with system bus) are started on the session bus. (eg: DLNA, time services)
Building an IceCream Truck IPC Service
I want to demonstrate how D-Bus works with an example of the IceCream Truck Application. Our application is a daemon that starts on the session bus. a user will be able to place an order and any application can listen to the signal and be aware of the flow of orders.
Prerequisites
- Rust - we will be using
zbus
library for this. busctl
- to monitor messages on the bus.
zbus
zbus
is D-Bus implementation written in Rust.
Know More
Let’s Code an IceCream Truck 🍦
let’s start by defining the required structs and enums:
use serde::{Deserialize, Serialize};
use std::future::pending;
use std::str::FromStr;
use strum_macros::{Display, EnumString};
use zbus::{connection, fdo, interface, SignalContext};
#[derive(Debug, Serialize, Deserialize, EnumString, Display, zbus::zvariant::Type)]
enum IceCreamFalvour {
Chocolate,
Vanilla,
}
#[derive(Debug, Serialize, Deserialize, zbus::zvariant::Type)]
struct IceCreamReply {
flavor: IceCreamFalvour,
quantity: u32,
total: f32,
}
#[allow(dead_code)]
struct IceCreamTruck {
name: String,
}
IceCreamFalvour
is an enum which declares the flavors we intend to sell. this will be used for input validation later on.IceCreamReply
is the struct we want to send to the user upon a successful call to the method.IceCreamTruck
is the base struct, we extend this withzbus
macros to define the interface’s methods and signals.
Let’s define our method on top.stdin.icecream
interface to buy an ice cream and a signal that will send out order details to all subscribers.
#[interface(name = "top.stdin.icecream")]
impl IceCreamTruck {
async fn buy_icecream(
&self,
flavor: &str,
quantity: u32,
#[zbus(signal_context)] ctx: SignalContext<'_>,
) -> fdo::Result<IceCreamReply> {
let flavor = match IceCreamFalvour::from_str(flavor) {
Ok(v) => v,
Err(err) => return Err(fdo::Error::Failed(format!("{:?}", err))),
};
println!(
"received order: flavor = {} quantity = {}",
flavor, quantity
);
let details = IceCreamReply {
flavor,
quantity,
total: 6.99 * quantity as f32,
};
Self::orders(&ctx, &details).await.unwrap();
Ok(details)
}
#[zbus(signal)]
async fn orders(ctxt: &SignalContext<'_>, details: &IceCreamReply) -> zbus::Result<()>;
}
- we start by adding methods to
IceCreamTruck
struct we defined earlier. we also add a macro,interface
from zbus with our intended interface name. - define a method
buy_icecream
which takes in the following args:flavor
- This string should match a value inIceCreamFalvour
enum.quantity
ctx
- this is a special argument from zbus with a static lifetime. we used this as a parameter to emit the signal.
- validate and convert the input string to
IceCreamFalvour
enum value. - print the order to the console.
- emit the signal to
orders
- which takes in the signal context (ctx
) andIceCreamReply
struct.
To start the service, put the following in main
:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let truck = IceCreamTruck {
name: "JK".to_string(),
};
let _conn = connection::Builder::session()?
.name("top.stdin.icecream")?
.serve_at("/top/stdin/IceCreamTruck", truck)?
.build()
.await;
pending::<()>().await;
Ok(())
}
- initialize our struct with
name
parameter. - using
connection::Builder
fromzbus
, we start on the session bus with the following details:- name =
top.stdin.icecream
- interface =
/top/stdin/IceCreamTruck
- name =
pending
will put our application into an async loop until exit.
What is an interface in dbus?
An interface defines the API exposed by object on the bus. They are akin to the concept of interfaces in many programming languages and traits in Rust. Each object can (and typically do) provide multiple interfaces at the same time. A D-Bus interface can have methods, properties and signals.
While each interface of a service is identified by a unique name, its API is described by an XML description. It is mostly a machine-level detail. Most services can be queried for this description through a D-Bus standard introspection interface.
Source: zbus Docs - Interfaces
Testing
we can use D-Spy
application 1 to call our interface’s method.
you can try invoking the BuyIcecream
method with parameters: ("Chocolate", 1)
. You will receive a reply with enum of Chocolate
and a total order value.
you can also listen to all the signal emitted by our interface by running the following command:
busctl --user monitor --match 'sender=top.stdin.icecream'
Conclusion
I hope this article provided you with an intro to D-Bus and how you could write your service. For more reading on D-Bus and zbus
you can refer to their official docs/book.