Basic DBUS tutorial
Introduction
DBUS is an Inter Process Communication (IPC) system present in linux-based OSs. These are its base characteristics:
- Is peer-to-peer client-server architecture
- According to its documentation, “It is easy to use.” Honestly, at first, is a pain to implement it, but once you understand its core concepts, it turns a little bit easier to use. A little bit
- It is a way to create APIs to communicate processes in your Linux system.
In this basic tutorial, we will create a simple program in C that calls a method in a DGBUS Go server.
Scope
Even though I will describe some technical and architectural topics for the DBUS protocol, I won’t go into any detail that does not serve the basic comprehension of this system. For more detailed information on the protocol, you can check the official documentation:
Assumptions
In this article, it’s assumed that the reader has some experience in programming, not necessarily in both languages used in this example (C and Go), but knows some basic programming concepts, since everything that is programmed using these languages can be achieved with other languages. Also, some basic Linux knowledge would be wonderful.
The link to the GitHub with the complete code with both examples will be at the end of the article.
That said, we can start.
DBUS
As said in the introduction, DBUS is an IPC system, which means we can establish communication between our programs using this protocol, and since it is intrinsic to Linux, we don’t have to do any installation, so let’s start explaining the basic concepts of DBUS.
In the DBUS we have:
- Bus name: This is what DBUS uses to associate a connection with a bus. So when we export methods and signals, these are associated with the bus name. The standard bus names are written like: com.example, com.example.system being com.example a DNS, but it is not necessary to use a DNS to use DBUS, since it is just a formality to identify the owner of that DBUS API.
- Methods: Is the action that is going to be performed. Methods have parameters and return values.
- Signals: Are special functions that, when they are called, every subscribed process to that signal will receive that call. Like a pub sub system.
- Interfaces: An interface is a collection of methods and signals: com. example (yes, you can have an interface called as your bus name, but you can have several interfaces) com. example.audio, com.example.power, and these interfaces have their methods and signals related to their system.
- Object paths: Objects have the interfaces, and the object path is a logical path described as it would be a Linux file path: /com/example/
- Services: a service is the executable that implements all objects in the system that call the DBUS daemon.
So when we create our DBUS API, we export interfaces that contains methods and signals that can bring us information or not, like an API would do. How do we call these methods? We have to specify the bus name, object path, interface, and method name that we want to call. A method called Hello, in the interface com.example, would be seen like: com.example.Hello. Anyways, let’s see first the server-side implementation.
Server side
The server side was developed in the Go language, which will implement a service that will return a greeting message whose parameter is a name. for example: I call it with the parameter “Juan” from the client, and it will return “Hi! Juan”. Let’s get started.
First, let’s establish our method, which will return the greetings:
type Server struct{}
func (g *Server) Hello(name string) (string, *dbus.Error) {
if name == "" {
return "", dbus.MakeFailedError(errors.New("empty name"))
}
return fmt.Sprintf("Hi! %v", name), nil
}This creates a struct called Server, and Hello is one of its methods. As mentioned, it will accept a string as a parameter and return a string, but also a DBus error object (we will cover that on the client side)
Now, we have created our method, now we need to expose it with an interface. For that, I have created the Init() function that will expose this method. Let’s break it into pieces:
conn, _err := dbus.SessionBus()
if _err != nil {
return _err
}First, we stablish a connection to the bus, with the SessionBus function. Something worth mentioning is that there are two types of “connections” to the bus:
- Session — Used with desktop environments
- System — Core system (doesn’t have any GUI system like GNOME)
To establish a system connection, just need to call the SystemBus() function, but for security reasons, the bus blocks these types of connections unless you configure the bus to accept this connection with a given name. For simplicity, we will tie to the Session bus.
Get Yiond’s stories in your inbox
Join Medium for free to get updates from this writer.
Now, we will request a bus name, since this will help us to know which bus to request. If we do not specify the bus name, DBus will assign one randomly, but we want to know it at all times. (The problem with the system bus mentioned before has to do with the bus name.) In Go, we can do it like this:
reply, _err := conn.RequestName("example.service", dbus.NameFlagDoNotQueue)
if _err != nil {
return _err
}
if reply != dbus.RequestNameReplyPrimaryOwner {
return fmt.Errorf("name already taken")
}
fmt.Println("Connected to session bus!")With this, we will request a name, and if it is taken, we will return an error. If everything is Ok, we will see the message “Connected to session bus!” in the console.
Lastly, it will export our interface and method:
_err = conn.Export(&Server{}, "/example/service", "example.service")
if _err != nil {
return _err
}
select {}We export our interface “example.service” in the object path “/example/service” and let the service run in the background. Now, let’s go with the client side.
Client side
This is the first thing someone can read on the main page of the DBus low-level C API: “If you use this low-level API directly, you’re signing up for some pain.” That said, it is not impossible to implement it and is kind of fun. It suggests using other languages, wrappers, or frameworks, but trying with this implementation allows us to see step by step the process of sending a message. So let’s see it.
DBusConnection *conn = NULL;
DBusError err;
dbus_error_init(&err);
conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err)) {
printf("Error: %s\n", err.message);
dbus_error_free(&err);
exit(1);
}
dbus_bus_set_unique_name(conn, "awesomeappiguess");The initial process is similar to the Go server example, but we add an “err” type of variable, which will receive any error that we can get in any call or configuration (for example, the error return type implemented in the method “Hello” in the Go server). As in the Go server, we request a session bus and establish our bus name “awesomeappiguess” (which is not necessary, since we don’t need to know at all times the bus name of this connection).
To check that the bus name was changed, we can ask with the dbus_bus_get_unique_name() function:
const char * busname = dbus_bus_get_unique_name(conn);
printf("Bus name is %s\n", busname);Now, let’s make a call to the Method exposed in the Go server:
char * name = "juan";
DBusMessage * msg = dbus_message_new_method_call("example.service", "/example/service", "example.service", "Hello");
dbus_bool_t res = dbus_message_append_args (msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID);
DBusMessage * reply_msg = dbus_connection_send_with_reply_and_block(conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err);
if (dbus_error_is_set(&err)) {
printf("Error %s\n", err.message);
dbus_error_free(&err);
}
dbus_connection_flush(conn);
dbus_message_unref(msg);So, it will send “juan” to the method for the greetings! First, it creates a message, in that message it specifies the busname, object path, interface, and lastly the method it wants to call, in this case, Hello (Note that I am passing the name of the method in a string as it is typed in the Go server function). Also, the method expects a parameter, so for that, it has to append it to the message with the function dbus_message_append_args
Since it is a function that returns a value, and it expects a reply from the method the function, calls the function dbus_connection_send_with_reply_and_block, which will block until we receive a reply message, stored in the reply_msg variable.
dbus_connection_flush will wait until the message queue is empty when sending a message, and dbus_message_unref will clean the memory it used to store the message it sent.
Now, let’s get the reply message that the program is expecting.
char * reply = NULL;
dbus_message_get_args(reply_msg, &err, DBUS_TYPE_STRING, &reply, DBUS_TYPE_INVALID);
// remember always check if some errors happened :)
printf("%s\n", reply);Since it is expecting a string, it creates a pointer to a char variable called reply and call the function dbus_message_get_args to extract the return value of the method.
Let’s test the implementation.
Result
Let’s run the server:
and, to compile the C code, we use this command:
gcc main.c -o main -I/usr/include/dbus-1.0 -I/usr/lib/x86_64-linux-gnu/dbus-1.0/include -ldbus-1And execute the binary:
And as that, the program in C, made a call to a program in Go
Complete source code:
https://github.com/juamarCas/DBUS_example
Acknowledgement
This article and project were possible thanks to the people who dedicated some time to explaining these concepts. All the links to pages that I used to create this article are listed here:
- Matthew Jhonson: https://www.matthew.ath.cx/misc/dbus
- DBUS freedesktop specification: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces
- DBUS low-level API for C: https://dbus.freedesktop.org/doc/api/html/index.html
- Investigating DBUS for Linux/GNU: https://github.com/makercrew/dbus-sample
- godbus: https://pkg.go.dev/github.com/godbus/dbus#NewConn




