Linux; monitor USB devices; libudev replacement with sd-device
Tags: c, linux, usb, monitoring
Published: 2023-09-09 11:09:42

## Some context

Recently I was writing a post related with a c [project](https://github.com/carvilsi/canaryusb) 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](https://man.archlinux.org/man/libudev.3.en) even still supported but should not be used in new projects. The [project](https://github.com/carvilsi/canaryusb) is quite new, so I started to update it in order to use the equivalent replacement with a more modern API; [sd-device](https://man.archlinux.org/man/sd-device.3.en) 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](https://github.com/carvilsi/systemd-usb-monitor-example) 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](https://man.archlinux.org/man/sd-device.3.en) and [sd-event](https://man.archlinux.org/man/sd-event.3.en) that we include with:

```c
#include <systemd/sd-event.h>
#include <systemd/sd-device.h>
```

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:

```c
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;
}
```

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:

```c
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;
```

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);`

```c
const char *id_vendor;
sd_device_get_sysattr_value(d, "idVendor", &id_vendor);
printf("idVendor: %s\n", id_vendor);
```

The whole example is available at [GitHub](https://github.com/carvilsi/systemd-usb-monitor-example) 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`