D-Bus has been present on the Linux desktop for a long time, but it’s often also availabe on servers. Systemd’s suite of tools expose pretty much everything on D-Bus, and more and more using Varlink. This means I can work against a stable API, instead of scripting systemctl.
This post is a tutorial and brain dump for myself. I hope it’s useful to others too.
Concepts
Aside from the broker itself, to work with D-Bus you need to know about:
- Interfaces which describe:
- Methods (with inputs and outputs.)
- Properties (and if they are settable.)
- Signals.
- Objects which are:
- Registered at a Path.
- Implement Interface(s).
- Services, or destinations identified by a well-known name.
Interface
Interfaces are much like interfaces in any programming language. They’re named using reverse domain name notation, so org.freedesktop.Something. Interfaces define a collection of Methods, Properties and Signals that any object providing that interface implements. Methods are called on an object, and properties can be retrieved or set. Signals are events that an object can emit, usually when its state changes. Signals are sent to any clients that are listening for a particular singal.
Object and path
Objects are “concrete things” that a service registers on the bus which provide one or multiple interfaces. They’re located at an (object) Path, which uses a path hierarchy rooted at / with / for segment separators. They also use reverse domain name notation but swap the . for the /.
The path hierarch is private, or local, to the service. Think of them like the path on a URL, not like a filesystem or on an MQTT broker. They’re not a global namespace.
Service or destination
Services register with a well-known name or destination, to make it easy to find them on the bus. These also uses reverse domain name notation.
This can also be useful when multiple implementations of a service exists. For example, NetworkManager registers org.freedesktop.NetworkManager. But systemd-networkd could also register org.freedesktop.NetworkManager. Gnome Settings talks to that destination, unaware of who is behind it. A well-known name is exclusive, no two services can claim the same name at the same time.
Smurf Naming Convention
Services register with a name that is often idential to an interface name, like org.freedesktop.Accounts. This can make things very confusing because two things that are named the same are not referring to the same thing. Since service names are destinations, a way to “lookup” a service, I’m surprised they don use regular/forward domain name notation instead. That would also differentiate between interface and destination.
So on my system the accounts service is registered on the bus with the well-known destination org.freedesktop.Accounts, which registers an object at /org/freedesktop/Accounts that implements the org.freedesktop.Accounts interface. Then there’s an object for each user at /org/freedesktop/Accounts/User<uid> which implements the org.freedesktop.Accounts.User interface and more.
Since each destination has its own path hierarchy we can have two services, com.example.Name1 and com.example.Name2 that each have an object at /com/example/Something. But since each service sits at its own root I don’t know why the deeply nested paths became a thing. It adds a lot of noise, and if all you have is the path you can’t say for certain which service it belongs to.
Introspection
Objects can self-describe the interfaces they implement. They do this by implementing the org.freedesktop.DBus.Introspectable interface, resulting in an org.freedesktop.DBus.Introspectable.Introspect method.
This returns an XML description of the interface following the D-Bus specification. It lists Methods with their inputs and outputs, Properties and Signals.
Type system
D-Bus has its own type system. It has basic types like strings and booleans, more complex types like arrays, structs and dictionaries and the dynamic type Variant. A variant has a signature describing what it may hold, and a value of that type. The value must be a valid D-Bus type.
This is used when the type of the value is only known at runtime. For example, org.freedesktop.DBus.Properties.GetAll has a type of a{sv}. An array of dictionaries with string keys, and variants for values. The type of the value can’t be specified by the org.freedesktop.DBus.Properties interface as any object can provide it, and which properties any given object has is up to the object itself. However, property names are always strings.
Usage
This is an example using Go and godbus:
package main
import (
"fmt"
"github.com/godbus/dbus/v5"
)
func main() {
conn, err := dbus.ConnectSystemBus()
if err != nil {
fmt.Println("failed to connect:", err)
return
}
defer conn.Close()
// Ask the org.freedekstop.DBus service to give us the object it has
// registered at /org/freedesktop/DBus
obj := conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
// There is a shortcut for the DBus object that does the same thing
// conn.BusOject()
var names []string
// Call the ListNames method of the org.freedesktop.DBus interface on the
// object we just got.
if err := obj.Call("org.freedesktop.DBus.ListNames", 0).Store(&names); err != nil {
fmt.Println("failed to call ListNames:", err)
return
}
fmt.Println("available names:", names)
}