Blob Blame History Raw
From 813afe206c8ff10cbbe715bf94980bf8ba6be70f Mon Sep 17 00:00:00 2001
From: Victor Toso <victortoso@redhat.com>
Date: Thu, 22 Dec 2022 16:13:08 +0100
Subject: [PATCH 7/8] usbredirect: allow multiple devices by vendor:product

Currently, if an user tries to redirect two devices with the same
vendor:product info, the second instance of usbredirect will not
succeed, leading to a segmentation fault.

The core of the problem is that usbredirect is using
libusb_open_device_with_vid_pid() which always returns the first
instance of a given vendor:product, leading the second instance of
usbredirect to give an usb device that is in use to usbredirhost.

This patch fixes the situation, making it possible to run usbredirect
with --device $vendor:$product multiple times. We do a early check
that we can claim the usb device, prior to handle it over to
usbredirhost.

Related: https://gitlab.freedesktop.org/spice/usbredir/-/issues/29
Signed-off-by: Victor Toso <victortoso@redhat.com>
---
 tools/usbredirect.c | 90 ++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 85 insertions(+), 5 deletions(-)

diff --git a/tools/usbredirect.c b/tools/usbredirect.c
index 95f3580..0b04418 100644
--- a/tools/usbredirect.c
+++ b/tools/usbredirect.c
@@ -466,6 +466,90 @@ signal_handler(gpointer user_data)
 }
 #endif
 
+static bool
+can_claim_usb_device(libusb_device *dev, libusb_device_handle **handle)
+{
+    int ret = libusb_open(dev, handle);
+    if (ret != 0) {
+        g_debug("Failed to open device");
+        return false;
+    }
+
+    /* Opening is not enough. We need to check if device can be claimed
+     * for I/O operations */
+    struct libusb_config_descriptor *config = NULL;
+    ret = libusb_get_active_config_descriptor(dev, &config);
+    if (ret != 0 || config == NULL) {
+        g_debug("Failed to get active descriptor");
+        *handle = NULL;
+        return false;
+    }
+
+#if LIBUSBX_API_VERSION >= 0x01000102
+    libusb_set_auto_detach_kernel_driver(*handle, 1);
+#endif
+
+    int i;
+    for (i = 0; i < config->bNumInterfaces; i++) {
+        int interface_num = config->interface[i].altsetting[0].bInterfaceNumber;
+#if LIBUSBX_API_VERSION < 0x01000102
+        ret = libusb_detach_kernel_driver(handle, interface_num);
+        if (ret != 0 && ret != LIBUSB_ERROR_NOT_FOUND
+            && ret != LIBUSB_ERROR_NOT_SUPPORTED) {
+            g_error("failed to detach driver from interface %d: %s",
+                    interface_num, libusb_error_name(ret));
+            *handle = NULL;
+            break
+        }
+#endif
+        ret = libusb_claim_interface(*handle, interface_num);
+        if (ret != 0) {
+            g_debug("Could not claim interface");
+            *handle = NULL;
+            break;
+        }
+        ret = libusb_release_interface(*handle, interface_num);
+        if (ret != 0) {
+            g_debug("Could not release interface");
+            *handle = NULL;
+            break;
+        }
+    }
+
+    libusb_free_config_descriptor(config);
+    return *handle != NULL;
+}
+
+static libusb_device_handle *
+open_usb_device(redirect *self)
+{
+    struct libusb_device **devs;
+    struct libusb_device_handle *dev_handle = NULL;
+    size_t i, ndevices;
+
+    ndevices = libusb_get_device_list(NULL, &devs);
+    for (i = 0; i < ndevices; i++) {
+        struct libusb_device_descriptor desc;
+        if (libusb_get_device_descriptor(devs[i], &desc) != 0) {
+            g_warning("Failed to get descriptor");
+            continue;
+        }
+
+        if (self->device.vendor != desc.idVendor ||
+            self->device.product != desc.idProduct) {
+            continue;
+        }
+
+        if (can_claim_usb_device(devs[i], &dev_handle)) {
+            break;
+        }
+    }
+
+    libusb_free_device_list(devs, 1);
+    return dev_handle;
+}
+
+
 static gboolean
 connection_incoming_cb(GSocketService    *service,
                        GSocketConnection *client_connection,
@@ -515,11 +599,7 @@ main(int argc, char *argv[])
     g_unix_signal_add(SIGTERM, signal_handler, self);
 #endif
 
-    /* This is binary is not meant to support plugins so it is safe to pass
-     * NULL as libusb_context here and all subsequent calls */
-    libusb_device_handle *device_handle = libusb_open_device_with_vid_pid(NULL,
-            self->device.vendor,
-            self->device.product);
+    libusb_device_handle *device_handle = open_usb_device(self);
     if (!device_handle) {
         g_printerr("Failed to open device!\n");
         goto err_init;
-- 
2.39.0