With the new v221 release of
systemd
we are declaring the
sd-bus
API shipped with
systemd
stable. sd-bus is our minimal D-Bus
IPC C library, supporting as
back-ends both classic socket-based D-Bus and
kdbus. The library has been been
part of systemd for a while, but has only been used internally, since
we wanted to have the liberty to still make API changes without
affecting external consumers of the library. However, now we are
confident to commit to a stable API for it, starting with v221.
In this blog story I hope to provide you with a quick overview on
sd-bus, a short reiteration on D-Bus and its concepts, as well as a
few simple examples how to write D-Bus clients and services with it.
What is D-Bus again?
Let's start with a quick reminder what
D-Bus actually is: it's a
powerful, generic IPC system for Linux and other operating systems. It
knows concepts like buses, objects, interfaces, methods, signals,
properties. It provides you with fine-grained access control, a rich
type system, discoverability, introspection, monitoring, reliable
multicasting, service activation, file descriptor passing, and
more. There are bindings for numerous programming languages that are
used on Linux.
D-Bus has been a core component of Linux systems since more than 10
years. It is certainly the most widely established high-level local
IPC system on Linux. Since systemd's inception it has been the IPC
system it exposes its interfaces on. And even before systemd, it was
the IPC system Upstart used to expose its interfaces. It is used by
GNOME, by KDE and by a variety of system components.
D-Bus refers to both a
specification,
and a reference
implementation. The
reference implementation provides both a bus server component, as well
as a client library. While there are multiple other, popular
reimplementations of the client library – for both C and other
programming languages –, the only commonly used server side is the
one from the reference implementation. (However, the kdbus project is
working on providing an alternative to this server implementation as a
kernel component.)
D-Bus is mostly used as local IPC, on top of AF_UNIX sockets. However,
the protocol may be used on top of TCP/IP as well. It does not
natively support encryption, hence using D-Bus directly on TCP is
usually not a good idea. It is possible to combine D-Bus with a
transport like ssh in order to secure it. systemd uses this to make
many of its APIs accessible remotely.
A frequently asked question about D-Bus is why it exists at all,
given that AF_UNIX sockets and FIFOs already exist on UNIX and have
been used for a long time successfully. To answer this question let's
make a comparison with popular web technology of today: what
AF_UNIX/FIFOs are to D-Bus, TCP is to HTTP/REST. While AF_UNIX
sockets/FIFOs only shovel raw bytes between processes, D-Bus defines
actual message encoding and adds concepts like method call
transactions, an object system, security mechanisms, multicasting and
more.
From our 10year+ experience with D-Bus we know today that while there
are some areas where we can improve things (and we are working on
that, both with kdbus and sd-bus), it generally appears to be a very
well designed system, that stood the test of time, aged well and is
widely established. Today, if we'd sit down and design a completely
new IPC system incorporating all the experience and knowledge we
gained with D-Bus, I am sure the result would be very close to what
D-Bus already is.
Or in short: D-Bus is great. If you hack on a Linux project and need a
local IPC, it should be your first choice. Not only because D-Bus is
well designed, but also because there aren't many alternatives that
can cover similar functionality.
Where does sd-bus fit in?
Let's discuss why sd-bus exists, how it compares with the other
existing C D-Bus libraries and why it might be a library to consider
for your project.
For C, there are two established, popular D-Bus libraries: libdbus, as
it is shipped in the reference implementation of D-Bus, as well as
GDBus, a component of GLib, the low-level tool library of GNOME.
Of the two libdbus is the much older one, as it was written at the
time the specification was put together. The library was written with
a focus on being portable and to be useful as back-end for higher-level
language bindings. Both of these goals required the API to be very
generic, resulting in a relatively baroque, hard-to-use API that lacks
the bits that make it easy and fun to use from C. It provides the
building blocks, but few tools to actually make it straightforward to
build a house from them. On the other hand, the library is suitable
for most use-cases (for example, it is OOM-safe making it suitable for
writing lowest level system software), and is portable to operating
systems like Windows or more exotic UNIXes.
GDBus
is a much newer implementation. It has been written after considerable
experience with using a GLib/GObject wrapper around libdbus. GDBus is
implemented from scratch, shares no code with libdbus. Its design
differs substantially from libdbus, it contains code generators to
make it specifically easy to expose GObject objects on the bus, or
talking to D-Bus objects as GObject objects. It translates D-Bus data
types to GVariant, which is GLib's powerful data serialization
format. If you are used to GLib-style programming then you'll feel
right at home, hacking D-Bus services and clients with it is a lot
simpler than using libdbus.
With sd-bus we now provide a third implementation, sharing no code
with either libdbus or GDBus. For us, the focus was on providing kind
of a middle ground between libdbus and GDBus: a low-level C library
that actually is fun to work with, that has enough syntactic sugar to
make it easy to write clients and services with, but on the other hand
is more low-level than GDBus/GLib/GObject/GVariant. To be able to use
it in systemd's various system-level components it needed to be
OOM-safe and minimal. Another major point we wanted to focus on was
supporting a kdbus back-end right from the beginning, in addition to
the socket transport of the original D-Bus specification ("dbus1"). In
fact, we wanted to design the library closer to kdbus' semantics than
to dbus1's, wherever they are different, but still cover both
transports nicely. In contrast to libdbus or GDBus portability is not
a priority for sd-bus, instead we try to make the best of the Linux
platform and expose specific Linux concepts wherever that is
beneficial. Finally, performance was also an issue (though a secondary
one): neither libdbus nor GDBus will win any speed records. We wanted
to improve on performance (throughput and latency) -- but simplicity
and correctness are more important to us. We believe the result of our
work delivers our goals quite nicely: the library is fun to use,
supports kdbus and sockets as back-end, is relatively minimal, and the
performance is substantially
better
than both libdbus and GDBus.
To decide which of the three APIs to use for you C project, here are
short guidelines:
-
If you hack on a GLib/GObject project, GDBus is definitely your
first choice.
-
If portability to non-Linux kernels -- including Windows, Mac OS and
other UNIXes -- is important to you, use either GDBus (which more or
less means buying into GLib/GObject) or libdbus (which requires a
lot of manual work).
-
Otherwise, sd-bus would be my recommended choice.
(I am not covering C++ specifically here, this is all about plain C
only. But do note: if you use Qt, then QtDBus is the D-Bus API of
choice, being a wrapper around libdbus.)
Introduction to D-Bus Concepts
To the uninitiated D-Bus usually appears to be a relatively opaque
technology. It uses lots of concepts that appear unnecessarily complex
and redundant on first sight. But actually, they make a lot of
sense. Let's have a look:
-
A bus is where you look for IPC services. There are usually two
kinds of buses: a system bus, of which there's exactly one per
system, and which is where you'd look for system services; and a
user bus, of which there's one per user, and which is where you'd
look for user services, like the address book service or the mail
program. (Originally, the user bus was actually a session bus -- so
that you get multiple of them if you log in many times as the same
user --, and on most setups it still is, but we are working on
moving things to a true user bus, of which there is only one per
user on a system, regardless how many times that user happens to
log in.)
-
A service is a program that offers some IPC API on a bus. A
service is identified by a name in reverse domain name
notation. Thus, the org.freedesktop.NetworkManager
service on the
system bus is where NetworkManager's APIs are available and
org.freedesktop.login1
on the system bus is where
systemd-logind
's APIs are exposed.
-
A client is a program that makes use of some IPC API on a bus. It
talks to a service, monitors it and generally doesn't provide any
services on its own. That said, lines are blurry and many services
are also clients to other services. Frequently the term peer is
used as a generalization to refer to either a service or a client.
-
An object path is an identifier for an object on a specific
service. In a way this is comparable to a C pointer, since that's
how you generally reference a C object, if you hack object-oriented
programs in C. However, C pointers are just memory addresses, and
passing memory addresses around to other processes would make
little sense, since they of course refer to the address space of
the service, the client couldn't make sense of it. Thus, the D-Bus
designers came up with the object path concept, which is just a
string that looks like a file system path. Example:
/org/freedesktop/login1
is the object path of the 'manager'
object of the org.freedesktop.login1
service (which, as we
remember from above, is still the service systemd-logind
exposes). Because object paths are structured like file system
paths they can be neatly arranged in a tree, so that you end up
with a venerable tree of objects. For example, you'll find all user
sessions systemd-logind
manages below the
/org/freedesktop/login1/session
sub-tree, for example called
/org/freedesktop/login1/session/_7
,
/org/freedesktop/login1/session/_55
and so on. How services
precisely label their objects and arrange them in a tree is
completely up to the developers of the services.
-
Each object that is identified by an object path has one or more
interfaces. An interface is a collection of signals, methods, and
properties (collectively called members), that belong
together. The concept of a D-Bus interface is actually pretty
much identical to what you know from programming languages such as
Java, which also know an interface concept. Which interfaces an
object implements are up the developers of the service. Interface
names are in reverse domain name notation, much like service
names. (Yes, that's admittedly confusing, in particular since it's
pretty common for simpler services to reuse the service name string
also as an interface name.) A couple of interfaces are standardized
though and you'll find them available on many of the objects
offered by the various services. Specifically, those are
org.freedesktop.DBus.Introspectable
, org.freedesktop.DBus.Peer
and org.freedesktop.DBus.Properties
.
-
An interface can contain methods. The word "method" is more or
less just a fancy word for "function", and is a term used pretty
much the same way in object-oriented languages such as Java. The
most common interaction between D-Bus peers is that one peer
invokes one of these methods on another peer and gets a reply. A
D-Bus method takes a couple of parameters, and returns others. The
parameters are transmitted in a type-safe way, and the type
information is included in the introspection data you can query
from each object. Usually, method names (and the other member
types) follow a CamelCase syntax. For example, systemd-logind
exposes an ActivateSession
method on the
org.freedesktop.login1.Manager
interface that is available on the
/org/freedesktop/login1
object of the org.freedesktop.login1
service.
-
A signature describes a set of parameters a function (or signal,
property, see below) takes or returns. It's a series of characters
that each encode one parameter by its type. The set of types
available is pretty powerful. For example, there are simpler types
like s
for string, or u
for 32bit integer, but also complex
types such as as
for an array of strings or a(sb)
for an array
of structures consisting of one string and one boolean each. See
the D-Bus specification
for the full explanation of the type system. The
ActivateSession
method mentioned above takes a single string as
parameter (the parameter signature is hence s
), and returns
nothing (the return signature is hence the empty string). Of
course, the signature can get a lot more complex, see below for
more examples.
-
A signal is another member type that the D-Bus object system
knows. Much like a method it has a signature. However, they serve
different purposes. While in a method call a single client issues a
request on a single service, and that service sends back a response
to the client, signals are for general notification of
peers. Services send them out when they want to tell one or more
peers on the bus that something happened or changed. In contrast to
method calls and their replies they are hence usually broadcast
over a bus. While method calls/replies are used for duplex
one-to-one communication, signals are usually used for simplex
one-to-many communication (note however that that's not a
requirement, they can also be used one-to-one). Example:
systemd-logind
broadcasts a SessionNew
signal from its manager
object each time a user logs in, and a SessionRemoved
signal
every time a user logs out.
-
A property is the third member type that the D-Bus object system
knows. It's similar to the property concept known by languages like
C#. Properties also have a signature, and are more or less just
variables that an object exposes, that can be read or altered by
clients. Example: systemd-logind
exposes a property Docked
of
the signature b
(a boolean). It reflects whether systemd-logind
thinks the system is currently in a docking station of some form
(only applies to laptops …).
So much for the various concepts D-Bus knows. Of course, all these new
concepts might be overwhelming. Let's look at them from a different
perspective. I assume many of the readers have an understanding of
today's web technology, specifically HTTP and REST. Let's try to
compare the concept of a HTTP request with the concept of a D-Bus
method call:
-
A HTTP request you issue on a specific network. It could be the
Internet, or it could be your local LAN, or a company
VPN. Depending on which network you issue the request on, you'll be
able to talk to a different set of servers. This is not unlike the
"bus" concept of D-Bus.
-
On the network you then pick a specific HTTP server to talk
to. That's roughly comparable to picking a service on a specific bus.
-
On the HTTP server you then ask for a specific URL. The "path" part
of the URL (by which I mean everything after the host name of the
server, up to the last "/") is pretty similar to a D-Bus object path.
-
The "file" part of the URL (by which I mean everything after the
last slash, following the path, as described above), then defines
the actual call to make. In D-Bus this could be mapped to an
interface and method name.
-
Finally, the parameters of a HTTP call follow the path after the
"?", they map to the signature of the D-Bus call.
Of course, comparing an HTTP request to a D-Bus method call is a bit
comparing apples and oranges. However, I think it's still useful to
get a bit of a feeling of what maps to what.
From the shell
So much about the concepts and the gray theory behind them. Let's make
this exciting, let's actually see how this feels on a real system.
Since a while systemd has included a tool busctl
that is useful to
explore and interact with the D-Bus object system. When invoked
without parameters, it will show you a list of all peers connected to
the system bus. (Use --user
to see the peers of your user bus
instead):
$ busctl
NAME PID PROCESS USER CONNECTION UNIT SESSION DESCRIPTION
:1.1 1 systemd root :1.1 - - -
:1.11 705 NetworkManager root :1.11 NetworkManager.service - -
:1.14 744 gdm root :1.14 gdm.service - -
:1.4 708 systemd-logind root :1.4 systemd-logind.service - -
:1.7200 17563 busctl lennart :1.7200 session-1.scope 1 -
[…]
org.freedesktop.NetworkManager 705 NetworkManager root :1.11 NetworkManager.service - -
org.freedesktop.login1 708 systemd-logind root :1.4 systemd-logind.service - -
org.freedesktop.systemd1 1 systemd root :1.1 - - -
org.gnome.DisplayManager 744 gdm root :1.14 gdm.service - -
[…]
(I have shortened the output a bit, to make keep things brief).
The list begins with a list of all peers currently connected to the
bus. They are identified by peer names like ":1.11". These are called
unique names in D-Bus nomenclature. Basically, every peer has a
unique name, and they are assigned automatically when a peer connects
to the bus. They are much like an IP address if you so will. You'll
notice that a couple of peers are already connected, including our
little busctl tool itself as well as a number of system services. The
list then shows all actual services on the bus, identified by their
service names (as discussed above; to discern them from the unique
names these are also called well-known names). In many ways
well-known names are similar to DNS host names, i.e. they are a
friendlier way to reference a peer, but on the lower level they just
map to an IP address, or in this comparison the unique name. Much like
you can connect to a host on the Internet by either its host name or
its IP address, you can also connect to a bus peer either by its
unique or its well-known name. (Note that each peer can have as many
well-known names as it likes, much like an IP address can have
multiple host names referring to it).
OK, that's already kinda cool. Try it for yourself, on your local
machine (all you need is a recent, systemd-based distribution).
Let's now go the next step. Let's see which objects the
org.freedesktop.login1
service actually offers:
$ busctl tree org.freedesktop.login1
└─/org/freedesktop/login1
├─/org/freedesktop/login1/seat
│ ├─/org/freedesktop/login1/seat/seat0
│ └─/org/freedesktop/login1/seat/self
├─/org/freedesktop/login1/session
│ ├─/org/freedesktop/login1/session/_31
│ └─/org/freedesktop/login1/session/self
└─/org/freedesktop/login1/user
├─/org/freedesktop/login1/user/_1000
└─/org/freedesktop/login1/user/self
Pretty, isn't it? What's actually even nicer, and which the output
does not show is that there's full command line completion
available: as you press TAB the shell will auto-complete the service
names for you. It's a real pleasure to explore your D-Bus objects that
way!
The output shows some objects that you might recognize from the
explanations above. Now, let's go further. Let's see what interfaces,
methods, signals and properties one of these objects actually exposes:
$ busctl introspect org.freedesktop.login1 /org/freedesktop/login1/session/_31
NAME TYPE SIGNATURE RESULT/VALUE FLAGS
org.freedesktop.DBus.Introspectable interface - - -
.Introspect method - s -
org.freedesktop.DBus.Peer interface - - -
.GetMachineId method - s -
.Ping method - - -
org.freedesktop.DBus.Properties interface - - -
.Get method ss v -
.GetAll method s a{sv} -
.Set method ssv - -
.PropertiesChanged signal sa{sv}as - -
org.freedesktop.login1.Session interface - - -
.Activate method - - -
.Kill method si - -
.Lock method - - -
.PauseDeviceComplete method uu - -
.ReleaseControl method - - -
.ReleaseDevice method uu - -
.SetIdleHint method b - -
.TakeControl method b - -
.TakeDevice method uu hb -
.Terminate method - - -
.Unlock method - - -
.Active property b true emits-change
.Audit property u 1 const
.Class property s "user" const
.Desktop property s "" const
.Display property s "" const
.Id property s "1" const
.IdleHint property b true emits-change
.IdleSinceHint property t 1434494624206001 emits-change
.IdleSinceHintMonotonic property t 0 emits-change
.Leader property u 762 const
.Name property s "lennart" const
.Remote property b false const
.RemoteHost property s "" const
.RemoteUser property s "" const
.Scope property s "session-1.scope" const
.Seat property (so) "seat0" "/org/freedesktop/login1/seat... const
.Service property s "gdm-autologin" const
.State property s "active" -
.TTY property s "/dev/tty1" const
.Timestamp property t 1434494630344367 const
.TimestampMonotonic property t 34814579 const
.Type property s "x11" const
.User property (uo) 1000 "/org/freedesktop/login1/user/_1... const
.VTNr property u 1 const
.Lock signal - - -
.PauseDevice signal uus - -
.ResumeDevice signal uuh - -
.Unlock signal - - -
As before, the busctl command supports command line completion, hence
both the service name and the object path used are easily put together
on the shell simply by pressing TAB. The output shows the methods,
properties, signals of one of the session objects that are currently
made available by systemd-logind
. There's a section for each
interface the object knows. The second column tells you what kind of
member is shown in the line. The third column shows the signature of
the member. In case of method calls that's the input parameters, the
fourth column shows what is returned. For properties, the fourth
column encodes the current value of them.
So far, we just explored. Let's take the next step now: let's become
active - let's call a method:
# busctl call org.freedesktop.login1 /org/freedesktop/login1/session/_31 org.freedesktop.login1.Session Lock
I don't think I need to mention this anymore, but anyway: again
there's full command line completion available. The third argument is
the interface name, the fourth the method name, both can be easily
completed by pressing TAB. In this case we picked the Lock
method,
which activates the screen lock for the specific session. And yupp,
the instant I pressed enter on this line my screen lock turned on
(this only works on DEs that correctly hook into systemd-logind
for
this to work. GNOME works fine, and KDE should work too).
The Lock
method call we picked is very simple, as it takes no
parameters and returns none. Of course, it can get more complicated
for some calls. Here's another example, this time using one of
systemd's own bus calls, to start an arbitrary system unit:
# busctl call org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager StartUnit ss "cups.service" "replace"
o "/org/freedesktop/systemd1/job/42684"
This call takes two strings as input parameters, as we denote in the
signature string that follows the method name (as usual, command line
completion helps you getting this right). Following the signature the
next two parameters are simply the two strings to pass. The specified
signature string hence indicates what comes next. systemd's StartUnit
method call takes the unit name to start as first parameter, and the
mode in which to start it as second. The call returned a single object
path value. It is encoded the same way as the input parameter: a
signature (just o
for the object path) followed by the actual value.
Of course, some method call parameters can get a ton more complex, but
with busctl
it's relatively easy to encode them all. See the man
page for
details.
busctl
knows a number of other operations. For example, you can use
it to monitor D-Bus traffic as it happens (including generating a
.cap
file for use with Wireshark!) or you can set or get specific
properties. However, this blog story was supposed to be about sd-bus,
not busctl
, hence let's cut this short here, and let me direct you
to the man page in case you want to know more about the tool.
busctl
(like the rest of system) is implemented using the sd-bus
API. Thus it exposes many of the features of sd-bus itself. For
example, you can use to connect to remote or container buses. It
understands both kdbus and classic D-Bus, and more!
sd-bus
But enough! Let's get back on topic, let's talk about sd-bus itself.
The sd-bus set of APIs is mostly contained in the header file
sd-bus.h.
Here's a random selection of features of the library, that make it
compare well with the other implementations available.
-
Supports both kdbus and dbus1 as back-end.
-
Has high-level support for connecting to remote buses via ssh, and
to buses of local OS containers.
-
Powerful credential model, to implement authentication of clients
in services. Currently 34 individual fields are supported, from the
PID of the client to the cgroup or capability sets.
-
Support for tracking the life-cycle of peers in order to release
local objects automatically when all peers referencing them
disconnected.
-
The client builds an efficient decision tree to determine which
handlers to deliver an incoming bus message to.
-
Automatically translates D-Bus errors into UNIX style errors and
back (this is lossy though), to ensure best integration of D-Bus
into low-level Linux programs.
-
Powerful but lightweight object model for exposing local objects on
the bus. Automatically generates introspection as necessary.
The API is currently not fully documented, but we are working on
completing the set of manual pages. For details
see all pages starting with sd_bus_
.
Invoking a Method, from C, with sd-bus
So much about the library in general. Here's an example for connecting
to the bus and issuing a method call:
#include <stdio.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>
int main(int argc, char *argv[]) {
sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus_message *m = NULL;
sd_bus *bus = NULL;
const char *path;
int r;
/* Connect to the system bus */
r = sd_bus_open_system(&bus);
if (r < 0) {
fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-r));
goto finish;
}
/* Issue the method call and store the respons message in m */
r = sd_bus_call_method(bus,
"org.freedesktop.systemd1", /* service to contact */
"/org/freedesktop/systemd1", /* object path */
"org.freedesktop.systemd1.Manager", /* interface name */
"StartUnit", /* method name */
&error, /* object to return error in */
&m, /* return message on success */
"ss", /* input signature */
"cups.service", /* first argument */
"replace"); /* second argument */
if (r < 0) {
fprintf(stderr, "Failed to issue method call: %s\n", error.message);
goto finish;
}
/* Parse the response message */
r = sd_bus_message_read(m, "o", &path);
if (r < 0) {
fprintf(stderr, "Failed to parse response message: %s\n", strerror(-r));
goto finish;
}
printf("Queued service job as %s.\n", path);
finish:
sd_bus_error_free(&error);
sd_bus_message_unref(m);
sd_bus_unref(bus);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
Save this example as bus-client.c
, then build it with:
$ gcc bus-client.c -o bus-client `pkg-config --cflags --libs libsystemd`
This will generate a binary bus-client
you can now run. Make sure to
run it as root though, since access to the StartUnit
method is
privileged:
# ./bus-client
Queued service job as /org/freedesktop/systemd1/job/3586.
And that's it already, our first example. It showed how we invoked a
method call on the bus. The actual function call of the method is very
close to the busctl
command line we used before. I hope the code
excerpt needs little further explanation. It's supposed to give you a
taste how to write D-Bus clients with sd-bus. For more more
information please have a look at the header file, the man page or
even the sd-bus sources.
Implementing a Service, in C, with sd-bus
Of course, just calling a single method is a rather simplistic
example. Let's have a look on how to write a bus service. We'll write
a small calculator service, that exposes a single object, which
implements an interface that exposes two methods: one to multiply two
64bit signed integers, and one to divide one 64bit signed integer by
another.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <systemd/sd-bus.h>
static int method_multiply(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
int64_t x, y;
int r;
/* Read the parameters */
r = sd_bus_message_read(m, "xx", &x, &y);
if (r < 0) {
fprintf(stderr, "Failed to parse parameters: %s\n", strerror(-r));
return r;
}
/* Reply with the response */
return sd_bus_reply_method_return(m, "x", x * y);
}
static int method_divide(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
int64_t x, y;
int r;
/* Read the parameters */
r = sd_bus_message_read(m, "xx", &x, &y);
if (r < 0) {
fprintf(stderr, "Failed to parse parameters: %s\n", strerror(-r));
return r;
}
/* Return an error on division by zero */
if (y == 0) {
sd_bus_error_set_const(ret_error, "net.poettering.DivisionByZero", "Sorry, can't allow division by zero.");
return -EINVAL;
}
return sd_bus_reply_method_return(m, "x", x / y);
}
/* The vtable of our little object, implements the net.poettering.Calculator interface */
static const sd_bus_vtable calculator_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("Multiply", "xx", "x", method_multiply, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Divide", "xx", "x", method_divide, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END
};
int main(int argc, char *argv[]) {
sd_bus_slot *slot = NULL;
sd_bus *bus = NULL;
int r;
/* Connect to the user bus this time */
r = sd_bus_open_user(&bus);
if (r < 0) {
fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-r));
goto finish;
}
/* Install the object */
r = sd_bus_add_object_vtable(bus,
&slot,
"/net/poettering/Calculator", /* object path */
"net.poettering.Calculator", /* interface name */
calculator_vtable,
NULL);
if (r < 0) {
fprintf(stderr, "Failed to issue method call: %s\n", strerror(-r));
goto finish;
}
/* Take a well-known service name so that clients can find us */
r = sd_bus_request_name(bus, "net.poettering.Calculator", 0);
if (r < 0) {
fprintf(stderr, "Failed to acquire service name: %s\n", strerror(-r));
goto finish;
}
for (;;) {
/* Process requests */
r = sd_bus_process(bus, NULL);
if (r < 0) {
fprintf(stderr, "Failed to process bus: %s\n", strerror(-r));
goto finish;
}
if (r > 0) /* we processed a request, try to process another one, right-away */
continue;
/* Wait for the next request to process */
r = sd_bus_wait(bus, (uint64_t) -1);
if (r < 0) {
fprintf(stderr, "Failed to wait on bus: %s\n", strerror(-r));
goto finish;
}
}
finish:
sd_bus_slot_unref(slot);
sd_bus_unref(bus);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
Save this example as bus-service.c
, then build it with:
$ gcc bus-service.c -o bus-service `pkg-config --cflags --libs libsystemd`
Now, let's run it:
In another terminal, let's try to talk to it. Note that this service
is now on the user bus, not on the system bus as before. We do this
for simplicity reasons: on the system bus access to services is
tightly controlled so unprivileged clients cannot request privileged
operations. On the user bus however things are simpler: as only
processes of the user owning the bus can connect no further policy
enforcement will complicate this example. Because the service is on
the user bus, we have to pass the --user
switch on the busctl
command line. Let's start with looking at the service's object tree.
$ busctl --user tree net.poettering.Calculator
└─/net/poettering/Calculator
As we can see, there's only a single object on the service, which is
not surprising, given that our code above only registered one. Let's
see the interfaces and the members this object exposes:
$ busctl --user introspect net.poettering.Calculator /net/poettering/Calculator
NAME TYPE SIGNATURE RESULT/VALUE FLAGS
net.poettering.Calculator interface - - -
.Divide method xx x -
.Multiply method xx x -
org.freedesktop.DBus.Introspectable interface - - -
.Introspect method - s -
org.freedesktop.DBus.Peer interface - - -
.GetMachineId method - s -
.Ping method - - -
org.freedesktop.DBus.Properties interface - - -
.Get method ss v -
.GetAll method s a{sv} -
.Set method ssv - -
.PropertiesChanged signal sa{sv}as - -
The sd-bus library automatically added a couple of generic interfaces,
as mentioned above. But the first interface we see is actually the one
we added! It shows our two methods, and both take "xx" (two 64bit
signed integers) as input parameters, and return one "x". Great! But
does it work?
$ busctl --user call net.poettering.Calculator /net/poettering/Calculator net.poettering.Calculator Multiply xx 5 7
x 35
Woohoo! We passed the two integers 5 and 7, and the service actually
multiplied them for us and returned a single integer 35! Let's try the
other method:
$ busctl --user call net.poettering.Calculator /net/poettering/Calculator net.poettering.Calculator Divide xx 99 17
x 5
Oh, wow! It can even do integer division! Fantastic! But let's trick
it into dividing by zero:
$ busctl --user call net.poettering.Calculator /net/poettering/Calculator net.poettering.Calculator Divide xx 43 0
Sorry, can't allow division by zero.
Nice! It detected this nicely and returned a clean error about it. If
you look in the source code example above you'll see how precisely we
generated the error.
And that's really all I have for today. Of course, the examples I
showed are short, and I don't get into detail here on what precisely
each line does. However, this is supposed to be a short introduction
into D-Bus and sd-bus, and it's already way too long for that …
I hope this blog story was useful to you. If you are interested in
using sd-bus for your own programs, I hope this gets you started. If
you have further questions, check the (incomplete) man pages, and
inquire us on IRC or the systemd mailing list. If you need more
examples, have a look at the systemd source tree, all of systemd's
many bus services use sd-bus extensively.