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 and ObjectManager) 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 and org.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.
  • 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) and RemoveEntry(id) methods with a ChangeEntries(a(params_to_insert), a(ids_to_remove)) method and a EntriesChanged(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:

  1. 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.
  2. Always respond exactly once to an incoming D-Bus method invocation, either by calling g_dbus_method_invocation_return_error() or g_dbus_method_invocation_return_value(). The latter is automatically wrapped as the [namespace]_[object]_[method]_complete_*() functions by gdbus-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.