D-Bus Tutorial

  • Post author:
  • Post last modified:February 29, 2024
  • Reading time:13 mins read

D-Bus is a mechanism for inter-process communication (IPC) between processes running on the same host. D-Bus provides a protocol and APIs for communication betwen processes. As there are many processes running at a time, D-Bus provides a convenient and standard mechanism for communication between them.

1.0 D-Bus

D-Bus is a mechanism for interprocess communication under Linux and other Unix-like systems. D-Bus has a layered architecture. At the lowest level is the D-Bus wire protocol described in the D-Bus Specification. The libdbus library is an implementation of the wire protocol. It provides the C Language interface for communication between two processes. At the highest level is the D-Bus daemon, or the message bus daemon. Processes can connect to a D-Bus daemon and exchange messages. There may be multiple D-Bus daemons running in the system at a time. First, there is the system D-BUS daemon for communication between the kernel, system-wide services and the user. Then, there is the session D-BUS daemon, primarily intended for communication between processes for the desktop applications of the logged-in user.

libdbus is a low level API from the freedesktop.org project. There are other higher level implementations, viz., GDBus for GIO, QtDBus for Qt, dbus-java and sd-bus, for the systemd software suite.

2.0 D-Bus concepts

2.1 Message

A message is the unit of data transfer between processes. Messages are discreet as opposed to continuous stream of data transferred between processes by mechanisms like pipes. A message has a header, which identifies its sender, receiver and method or signal name, etc. and message body containing a data payload.

2.2 Message Types

There are four types of messages, SIGNAL, METHOD_CALL, METHOD_RETURN and ERROR.

A SIGNAL is a message that is broadcast by a process and can be received by other interested processes.

A METHOD_CALL message is a request by the sender for a particular operation on an object of the receiver. For example, the receiver may be a service with a singleton object. The sender could be a client, requesting the execution of a method by the server. The method call message has the name of the method to be executed and also the arguments required for execution. The receiver is required to execute the method and respond back to the sender with a METHOD_RETURN message containing the result(s) of the operation. Or, if there was an error, the receiver can respond with an ERROR message.

2.3 Message bus

A message bus is a daemon process which routes messages between other processes.

2.4 Service

A service is a daemon process that provides some utility in the system. A service is a server process which does work for the clients. A service has a singleton object.

2.5 Object

An object is an entity in a process, which does some work. An object is identified by a path name. A path is like a complete file name in the system. So, an object might have a path name like, /com/example/someserver. An object has members, which means methods and signals.

2.6 Interfaces

An interface is a group of functions. An object supports one or more interfaces. The interfaces supported by an object specify the members of that object.

2.7 Connection names

When an application connects with the D-Bus daemon, it is assigned a unique connection name. A unique connection name starts with the colon character “:”.

An application may also ask for a well-known name to be assigned to a connection. This is of the form of a reverse domain name like, com.example.somename.

For each connection name there is an application which is its primary owner. All messages sent to a name are delivered to the primary owner. Then, there is a queue of secondary owners. As soon as a primary owner relinquishes the name, the application at the top of the queue of aspiring secondary owners takes charge and becomes the new primary owner.

3.0 D-Bus Configuration

The D-Bus daemon configuration files are located in the /usr/share/dbus-1 directory. The configuration files, system.conf and session.conf for the system bus and session bus respectively are also symbolically linked in the /etc/dbus-1 directory. There are directives in system.conf and session.conf to include files in the system.d and session.d sub-directories, respectively.

4.0 Use Cases

D-Bus is used for interprocess communication between a server or service process and clients. There are two situations. First, there, is only one-way communication. The clients may inform the server of some events and the server makes a note of it. The server does not respond back to the client. Similarly, a service may broadcast some information and those (processes) which are interested, make a note of it. The clients do not respond back to the server. The second case is a full-fledged two-way communication. A client sends some information in a request message to the server. The server receives the message, processes the data and sends a reply message back to the client. The first case is accomplished using signals whereas the second is implemented using method calls. The example given below pertains to the second case.

5.0 A client-server example in C

The server in this example provides a simple addition service. The clients send two integers in a method call. The server adds them up and sends the answer as the reply. We will use the low-level libdbus interface from C programs.

The server program is,

/*
 *
 *     add-server.c: server program, receives message,
 *                   adds numbers in message and 
 *                   gives back result to client
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <ctype.h>
#include <dbus/dbus.h>

const char *const INTERFACE_NAME = "in.softprayog.dbus_example";
const char *const SERVER_BUS_NAME = "in.softprayog.add_server";
const char *const OBJECT_PATH_NAME = "/in/softprayog/adder";
const char *const METHOD_NAME = "add_numbers";

DBusError dbus_error;
void print_dbus_error (char *str);
bool isinteger (char *ptr);

int main (int argc, char **argv)
{
    DBusConnection *conn;
    int ret;

    dbus_error_init (&dbus_error);

    conn = dbus_bus_get (DBUS_BUS_SESSION, &dbus_error);

    if (dbus_error_is_set (&dbus_error))
        print_dbus_error ("dbus_bus_get");

    if (!conn) 
        exit (1);

    // Get a well known name
    ret = dbus_bus_request_name (conn, SERVER_BUS_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE, &dbus_error);

    if (dbus_error_is_set (&dbus_error))
        print_dbus_error ("dbus_bus_get");

    if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
        fprintf (stderr, "Dbus: not primary owner, ret = %d\n", ret);
        exit (1);
    }

    // Handle request from clients
    while (1) {
        // Block for msg from client
        if (!dbus_connection_read_write_dispatch (conn, -1)) {
            fprintf (stderr, "Not connected now.\n");
            exit (1);
        }
     
        DBusMessage *message;

        if ((message = dbus_connection_pop_message (conn)) == NULL) {
            fprintf (stderr, "Did not get message\n");
            continue;
        } 
        
        if (dbus_message_is_method_call (message, INTERFACE_NAME, METHOD_NAME)) {
            char *s;
            char *str1 = NULL, *str2 = NULL;
            const char space [4] = " \n\t";
            long i, j;
            bool error = false;

            if (dbus_message_get_args (message, &dbus_error, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID)) {
                printf ("%s", s);
                // Validate received message
                str1 = strtok (s, space);
                if (str1)
                    str2 = strtok (NULL, space);
 
                if (!str1 || !str2)
                    error = true; 
            
                if (!error) {
                    if (isinteger (str1))
                        i = atol (str1);
                    else
                        error = true;
                }
                if (!error) {
                    if (isinteger (str2))
                        j = atol (str2);
                    else
                        error = true;
                }

                if (!error) {
                    // send reply
                    DBusMessage *reply;
                    char answer [40];

                    sprintf (answer, "Sum is %ld", i + j);
                    if ((reply = dbus_message_new_method_return (message)) == NULL) {
                        fprintf (stderr, "Error in dbus_message_new_method_return\n");
                        exit (1);
                    }
    
                    DBusMessageIter iter;
                    dbus_message_iter_init_append (reply, &iter);
                    char *ptr = answer;
                    if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &ptr)) {
                        fprintf (stderr, "Error in dbus_message_iter_append_basic\n");
                        exit (1);
                    }

                    if (!dbus_connection_send (conn, reply, NULL)) {
                        fprintf (stderr, "Error in dbus_connection_send\n");
                        exit (1);
                    }

                    dbus_connection_flush (conn);
                
                    dbus_message_unref (reply);	
                }
                else // There was an error
                {
                    DBusMessage *dbus_error_msg;
                    char error_msg [] = "Error in input";
                    if ((dbus_error_msg = dbus_message_new_error (message, DBUS_ERROR_FAILED, error_msg)) == NULL) {
                         fprintf (stderr, "Error in dbus_message_new_error\n");
                         exit (1);
                    }

                    if (!dbus_connection_send (conn, dbus_error_msg, NULL)) {
                        fprintf (stderr, "Error in dbus_connection_send\n");
                        exit (1);
                    }

                    dbus_connection_flush (conn);
                
                    dbus_message_unref (dbus_error_msg);	
                }
            }
            else
            {
                print_dbus_error ("Error getting message");
            }
        }
    }

    return 0;
}


bool isinteger (char *ptr)
{

    if (*ptr == '+' || *ptr == '-')
        ptr++;

    while (*ptr) {
        if (!isdigit ((int) *ptr++))
            return false;
    }
    
    return true;
}

void print_dbus_error (char *str) 
{
    fprintf (stderr, "%s: %s\n", str, dbus_error.message);
    dbus_error_free (&dbus_error);
}

And, the client program is

/*
 *
 *     add-client.c: client program, takes two numbers as input,
 *                   sends to server for addition,
 *                   gets result from server,
 *                   prints the result on the screen
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> 
#include <string.h>
#include <stdbool.h>
#include <ctype.h>

#include <dbus/dbus.h>


const char *const INTERFACE_NAME = "in.softprayog.dbus_example";
const char *const SERVER_BUS_NAME = "in.softprayog.add_server";
const char *const CLIENT_BUS_NAME = "in.softprayog.add_client";
const char *const SERVER_OBJECT_PATH_NAME = "/in/softprayog/adder";
const char *const CLIENT_OBJECT_PATH_NAME = "/in/softprayog/add_client";
const char *const METHOD_NAME = "add_numbers";

DBusError dbus_error;
void print_dbus_error (char *str);

int main (int argc, char **argv)
{
    DBusConnection *conn;
    int ret;
    char input [80];

    dbus_error_init (&dbus_error);

    conn = dbus_bus_get (DBUS_BUS_SESSION, &dbus_error);

    if (dbus_error_is_set (&dbus_error))
        print_dbus_error ("dbus_bus_get");

    if (!conn) 
        exit (1);

    printf ("Please type two numbers: ");
    while (fgets (input, 78, stdin) != NULL) {

        // Get a well known name
        while (1) {
            ret = dbus_bus_request_name (conn, CLIENT_BUS_NAME, 0, &dbus_error);

            if (ret == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) 
               break;

            if (ret == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
               fprintf (stderr, "Waiting for the bus ... \n");
               sleep (1);
               continue;
            }
            if (dbus_error_is_set (&dbus_error))
               print_dbus_error ("dbus_bus_get");
        }
        
        DBusMessage *request;

        if ((request = dbus_message_new_method_call (SERVER_BUS_NAME, SERVER_OBJECT_PATH_NAME, 
                           INTERFACE_NAME, METHOD_NAME)) == NULL) {
            fprintf (stderr, "Error in dbus_message_new_method_call\n");
            exit (1);
        }

        DBusMessageIter iter;
        dbus_message_iter_init_append (request, &iter);
        char *ptr = input;
        if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &ptr)) {
            fprintf (stderr, "Error in dbus_message_iter_append_basic\n");
            exit (1);
        }
        DBusPendingCall *pending_return;
        if (!dbus_connection_send_with_reply (conn, request, &pending_return, -1)) {
            fprintf (stderr, "Error in dbus_connection_send_with_reply\n");
            exit (1);
        }

        if (pending_return == NULL) {
            fprintf (stderr, "pending return is NULL");
            exit (1);
        }

        dbus_connection_flush (conn);
                
        dbus_message_unref (request);	

        dbus_pending_call_block (pending_return);

        DBusMessage *reply;
        if ((reply = dbus_pending_call_steal_reply (pending_return)) == NULL) {
            fprintf (stderr, "Error in dbus_pending_call_steal_reply");
            exit (1);
        }

        dbus_pending_call_unref	(pending_return);

        char *s;
        if (dbus_message_get_args (reply, &dbus_error, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID)) {
            printf ("%s\n", s);
        }
        else
        {
             fprintf (stderr, "Did not get arguments in reply\n");
             exit (1);
        }
        dbus_message_unref (reply);	

        if (dbus_bus_release_name (conn, CLIENT_BUS_NAME, &dbus_error) == -1) {
             fprintf (stderr, "Error in dbus_bus_release_name\n");
             exit (1);
        }

        printf ("Please type two numbers: ");
    }

    return 0;
}

void print_dbus_error (char *str) 
{
    fprintf (stderr, "%s: %s\n", str, dbus_error.message);
    dbus_error_free (&dbus_error);
}

The makefile for building the software is given below. The spaces to the left of gcc and rm commands are for the tab character.

#
# Makefile
#

all: add-server add-client

%.o: %.c
        gcc -Wall -c $< `pkg-config --cflags dbus-1`

add-server: add-server.o
        gcc add-server.o -o add-server `pkg-config --libs dbus-1`

add-client: add-client.o
        gcc add-client.o -o add-client `pkg-config --libs dbus-1`

.PHONY: clean
clean:
        rm *.o add-server add-client

We can compile and run the server and client programs,

Server and client processes using D-Bus

6.0 REFERENCES

Share

Karunesh Johri

Software developer, working with C and Linux.
4.2 5 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments