2

I am working on a C++ application for embedded devices that listens to USB hotplug events via a netlink socket. Detecting events works flawlessly but additionally I would like to query already attached devices in the beginning of the program. I was able to archive the same functionality for network interfaces but it seems that USB is a pretty different story. So my questions are:

  • Is it even possible to list already attached USB devices using a netlink socket?
  • If it is possible, how would a request message look like?
  • If it is not possible, what would be a good alternative with little dependencies?

MWE for receiving hotplug events:

#include <sys/signalfd.h>
#include <csignal>
#include <linux/netlink.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <poll.h>

int main() {
    struct sockaddr_nl addr = {0};
    char buffer[4096];
    sigset_t signal_set;
    struct signalfd_siginfo signal_info;
    struct pollfd pfd[2];
    int ret_poll;
    ssize_t n;

    // Set signals we want to catch
    sigemptyset(&signal_set);
    sigaddset(&signal_set, SIGTERM);
    sigaddset(&signal_set, SIGINT);

    // Change the signal mask and check
    if (sigprocmask(SIG_BLOCK, &signal_set, nullptr) < 0) {
        fprintf(stderr, "Error while sigprocmask(): %s\n", strerror(errno));
        return EXIT_FAILURE;
    }
    // Get a signal file descriptor
    pfd[0].fd = signalfd(-1, &signal_set, 0);
    // Check the signal file descriptor
    if (pfd[0].fd < 0) {
        fprintf(stderr, "Error while signalfd(): %s\n", strerror(errno));
        return EXIT_FAILURE;
    }

    // Create a netlink socket
    pfd[1].fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
    if (pfd[1].fd < 0) {
        fprintf(stderr, "Netlink socket create failed: %s\n", strerror(errno));
        return EXIT_FAILURE;
    }

    addr.nl_family = AF_NETLINK;
    addr.nl_pid = getpid();
    addr.nl_groups = 2;

    if (bind(pfd[1].fd, (struct sockaddr *) &addr, sizeof(addr))) {
        fprintf(stderr, "Netlink socket bind() failed: %s\n", strerror(errno));
        return EXIT_FAILURE;
    }

    pfd[0].events = POLLIN;
    pfd[1].events = POLLIN;

    while (true) {
        // Wait for events without time limit
        ret_poll = poll(pfd, 2, -1);
        if (ret_poll < 0) {
            fprintf(stderr, "SystemMaster::execute() -> "
                            "Error while poll(): %s\n", strerror(errno));
            return EXIT_FAILURE;
        }
        // True, if a signal from the operating system was sent to this process
        if (pfd[0].revents & POLLIN) {
            // Get the signal
            n = read(pfd[0].fd, &signal_info, sizeof(signal_info));
            // True, if an error occurred while getting the signal
            if (n == -1) {
                fprintf(stderr, "Error while read() on signal pipe: %s\n", strerror(errno));
            }
            // Check, if we are really interested in the caught signal
            if ((signal_info.ssi_signo == SIGTERM) || (signal_info.ssi_signo == SIGINT)) {
                printf("Signal received\n");
            }
            break;
        }
        // True, if a netlink message is available
        if (pfd[1].revents & POLLIN) {
            n = recv(pfd[1].fd, &buffer, sizeof(buffer), 0);

            for (int i = 0; i < n; ++i) {
                if (buffer[i] == 0) printf("\n");
                else if (buffer[i] > 33 && buffer[i] < 126) printf("%c", buffer[i]);
            }
        }
    }
    // Close both file descriptors
    close(pfd[0].fd);
    close(pfd[1].fd);
    return 0;
}

Thanks in advance for any response!

teawolf
  • 31
  • 4

1 Answers1

0

As proposed by @UlrichEckhardt I checked out lsusb and its sources. It uses libusb, where the Linux part seems to use libudev and falls back to netlink sockets for hotplug detection, if libudev is not available. It seems that when only netlink is used, the possibility to scan devices is not available. However, no guarantees whatsoever as I didn't dig deep enough to check everything.

Anyway, I decided to use libudev for scanning and hotplug as I like uniform solutions. The code looks as follows:

#include <unistd.h>
#include <poll.h>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <sys/signalfd.h>
#include <csignal>
#include <libudev.h>

void scanDevices(struct udev *udev) {
    struct udev_device *device;
    struct udev_enumerate *enumerate;
    struct udev_list_entry *devices, *dev_list_entry;

    // Create enumerate object
    enumerate = udev_enumerate_new(udev);
    if (!enumerate) {
        printf("Error while creating udev enumerate\n");
        return;
    }

    // Scan devices
    udev_enumerate_scan_devices(enumerate);

    // Fill up device list
    devices = udev_enumerate_get_list_entry(enumerate);
    if (!devices) {
        printf("Error while getting device list\n");
        return;
    }

    udev_list_entry_foreach(dev_list_entry, devices) {
        // Get the device
        device = udev_device_new_from_syspath(udev, udev_list_entry_get_name(dev_list_entry));
        // Print device information
        printf("DEVNODE=%s\n", udev_device_get_devnode(device));
        printf("KERNEL=%s\n", udev_device_get_sysname(device));
        printf("DEVPATH=%s\n", udev_device_get_devpath(device));
        printf("DEVTYPE=%s\n\n", udev_device_get_devtype(device));
        // Free the device
        udev_device_unref(device);
    }
    // Free enumerate
    udev_enumerate_unref(enumerate);
}

void monitorDevices(int signal_fd, struct udev *udev) {
    udev_monitor *monitor = udev_monitor_new_from_netlink(udev, "udev");
    struct pollfd pfd[2];
    int ret_poll;
    ssize_t  n;

    // Enable receiving hotplug events
    udev_monitor_enable_receiving(monitor);

    pfd[0].events = POLLIN;
    pfd[0].fd = signal_fd;
    pfd[1].events = POLLIN;
    pfd[1].fd = udev_monitor_get_fd(monitor);
    if (pfd[1].fd < 0) {
        printf("Error while getting hotplug monitor\n");
        udev_monitor_unref(monitor);
        return;
    }

    while (true) {
        // Wait for events without time limit
        ret_poll = poll(pfd, 2, -1);
        if (ret_poll < 0) {
            printf("Error while polling file descriptors\n");
            break;
        }
        // True, if a signal from the operating system was sent to this process
        if (pfd[0].revents & POLLIN) {
            struct signalfd_siginfo signal_info;
            // Get the signal
            n = read(pfd[0].fd, &signal_info, sizeof(signal_info));
            // True, if an error occurred while getting the signal
            if (n == -1) {
                printf("Error while read on signal file descriptor\n");
                break;
            }
            // Check which signal was caught
            switch (signal_info.ssi_signo) {
                case SIGINT:
                    printf("SIGINT received\n");
                    break;

                case SIGTERM:
                    printf("SIGTERM received\n");
                    break;

                default:
                    printf("Unknown signal received\n");
            }
            break;
        }
        if (pfd[1].revents & POLLIN) {
            // Get the device
            struct udev_device *device = udev_monitor_receive_device(monitor);
            if (!device) {
                printf("Error while getting device...returning to work\n");
                continue;
            }
            // Print device information
            printf("DEVNODE=%s\n", udev_device_get_devnode(device));
            printf("KERNEL=%s\n", udev_device_get_sysname(device));
            printf("DEVPATH=%s\n", udev_device_get_devpath(device));
            printf("DEVTYPE=%s\n\n", udev_device_get_devtype(device));
            // Free the device
            udev_device_unref(device);
        }
    }
    // Free the monitor
    udev_monitor_unref(monitor);
}

int main() {
    // Create a new udev object
    struct udev *udev = udev_new();
    if (!udev) {
        printf("Error while initialization!\n");
        return EXIT_FAILURE;
    }

    sigset_t mask;

    // Set signals we want to catch
    sigemptyset(&mask);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGINT);

    // Change the signal mask and check
    if (sigprocmask(SIG_BLOCK, &mask, nullptr) < 0) {
        fprintf(stderr, "Error while sigprocmask(): %s\n", std::strerror(errno));
        return EXIT_FAILURE;
    }
    // Get a signal file descriptor
    int signal_fd = signalfd(-1, &mask, 0);
    // Check the signal file descriptor
    if (signal_fd < 0) {
        fprintf(stderr, "Error while signalfd(): %s\n", std::strerror(errno));
        return EXIT_FAILURE;
    }
    // First scan already attached devices
    scanDevices(udev);
    // Second monitor hotplug events
    monitorDevices(signal_fd, udev);
    // Free the udev object
    udev_unref(udev);
}

I really would have liked to use netlink for everything, as other parts of my complete program uses it also. If anyone knows a possibility for using netlink for my primary question, I would be very grateful for sharing.

teawolf
  • 31
  • 4
  • Why you simple can't use `libusb`? – 0andriy Jun 16 '20 at 19:24
  • @0andriy I want as little dependencies as possible. I need libudev anyway since libusb uses it and I see, for my specific case, no advantage in using libusb that would justify the extra dependencies. – teawolf Jun 17 '20 at 14:34