core: ensure that all devices are properly cleaned up on libusb_exit

When cleaning up the context on libusb_exit the last step is to to call
hotplug_exit. This function took one pass over the devices and released any
devices where the reference count had reached 0. All remaining devices
were assumed to have leaked references and a warning message was printed
out. Unfortunately, this cleanup was too simplistic. It ignored the
references created when a device was the parent of another device (which
takes a reference). This reference is released when the child device is
released.

To ensure that we do not erroneously warn about devices with leftover
references the code now loops over the device list until no more devices
have a single reference. This ensures that we will eventually remove
all devices with no external references.

Fixes #924

Signed-off-by: Nathan Hjelm <hjelmn@google.com>
diff --git a/libusb/hotplug.c b/libusb/hotplug.c
index 387b49f..deb138d 100644
--- a/libusb/hotplug.c
+++ b/libusb/hotplug.c
@@ -166,6 +166,7 @@
 	struct usbi_hotplug_callback *hotplug_cb, *next_cb;
 	struct usbi_hotplug_message *msg;
 	struct libusb_device *dev, *next_dev;
+	int devices_released;
 
 	/* check for hotplug support */
 	if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG))
@@ -190,15 +191,19 @@
 		free(msg);
 	}
 
-	/* free all discovered devices */
-	for_each_device_safe(ctx, dev, next_dev) {
-		/* remove the device from the usb_devs list only if there are no
-		 * references held, otherwise leave it on the list so that a
-		 * warning message will be shown */
-		if (usbi_atomic_load(&dev->refcnt) == 1)
-			list_del(&dev->list);
-		libusb_unref_device(dev);
-	}
+	/* free all discovered devices. due to parent references loop until no devices are freed. */
+	do {
+		devices_released = 0;
+		for_each_device_safe(ctx, dev, next_dev) {
+			/* remove the device from the usb_devs list only if there are no
+			 * references held, otherwise leave it on the list so that a
+			 * warning message will be shown */
+			if (usbi_atomic_load(&dev->refcnt) == 1)
+				list_del(&dev->list);
+			libusb_unref_device(dev);
+			++devices_released;
+		}
+	} while (devices_released > 0);
 
 	usbi_mutex_destroy(&ctx->hotplug_cbs_lock);
 }
diff --git a/libusb/version_nano.h b/libusb/version_nano.h
index 9a19b34..21591b9 100644
--- a/libusb/version_nano.h
+++ b/libusb/version_nano.h
@@ -1 +1 @@
-#define LIBUSB_NANO 11623
+#define LIBUSB_NANO 11624