root/tools/usb/usbip/src/usbip_list.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2011 matt mooney <mfm@muteddisk.com>
 *               2005-2007 Takahiro Hirofuchi
 * Copyright (C) 2015-2016 Samsung Electronics
 *               Igor Kotrasinski <i.kotrasinsk@samsung.com>
 *               Krzysztof Opasiak <k.opasiak@samsung.com>
 */

#include <sys/types.h>
#include <libudev.h>

#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <getopt.h>
#include <netdb.h>
#include <unistd.h>

#include <dirent.h>

#include <linux/usb/ch9.h>

#include "usbip_common.h"
#include "usbip_network.h"
#include "usbip.h"

static const char usbip_list_usage_string[] =
        "usbip list [-p|--parsable] <args>\n"
        "    -p, --parsable         Parsable list format\n"
        "    -r, --remote=<host>    List the exportable USB devices on <host>\n"
        "    -l, --local            List the local USB devices\n"
        "    -d, --device           List the local USB gadgets bound to usbip-vudc\n";

void usbip_list_usage(void)
{
        printf("usage: %s", usbip_list_usage_string);
}

static int get_exported_devices(char *host, int sockfd)
{
        char product_name[100];
        char class_name[100];
        struct op_devlist_reply reply;
        uint16_t code = OP_REP_DEVLIST;
        struct usbip_usb_device udev;
        struct usbip_usb_interface uintf;
        unsigned int i;
        int rc, j;
        int status;

        rc = usbip_net_send_op_common(sockfd, OP_REQ_DEVLIST, 0);
        if (rc < 0) {
                dbg("usbip_net_send_op_common failed");
                return -1;
        }

        rc = usbip_net_recv_op_common(sockfd, &code, &status);
        if (rc < 0) {
                err("Exported Device List Request failed - %s\n",
                    usbip_op_common_status_string(status));
                return -1;
        }

        memset(&reply, 0, sizeof(reply));
        rc = usbip_net_recv(sockfd, &reply, sizeof(reply));
        if (rc < 0) {
                dbg("usbip_net_recv_op_devlist failed");
                return -1;
        }
        PACK_OP_DEVLIST_REPLY(0, &reply);
        dbg("exportable devices: %d\n", reply.ndev);

        if (reply.ndev == 0) {
                info("no exportable devices found on %s", host);
                return 0;
        }

        printf("Exportable USB devices\n");
        printf("======================\n");
        printf(" - %s\n", host);

        for (i = 0; i < reply.ndev; i++) {
                memset(&udev, 0, sizeof(udev));
                rc = usbip_net_recv(sockfd, &udev, sizeof(udev));
                if (rc < 0) {
                        dbg("usbip_net_recv failed: usbip_usb_device[%d]", i);
                        return -1;
                }
                usbip_net_pack_usb_device(0, &udev);

                usbip_names_get_product(product_name, sizeof(product_name),
                                        udev.idVendor, udev.idProduct);
                usbip_names_get_class(class_name, sizeof(class_name),
                                      udev.bDeviceClass, udev.bDeviceSubClass,
                                      udev.bDeviceProtocol);
                printf("%11s: %s\n", udev.busid, product_name);
                printf("%11s: %s\n", "", udev.path);
                printf("%11s: %s\n", "", class_name);

                for (j = 0; j < udev.bNumInterfaces; j++) {
                        rc = usbip_net_recv(sockfd, &uintf, sizeof(uintf));
                        if (rc < 0) {
                                err("usbip_net_recv failed: usbip_usb_intf[%d]",
                                                j);

                                return -1;
                        }
                        usbip_net_pack_usb_interface(0, &uintf);

                        usbip_names_get_class(class_name, sizeof(class_name),
                                        uintf.bInterfaceClass,
                                        uintf.bInterfaceSubClass,
                                        uintf.bInterfaceProtocol);
                        printf("%11s: %2d - %s\n", "", j, class_name);
                }

                printf("\n");
        }

        return 0;
}

static int list_exported_devices(char *host)
{
        int rc;
        int sockfd;

        sockfd = usbip_net_tcp_connect(host, usbip_port_string);
        if (sockfd < 0) {
                err("could not connect to %s:%s: %s", host,
                    usbip_port_string, gai_strerror(sockfd));
                return -1;
        }
        dbg("connected to %s:%s", host, usbip_port_string);

        rc = get_exported_devices(host, sockfd);
        if (rc < 0) {
                err("failed to get device list from %s", host);
                return -1;
        }

        close(sockfd);

        return 0;
}

static void print_device(const char *busid, const char *vendor,
                         const char *product, bool parsable)
{
        if (parsable)
                printf("busid=%s#usbid=%.4s:%.4s#", busid, vendor, product);
        else
                printf(" - busid %s (%.4s:%.4s)\n", busid, vendor, product);
}

static void print_product_name(char *product_name, bool parsable)
{
        if (!parsable)
                printf("   %s\n", product_name);
}

static int list_devices(bool parsable)
{
        struct udev *udev;
        struct udev_enumerate *enumerate;
        struct udev_list_entry *devices, *dev_list_entry;
        struct udev_device *dev;
        const char *path;
        const char *idVendor;
        const char *idProduct;
        const char *bConfValue;
        const char *bNumIntfs;
        const char *busid;
        char product_name[128];
        int ret = -1;
        const char *devpath;

        /* Create libudev context. */
        udev = udev_new();

        /* Create libudev device enumeration. */
        enumerate = udev_enumerate_new(udev);

        /* Take only USB devices that are not hubs and do not have
         * the bInterfaceNumber attribute, i.e. are not interfaces.
         */
        udev_enumerate_add_match_subsystem(enumerate, "usb");
        udev_enumerate_add_nomatch_sysattr(enumerate, "bDeviceClass", "09");
        udev_enumerate_add_nomatch_sysattr(enumerate, "bInterfaceNumber", NULL);
        udev_enumerate_scan_devices(enumerate);

        devices = udev_enumerate_get_list_entry(enumerate);

        /* Show information about each device. */
        udev_list_entry_foreach(dev_list_entry, devices) {
                path = udev_list_entry_get_name(dev_list_entry);
                dev = udev_device_new_from_syspath(udev, path);

                /* Ignore devices attached to vhci_hcd */
                devpath = udev_device_get_devpath(dev);
                if (strstr(devpath, USBIP_VHCI_DRV_NAME)) {
                        dbg("Skip the device %s already attached to %s\n",
                            devpath, USBIP_VHCI_DRV_NAME);
                        continue;
                }

                /* Get device information. */
                idVendor = udev_device_get_sysattr_value(dev, "idVendor");
                idProduct = udev_device_get_sysattr_value(dev, "idProduct");
                bConfValue = udev_device_get_sysattr_value(dev,
                                "bConfigurationValue");
                bNumIntfs = udev_device_get_sysattr_value(dev,
                                "bNumInterfaces");
                busid = udev_device_get_sysname(dev);
                if (!idVendor || !idProduct || !bConfValue || !bNumIntfs) {
                        err("problem getting device attributes: %s",
                            strerror(errno));
                        goto err_out;
                }

                /* Get product name. */
                usbip_names_get_product(product_name, sizeof(product_name),
                                        strtol(idVendor, NULL, 16),
                                        strtol(idProduct, NULL, 16));

                /* Print information. */
                print_device(busid, idVendor, idProduct, parsable);
                print_product_name(product_name, parsable);

                printf("\n");

                udev_device_unref(dev);
        }

        ret = 0;

err_out:
        udev_enumerate_unref(enumerate);
        udev_unref(udev);

        return ret;
}

static int list_gadget_devices(bool parsable)
{
        int ret = -1;
        struct udev *udev;
        struct udev_enumerate *enumerate;
        struct udev_list_entry *devices, *dev_list_entry;
        struct udev_device *dev;
        const char *path;
        const char *driver;

        const struct usb_device_descriptor *d_desc;
        const char *descriptors;
        char product_name[128];

        uint16_t idVendor;
        char idVendor_buf[8];
        uint16_t idProduct;
        char idProduct_buf[8];
        const char *busid;

        udev = udev_new();
        enumerate = udev_enumerate_new(udev);

        udev_enumerate_add_match_subsystem(enumerate, "platform");

        udev_enumerate_scan_devices(enumerate);
        devices = udev_enumerate_get_list_entry(enumerate);

        udev_list_entry_foreach(dev_list_entry, devices) {
                path = udev_list_entry_get_name(dev_list_entry);
                dev = udev_device_new_from_syspath(udev, path);

                driver = udev_device_get_driver(dev);
                /* We only have mechanism to enumerate gadgets bound to vudc */
                if (driver == NULL || strcmp(driver, USBIP_DEVICE_DRV_NAME))
                        continue;

                /* Get device information. */
                descriptors = udev_device_get_sysattr_value(dev,
                                VUDC_DEVICE_DESCR_FILE);

                if (!descriptors) {
                        err("problem getting device attributes: %s",
                            strerror(errno));
                        goto err_out;
                }

                d_desc = (const struct usb_device_descriptor *) descriptors;

                idVendor = le16toh(d_desc->idVendor);
                sprintf(idVendor_buf, "0x%4x", idVendor);
                idProduct = le16toh(d_desc->idProduct);
                sprintf(idProduct_buf, "0x%4x", idVendor);
                busid = udev_device_get_sysname(dev);

                /* Get product name. */
                usbip_names_get_product(product_name, sizeof(product_name),
                                        le16toh(idVendor),
                                        le16toh(idProduct));

                /* Print information. */
                print_device(busid, idVendor_buf, idProduct_buf, parsable);
                print_product_name(product_name, parsable);

                printf("\n");

                udev_device_unref(dev);
        }
        ret = 0;

err_out:
        udev_enumerate_unref(enumerate);
        udev_unref(udev);

        return ret;
}

int usbip_list(int argc, char *argv[])
{
        static const struct option opts[] = {
                { "parsable", no_argument,       NULL, 'p' },
                { "remote",   required_argument, NULL, 'r' },
                { "local",    no_argument,       NULL, 'l' },
                { "device",    no_argument,       NULL, 'd' },
                { NULL,       0,                 NULL,  0  }
        };

        bool parsable = false;
        int opt;
        int ret = -1;

        if (usbip_names_init(USBIDS_FILE))
                err("failed to open %s", USBIDS_FILE);

        for (;;) {
                opt = getopt_long(argc, argv, "pr:ld", opts, NULL);

                if (opt == -1)
                        break;

                switch (opt) {
                case 'p':
                        parsable = true;
                        break;
                case 'r':
                        ret = list_exported_devices(optarg);
                        goto out;
                case 'l':
                        ret = list_devices(parsable);
                        goto out;
                case 'd':
                        ret = list_gadget_devices(parsable);
                        goto out;
                default:
                        goto err_out;
                }
        }

err_out:
        usbip_list_usage();
out:
        usbip_names_free();

        return ret;
}