DEV Community

Carvilsi [Char]
Carvilsi [Char]

Posted on • Edited on

Linux; monitor USB devices; libudev replacement with sd-device

Some context

Recently I was writing a post related with a c project to monitor and send notifications when a unknown USB device is connected to a Linux system and just notice that the library that I was using, libudev even still supported but should not be used in new projects. The project is quite new, so I started to update it in order to use the equivalent replacement with a more modern API; sd-device that is part of libsystemd.

sd-device.h is part of libsystemd(3) and provides an API to introspect and enumerate devices on the local system. It provides a programmatic interface to the database of devices and their properties mananaged by systemd-udevd.service(8). This API is a replacement for libudev(3) and libudev.h.

The case is that the replacement took me more effort than I expected, almost all the concepts still the same, but there are some changes.

The code

The code is just a simple example that reads some values of a new connected USB to a Linux system.
It's written in C and uses two components from libsystemd sd-device and sd-event that we include with:

#include <systemd/sd-event.h>
#include <systemd/sd-device.h>
Enter fullscreen mode Exit fullscreen mode

Then at the main block we have the part for monitor the device and deal with the events. Despite failure handling and related stop actions, these are the basic steps:

  1. create a new monitor device

    int sd_device_monitor_new(sd_device_monitor **ret);

  2. add a filter that will match by subsystem and device type, for our purpose will use "usb" and "usb_device" respectively. With the second value for device type we restrict the amount of triggers for the monitor, e.g. if we do not specify (NULL) we'll be catching also the "usb_interface" related with the device.

    int sd_device_monitor_filter_add_match_subsystem_devtype(sd_device_monitor *m, const char *subsystem, const char *devtype);

  3. start the monitor, passing as parameter a handler function, to react when the filter will be triggered. We'll take a look to this handler later.

    int sd_device_monitor_start(sd_device_monitor *m, sd_device_monitor_handler_t callback, void *userdata);

  4. get the event for this device monitor

    sd_event *sd_device_monitor_get_event(sd_device_monitor *m);

  5. start the event loop. Run the event in loop mode, sd_event_run(), waiting for a sd_event_exit() or a SIGTERM

    int sd_event_loop(sd_event *e);

Notice that both, sd-event and sd-device return a negative errno-style error code.

The main function is something like:

int main()
{
        int r;
        sd_device_monitor *sddm = NULL;
        // create a new monitor device
        r = sd_device_monitor_new(&sddm);
        // check for errors, sd-device functions returns negative values on errors
        if (r < 0) 
                goto finish;

        // we would like to monitor USB activity
        r = sd_device_monitor_filter_add_match_subsystem_devtype(sddm, "usb", "usb_device");
        if (r < 0)
                goto finish;

        // start monitoring and attach a handler function to it
        r = sd_device_monitor_start(sddm, monitor_handler, NULL);
        if (r < 0) 
                goto finish;

        // get the event related with device monitor
        sd_event *event = sd_device_monitor_get_event(sddm);
        // starts the event loop
        r = sd_event_loop(event);
        if (r < 0)
                goto finish;

finish:
        if (r < 0) {
                errno = -r;
                fprintf(stderr, "Error: %m\n");
        }

        // stop and unref monitor and event
        sd_device_monitor_stop(sddm);
        sd_device_monitor_unref(sddm);
        sd_event_unref(event);
        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
Enter fullscreen mode Exit fullscreen mode

About the monitor handler function, with this signature:

typedef int (*sd_device_monitor_handler_t)(sd_device_monitor *m, sd_device *device, void *userdata);

It's main purpose is to print values of the connected USB device, and has a condition in order to only print the values when the action from the device is SD_DEVICE_ADD.

The list for different actions are:

typedef enum sd_device_action_t {
        SD_DEVICE_ADD,
        SD_DEVICE_REMOVE,
        SD_DEVICE_CHANGE,
        SD_DEVICE_MOVE,
        SD_DEVICE_ONLINE,
        SD_DEVICE_OFFLINE,
        SD_DEVICE_BIND,
        SD_DEVICE_UNBIND,
        _SD_DEVICE_ACTION_MAX,
        _SD_DEVICE_ACTION_INVALID = -EINVAL,
        _SD_ENUM_FORCE_S64(DEVICE_ACTION)
} sd_device_action_t;
Enter fullscreen mode Exit fullscreen mode

In order to retrieve the action inside the monitor handler we use:

int sd_device_get_action(sd_device *device, sd_device_action_t *ret);

And to retrieve e.g. the idVendor value of the device we use, passing "idVendor" as sysattr parameter:

int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **_value);

const char *id_vendor;
sd_device_get_sysattr_value(d, "idVendor", &id_vendor);
printf("idVendor: %s\n", id_vendor);
Enter fullscreen mode Exit fullscreen mode

The whole example is available at GitHub in case that you want to take a look or run it on your machine.

Dependencies

I tried the code on a Arch Linux and on a Ubuntu lunar (23.04) and for this one I needed to install libsystemd-dev; seems that Arc Linux has the package already installed.

If you want to install the package on Ubuntu lunar:

sudo apt install libsystemd-dev

Top comments (0)