Blob Blame History Raw
From c32774c8a46aa04bf8f882ea552df8cb2d9f3a59 Mon Sep 17 00:00:00 2001
From: Victor Toso <victortoso@redhat.com>
Date: Thu, 22 Dec 2022 16:58:43 +0100
Subject: [PATCH 8/8] usbredirect: use the correct bus-device

This patch is a continuation from:
"usbredirect: allow multiple devices by vendor:product"

As we were using libusb_open_device_with_vid_pid(), if an user
requested that device on 2-3 was redirected, we would instead get the
vendor and product info of device on 2-3 and use that info to pick a
usb device. This is wrong when multiple devices that shared
vendor:product are plugged as libbusb_open_device_with_vid_pid()
always return the same (first) device.

This commit now stores bus-device info and uses that to pick the usb
device to redirect.

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

diff --git a/tools/usbredirect.c b/tools/usbredirect.c
index 0b04418..cad9d23 100644
--- a/tools/usbredirect.c
+++ b/tools/usbredirect.c
@@ -24,9 +24,14 @@
 
 typedef struct redirect {
     struct {
+        /* vendor:product */
         int vendor;
         int product;
+        /* bus-device */
+        int bus;
+        int device_number;
     } device;
+    bool by_bus;
     bool is_client;
     bool keepalive;
     bool watch_inout;
@@ -46,7 +51,7 @@ typedef struct redirect {
 static void create_watch(redirect *self);
 
 static bool
-parse_opt_device(const char *device, int *vendor, int *product)
+parse_opt_device(redirect *self, const char *device)
 {
     if (!device) {
         g_warning("No device to redirect. For testing only\n");
@@ -54,37 +59,15 @@ parse_opt_device(const char *device, int *vendor, int *product)
     }
 
     if (g_strrstr(device, "-") != NULL) {
-        /* Get vendor and product by bus and address number */
+        self->by_bus = true;
         char **usbid = g_strsplit(device, "-", 2);
         if (usbid == NULL || usbid[0] == NULL || usbid[1] == NULL || usbid[2] != NULL) {
             g_strfreev(usbid);
             return false;
         }
-        gint64 bus = g_ascii_strtoll(usbid[0], NULL, 10);
-        gint64 addr = g_ascii_strtoll(usbid[1], NULL, 10);
-
-        libusb_device **list = NULL;
-        ssize_t i, n;
-
-        n = libusb_get_device_list(NULL, &list);
-        for (i = 0; i < n; i++) {
-            if (libusb_get_bus_number(list[i]) == bus &&
-                    libusb_get_device_address(list[i]) == addr) {
-                break;
-            }
-        }
-
-        if (i == n) {
-            libusb_free_device_list(list, true);
-            return false;
-        }
-
-        struct libusb_device_descriptor desc;
-        libusb_get_device_descriptor(list[i], &desc);
-        *vendor = desc.idVendor;
-        *product = desc.idProduct;
-
-        libusb_free_device_list(list, true);
+        self->device.bus = g_ascii_strtoll(usbid[0], NULL, 10);
+        self->device.device_number = g_ascii_strtoll(usbid[1], NULL, 10);
+        g_strfreev(usbid);
         return true;
     }
 
@@ -94,12 +77,14 @@ parse_opt_device(const char *device, int *vendor, int *product)
         return false;
     }
 
-    *vendor = g_ascii_strtoll(usbid[0], NULL, 16);
-    *product = g_ascii_strtoll(usbid[1], NULL, 16);
+    self->device.vendor = g_ascii_strtoll(usbid[0], NULL, 16);
+    self->device.product = g_ascii_strtoll(usbid[1], NULL, 16);
     g_strfreev(usbid);
 
-    if (*vendor <= 0 || *vendor > 0xffff || *product < 0 || *product > 0xffff) {
-        g_printerr("Bad vendor:product values %04x:%04x", *vendor, *product);
+    if (self->device.vendor <= 0 || self->device.vendor > 0xffff ||
+        self->device.product < 0 || self->device.product > 0xffff) {
+        g_printerr("Bad vendor:product values %04x:%04x",
+                   self->device.vendor, self->device.product);
         return false;
     }
 
@@ -166,7 +151,7 @@ parse_opts(int *argc, char ***argv)
 
     self = g_new0(redirect, 1);
     self->watch_inout = true;
-    if (!parse_opt_device(device, &self->device.vendor, &self->device.product)) {
+    if (!parse_opt_device(self, device)) {
         g_printerr("Failed to parse device: '%s' - expected: vendor:product or busnum-devnum\n", device);
         g_clear_pointer(&self, g_free);
         goto end;
@@ -535,9 +520,16 @@ open_usb_device(redirect *self)
             continue;
         }
 
-        if (self->device.vendor != desc.idVendor ||
-            self->device.product != desc.idProduct) {
-            continue;
+        if (self->by_bus &&
+            (self->device.bus != libusb_get_bus_number(devs[i]) ||
+             self->device.device_number != libusb_get_device_address(devs[i]))) {
+             continue;
+        }
+
+        if (!self->by_bus &&
+            (self->device.vendor != desc.idVendor ||
+             self->device.product != desc.idProduct)) {
+             continue;
         }
 
         if (can_claim_usb_device(devs[i], &dev_handle)) {
@@ -674,6 +666,7 @@ main(int argc, char *argv[])
 
         socket_service = g_socket_service_new ();
         GInetAddress *iaddr = g_inet_address_new_loopback(G_SOCKET_FAMILY_IPV4);
+
         GSocketAddress *saddr = g_inet_socket_address_new(iaddr, self->port);
         g_object_unref(iaddr);
 
-- 
2.39.0