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>
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:
-
create a new monitor device
int sd_device_monitor_new(sd_device_monitor **ret);
-
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);
-
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);
-
get the event for this device monitor
sd_event *sd_device_monitor_get_event(sd_device_monitor *m);
-
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;
}
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;
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);
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)