Table of Contents:
D-Bus services
Most Apertis components communicate with various daemons via D-Bus. The D-Bus interfaces exposed by those daemons are public API, and hence must be designed as carefully as any C API — changing them is not easy once other projects depend on them. D-Bus interfaces are described using an XML format which gives the parameters and types of every method.
Summary
- Use GDBus rather than libdbus or libdbus-glib. (Dependencies)
- Install D-Bus interface XML files. (Interface files)
- Version D-Bus interfaces to allow old and new versions to be used in parallel. (API versioning)
- Use standard D-Bus interfaces (
Properties
andObjectManager
) where possible for consistency. (API design) - Design D-Bus APIs to reduce round trips, as they are expensive. (API design)
- Use
gdbus-codegen
to generate the low-level C code for implementing a D-Bus API on the server and client side. (Code generation) - Use
gdbus-codegen
to generate documentation for a D-Bus API and include that in the project’s API documentation. (Documentation) - Never use synchronous calls in the C implementation of a D-Bus service. (Service implementation)
- Install D-Bus service files. (Service files)
Dependencies
Use GDBus, which is part of GLib’s GIO. Do not use libdbus or libdbus-glib, both of which are deprecated, do not integrate well with GLib, and are no longer maintained.
To depend on GDBus, add a pkg-config dependency on gio-2.0
to configure.ac
.
There should be no dependency on dbus-1
or dbus-glib-1
, both of which are
for the deprecated libraries.
Interface files
Interface files should be installed to $PREFIX/share/dbus-1/interfaces
so
that other services can introspect them. This can be done with the following
Makefile.am
snippet:
xmldir = $(datadir)/dbus-1/interfaces
xml_DATA = org.foo.MyApp.Interface1.xml org.foo.MyApp.Interface2.xml
If an interface defined by project A needs to be used by project B, project B should declare a build time dependency on project A, and use the installed copy of the interface file for any code generation it has to do. It should not have a local copy of the interface, as that could then go out of sync with the canonical copy in project A’s git repository.
API versioning
Just like C APIs, D-Bus interfaces should be designed to be parallel-usable with API-incompatible versions. This is typically achieved by including a version number in each interface name. A full article describing the approach is here.
API design
D-Bus API design is broadly the same as C API design, but there are a few additional points to bear in mind which arise both from D-Bus’ features (explicit errors, signals and properties), and from its implementation as an IPC system — D-Bus method calls are much more expensive than C function calls, typically taking on the order of milliseconds to complete a round trip. Therefore, the number of method calls needed to perform an operation should be minimised by design.
- Use enumerated values rather than booleans or undocumented ‘magic’ integers. This improves documentation, usability of the API, and also allows more values to be added to the enums in future without breaking API as would be needed for a boolean. See also: Use Enums Not Booleans.
- Use standard, existing D-Bus interfaces where possible. For example, the
D-Bus specification defines the
org.freedesktop.DBus.Properties
andorg.freedesktop.DBus.ObjectManager
interfaces, which should be implemented by any object in preference to home-grown solutions.- Specifically, emit the
PropertiesChanged
signal whenever the value of a property changes. This avoids the need for custom ‘[PropertyName]Changed’ signals for every property in an interface, and also allows change notifications to be bundled together to reduce IPC round trips. For this reason, properties should be used instead of ‘[PropertyName]Get’/‘[PropertyName]Set’ getter/setter methods wherever possible. - Note that D-Bus properties can be read-only as well as read-write, so can also replace single getter methods to allow change notification of the property.
- Specifically, emit the
- Use enumerated values instead of human readable strings. Two D-Bus peers could be running in different locales, which would complicate translation of the value — converting an enumerated value to a human readable string on the client side is much simpler.
- Reduce D-Bus round trips by supporting multiple related operations in a
single call where possible. For example, a D-Bus method to add an object on
the server could be expanded to take an array of parameters, and create
multiple objects in a single call, rather than a single one. The logical
conclusion of this is the pattern of replacing (e.g.)
InsertEntry(params)
andRemoveEntry(id)
methods with aChangeEntries(a(params_to_insert), a(ids_to_remove))
method and aEntriesChanged(a(ids_added), a(ids_removed))
signal. In the base case, the arrays in these D-Bus APIs would contain a single element, but could contain more if needed. - Long-running operations should communicate return values via normal D-Bus
method returns, rather than via a signal. D-Bus supports asynchronous
methods: the server implementation merely needs to delay calling the
g_dbus_method_invocation_return_value()
call for the invocation. - Use D-Bus error returns rather than custom error messages or success codes. This re-uses the D-Bus error infrastructure, bypasses the issue of which values to return in outbound parameters from the method invocation, and allows errors to be highlighted in D-Bus debugging tools.
- Follow standard naming conventions for objects, methods and parameters.
- Type information should not be included in parameter names (e.g. Hungarian notation).
- The convention for D-Bus argument naming is to use
lowercase_with_underscores
1.
- If sending sensitive data over D-Bus (such as passwords), double-check the system D-Bus eavesdropping settings. Eavesdropping should never be enabled on production systems, and should only be enabled on development systems for debugging purposes. Broadcast signals must not contain sensitive data, but unicast messages (including method calls, method replies and unicast signals) may.
- Ensure integers are signed (signature ‘i’) or unsigned (signature ‘u’) as appropriate.
- If specifying multiple parameters as identically-indexed arrays, consider combining the arrays to a single one with tuple elements containing the individual values. e.g. Signature ‘aiauas’ would become ‘a(ius)’, but only if all three arrays were indexed identically.
- More generally, expose as much structure in D-Bus types as possible, avoiding structured strings, as they require building on the server side and parsing on the client side, which is extra code, and extra potential for bugs or vulnerabilities.
- D-Bus arrays are automatically transmitted with their length, so it does not need to be encoded as a separate parameter.
Code generation
Rather than manually implementing both the server and client sides of a D-Bus
interface, automatically generate
GDBusProxy
and
GDBusInterfaceSkeleton
implementations for all APIs in a D-Bus interface using
gdbus-codegen
.
These can then be called from the client and server code to ease
implementation.
Here’s some standard Makefile.am
rules to generate the C and H files for an
interface, then compile them as a library. GLIB_CFLAGS and GLIB_LIBS must
contain the flags returned by a pkg-config query for glib-2.0 gio-2.0.
# Default values
lib_LTLIBRARIES =
dbus_sources_xml =
dbus_built_docs =
# Shared values
#
# Note that as this is all generated code, we disable various warnings.
service_cppflags = \
$(AM_CPPFLAGS)
service_cflags = \
-Wno-error \
-Wno-strict-aliasing \
-fno-strict-aliasing \
-Wno-redundant-decls \
$(GLIB_CFLAGS) \
$(CODE_COVERAGE_CFLAGS) \
$(AM_CFLAGS)
service_libadd = \
$(GLIB_LIBS) \
$(CODE_COVERAGE_LIBS) \
$(AM_LIBADD)
service_ldflags = \
$(ERROR_LDFLAGS) \
$(AM_LDFLAGS)
service_codegen_flags = \
--interface-prefix Namespace. \
--c-namespace Nspc \
--generate-docbook docs \
$(NULL)
# Generic rules
%.c %.h: %.xml
$(AM_V_GEN)$(GDBUS_CODEGEN) \
$(service_codegen_flags) --generate-c-code $* $<
dbus_sources_h = $(dbus_sources_xml:.xml=.h)
dbus_sources_c = $(dbus_sources_xml:.xml=.c)
EXTRA_DIST = $(dbus_sources_xml)
CLEANFILES = $(dbus_sources_h) $(dbus_sources_c) $(dbus_built_docs)
# Example library containing the interface code
example_sources_xml = org.foo.MyApp1.ExampleInterface.xml
example_docs_xml = docs-org.foo.MyApp1.ExampleInterface.xml
dbus_sources_xml += $(example_sources_xml)
dbus_built_docs += $(example_docs_xml)
$(example_docs_xml): $(example_sources_xml)
lib_LTLIBRARIES += libexample.la
nodist_libexample_la_SOURCES = \
$(example_sources_xml:.xml=.c) \
$(example_sources_xml:.xml=.h) \
$(NULL)
libexample_la_CPPFLAGS = $(service_cppflags)
libexample_la_CFLAGS = $(service_cflags)
libexample_la_LIBADD = $(service_libadd)
libexample_la_LDFLAGS = $(service_ldflags)
Documentation
Also just like C APIs, D-Bus APIs must be documented. The D-Bus interface XML format supports inline documentation for each method, property and signal, which can then be converted to DocBook format and included in the project’s API manual using gdbus-codegen and gtk-doc.
The standard interface rules in Code generation also generate a DocBook documentation file for each D-Bus API. Include these DocBook files in the API documentation using the approach described in the API documentation guide.
Service implementation
When implementing a D-Bus service in C using GDBus, a few rules need to be followed:
- Never use synchronous calls: always use the
*_async()
APIs. D-Bus IPC has no guaranteed latency, so a call may take several seconds, during which time a sync call would be blocking the program’s main loop, preventing it from doing any other work. - Always respond exactly once to an incoming D-Bus method invocation,
either by calling
g_dbus_method_invocation_return_error()
org_dbus_method_invocation_return_value()
. The latter is automatically wrapped as the[namespace]_[object]_[method]_complete_*()
functions bygdbus-codegen
.
The second point is important: each D-Bus method invocation should have exactly one response, which should either be an error, or a success return with zero or more outbound parameters. It is an error for a D-Bus method to, e.g., return both an error and then a return value; or for it to never return. Doing so would cause a timeout for the caller, and potentially memory leaks or use-after-frees.
Service files
Each D-Bus service must install a .service
file describing its service name
and which binary to run to make that service appear. This allows for service
activation (see ‘Message Bus Starting Services’ in
2).
To install a service file for the session bus, use the following Makefile.am
:
# DBus Activation file
servicedir = $(datadir)/dbus-1/services
service_in_files = org.foo.MyApp1.service.in
service_DATA = $(services_in_files:.in=)
edit = $(AM_V_GEN)sed \
-e 's|@sbindir[@]|$(sbindir)|g' \
-e 's|@sysconfdir[@]|$(sysconfdir)|g' \
-e 's|@localstatedir[@]|$(localstatedir)|g' \
-e 's|@libexecdir[@]|$(libexecdir)|g'
%.service: %.service.in
$(edit) $< >$@
DISTCLEANFILES = $(service_DATA)
EXTRA_DIST = $(service_in_files)
Service files for the system bus have to be installed elsewhere, but also typically require an accompanying D-Bus configuration file to set their security policy, which is beyond the scope of this document.
Debugging
Debugging D-Bus can be tricky, as it is a core system service. Three main tools are available: dbus-monitor, Bustle and D-Feet.
dbus-monitor
dbus-monitor allows monitoring of D-Bus messages as they are sent, outputting them to the console. It supports filtering the messages according to D-Bus match rules. The session bus can be monitored using:
dbus-monitor --session
and match rules applied as:
dbus-monitor "type=error" "sender=org.freedesktop.SystemToolsBackends"
(which will print messages matching either of the two rules: so all errors, and all messages originating from the org.freedesktop.SystemToolsBackends peer).
Because we have dbus-daemon newer than 1.9.10,
sudo dbus-monitor --system
can be used to monitor the system bus; reconfiguring it is not necessary (but you do have to be root, e.g. using sudo).
D-Feet
D-Feet is an explorer for a D-Bus bus. It connects to the bus, and displays all clients and objects currently available on the bus. The objects can be inspected, and their methods called directly. It is valuable for verifying that a service is exporting the expected objects to the bus, and that the objects’ methods can be called as expected.
Bustle
Bustle is a more advanced version of dbus-monitor which displays D-Bus calls graphically, including timing data. It is intended for profiling IPC performance by highlighting D-Bus calls which happen frequently or take a long time. It can be used as a general replacement for dbus-monitor, though.
There is a presentation available on profiling D-Bus APIs using Bustle.