Skip to content

Commit 8608a71

Browse files
authored
Merge pull request #31 from libusb/claude/fix-libusb-compat-issue-2wVhM
Fix memory leaks in USB bus and device cleanup
2 parents f16e0b3 + 46da511 commit 8608a71

5 files changed

Lines changed: 167 additions & 3 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ compile
2929
depcomp
3030
examples/lsusb
3131
examples/testlibusb
32+
examples/hotplug_monitor
3233
config.h.in
3334
libtool

examples/Makefile.am

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
AM_CPPFLAGS = -I$(top_srcdir)/libusb
2-
noinst_PROGRAMS = lsusb testlibusb
2+
noinst_PROGRAMS = lsusb testlibusb hotplug_monitor
33

44
lsusb_SOURCES = lsusb.c
55
lsusb_LDADD = ../libusb/libusb.la
66

77
testlibusb_SOURCES = testlibusb.c
88
testlibusb_LDADD = ../libusb/libusb.la
99

10+
hotplug_monitor_SOURCES = hotplug-monitor.c
11+
hotplug_monitor_LDADD = ../libusb/libusb.la
12+

examples/README

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
libusb-compat-0.1 examples
2+
==========================
3+
4+
Small programs that exercise the libusb-0.1 compatibility shim. They are
5+
built automatically by `make` but are not installed.
6+
7+
lsusb
8+
-----
9+
Prints `idVendor:idProduct` for every USB device. The smallest possible
10+
demonstration of the libusb-0.1 enumeration API.
11+
12+
testlibusb [-v]
13+
---------------
14+
Walks every bus and device, opens each one, and prints descriptors.
15+
With `-v`, also prints each device's full configuration tree.
16+
17+
hotplug_monitor [poll-ms]
18+
-------------------------
19+
Polls usb_find_busses() and usb_find_devices() in a loop and prints the
20+
current bus/device tree whenever either reports a change. Useful for
21+
exercising the bus-removal path in usb_find_busses() and for verifying
22+
that exit produces no "device X.Y still referenced" warnings from the
23+
underlying libusb (issue #25).
24+
25+
Default polling interval is 500 ms; pass an integer argv[1] in
26+
milliseconds to override. Exit with Ctrl-C; the destructor runs and
27+
frees all retained device references.
28+
29+
Triggering hot-plug events
30+
~~~~~~~~~~~~~~~~~~~~~~~~~~
31+
32+
* Device add/remove: plug or unplug any USB device.
33+
34+
* Bus add/remove (Linux, requires root): unbind and rebind the USB
35+
host controller PCI device. This is the only way to make a whole
36+
bus disappear from libusb's perspective on most systems.
37+
38+
lspci -D | grep USB
39+
# pick the BDF of the controller you want to drop, e.g.
40+
echo 0000:00:14.0 | sudo tee /sys/bus/pci/drivers/xhci_hcd/unbind
41+
echo 0000:00:14.0 | sudo tee /sys/bus/pci/drivers/xhci_hcd/bind
42+
43+
Replace `xhci_hcd` with `ehci-pci`, `ohci-pci`, or `uhci_hcd` as
44+
appropriate. Unbinding the controller you booted from will make
45+
attached input devices stop working until you rebind it; consider
46+
picking a controller that is not in use.
47+
48+
Verifying reference accounting
49+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
50+
51+
Run with the libusb-1.0 debug level cranked up and watch the exit
52+
output:
53+
54+
LIBUSB_DEBUG=4 ./hotplug_monitor
55+
56+
After Ctrl-C there should be no `device X.Y still referenced` warnings
57+
from `libusb_exit`. For an exhaustive memory check, run under valgrind:
58+
59+
LIBUSB_DEBUG=4 valgrind --leak-check=full ./hotplug_monitor

examples/hotplug-monitor.c

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* hotplug-monitor.c -- exercise libusb-compat-0.1 hot-plug paths.
3+
*
4+
* Polls usb_find_busses() and usb_find_devices() in a loop and prints the
5+
* current bus/device tree whenever either reports a change. Useful for
6+
* exercising the bus-removal path in usb_find_busses() and for verifying
7+
* that a clean Ctrl-C exit produces no "device X.Y still referenced"
8+
* warnings from the underlying libusb (issue #25).
9+
*
10+
* See examples/README for how to trigger bus add/remove on Linux.
11+
*
12+
* This file is in the public domain.
13+
*/
14+
15+
#include <signal.h>
16+
#include <stdio.h>
17+
#include <stdlib.h>
18+
#include <unistd.h>
19+
#include <usb.h>
20+
21+
static volatile sig_atomic_t running = 1;
22+
23+
static void on_signal(int signo)
24+
{
25+
(void) signo;
26+
running = 0;
27+
}
28+
29+
static void print_state(void)
30+
{
31+
struct usb_bus *bus;
32+
for (bus = usb_get_busses(); bus; bus = bus->next) {
33+
struct usb_device *dev;
34+
printf(" bus %s\n", bus->dirname);
35+
for (dev = bus->devices; dev; dev = dev->next)
36+
printf(" dev %s %04x:%04x\n",
37+
dev->filename,
38+
dev->descriptor.idVendor,
39+
dev->descriptor.idProduct);
40+
}
41+
}
42+
43+
int main(int argc, char *argv[])
44+
{
45+
unsigned int interval_ms = 500;
46+
int iter = 0;
47+
48+
if (argc > 1) {
49+
int v = atoi(argv[1]);
50+
if (v > 0)
51+
interval_ms = (unsigned int) v;
52+
}
53+
54+
signal(SIGINT, on_signal);
55+
signal(SIGTERM, on_signal);
56+
57+
usb_init();
58+
59+
while (running) {
60+
int bus_changes = usb_find_busses();
61+
int dev_changes = usb_find_devices();
62+
63+
if (iter == 0 || bus_changes || dev_changes) {
64+
printf("[iter %d] bus_changes=%d dev_changes=%d\n",
65+
iter, bus_changes, dev_changes);
66+
print_state();
67+
fflush(stdout);
68+
}
69+
70+
iter++;
71+
usleep(interval_ms * 1000u);
72+
}
73+
74+
printf("exiting\n");
75+
return 0;
76+
}

libusb/core.c

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,29 @@ static void __attribute__ ((constructor)) _usb_init (void)
8181
}
8282
#endif
8383

84+
static void free_device(struct usb_device *dev);
85+
86+
static void free_bus(struct usb_bus *bus)
87+
{
88+
struct usb_device *dev = bus->devices;
89+
while (dev) {
90+
struct usb_device *tdev = dev->next;
91+
free_device(dev);
92+
dev = tdev;
93+
}
94+
free(bus);
95+
}
96+
8497
static void __attribute__ ((destructor)) _usb_exit (void)
8598
{
99+
struct usb_bus *bus = usb_busses;
100+
while (bus) {
101+
struct usb_bus *tbus = bus->next;
102+
free_bus(bus);
103+
bus = tbus;
104+
}
105+
usb_busses = NULL;
106+
86107
if (ctx) {
87108
libusb_exit (ctx);
88109
ctx = NULL;
@@ -261,6 +282,7 @@ static int find_busses(struct usb_bus **ret)
261282
return 0;
262283

263284
err:
285+
libusb_free_device_list(dev_list, 1);
264286
bus = busses;
265287
while (bus) {
266288
struct usb_bus *tbus = bus->next;
@@ -317,7 +339,7 @@ API_EXPORTED int usb_find_busses(void)
317339
usbi_dbg("bus %d removed", bus->location);
318340
changes++;
319341
LIST_DEL(usb_busses, bus);
320-
free(bus);
342+
free_bus(bus);
321343
}
322344

323345
bus = tbus;
@@ -591,6 +613,7 @@ static int initialize_device(struct usb_device *dev)
591613
static void free_device(struct usb_device *dev)
592614
{
593615
clear_device(dev);
616+
free(dev->config);
594617
libusb_unref_device(dev->dev);
595618
free(dev);
596619
}
@@ -657,10 +680,12 @@ API_EXPORTED int usb_find_devices(void)
657680
dev = new_devices;
658681
while (dev) {
659682
struct usb_device *tdev = dev->next;
660-
r = initialize_device(dev);
683+
r = initialize_device(dev);
661684
if (r < 0) {
662685
usbi_err("couldn't initialize device %d.%d (error %d)",
663686
dev->bus->location, dev->devnum, r);
687+
LIST_DEL(new_devices, dev);
688+
free(dev);
664689
dev = tdev;
665690
continue;
666691
}

0 commit comments

Comments
 (0)