Rework configuration handling

libusb no longer caches descriptors in libusb_device but backends are
intended to be able to provide copies from memory. In the common linux
case we can use sysfs.
diff --git a/TODO b/TODO
index 478e43e..2b53246 100644
--- a/TODO
+++ b/TODO
@@ -7,6 +7,7 @@
 internal docs for OS porters
 configuration handling
 check which messages are sent during open, claim interface, close, release
+unconfigured devices
 
 1.0 API style/naming points to reconsider
 =========================================
diff --git a/examples/lsusb.c b/examples/lsusb.c
index 509e0bd..5a83020 100644
--- a/examples/lsusb.c
+++ b/examples/lsusb.c
@@ -28,10 +28,15 @@
 	int i = 0;
 
 	while ((dev = devs[i++]) != NULL) {
-		const struct libusb_device_descriptor *desc =
-			libusb_get_device_descriptor(dev);
+		struct libusb_device_descriptor desc;
+		int r = libusb_get_device_descriptor(dev, &desc);
+		if (r < 0) {
+			fprintf(stderr, "failed to get device descriptor");
+			return;
+		}
+
 		printf("%04x:%04x (bus %d, device %d)\n",
-			desc->idVendor, desc->idProduct,
+			desc.idVendor, desc.idProduct,
 			libusb_get_bus_number(dev), libusb_get_device_address(dev));
 	}
 }
diff --git a/libusb/core.c b/libusb/core.c
index f83305e..43491ad 100644
--- a/libusb/core.c
+++ b/libusb/core.c
@@ -304,95 +304,28 @@
 	return dev;
 }
 
-/* call the OS discovery routines to populate descriptors etc */
-int usbi_discover_device(struct libusb_device *dev)
+/* to be called by OS implementations when a new device is ready for final
+ * sanitization and checking before being returned in a device list. */
+int usbi_sanitize_device(struct libusb_device *dev)
 {
 	int r;
-	int i;
-	void *user_data;
 	unsigned char raw_desc[DEVICE_DESC_LENGTH];
-	size_t alloc_size;
+	uint8_t num_configurations;
 
-	dev->config = NULL;
-
-	r = usbi_backend->begin_discovery(dev, &user_data);
+	r = usbi_backend->get_device_descriptor(dev, raw_desc);
 	if (r < 0)
 		return r;
-	
-	r = usbi_backend->get_device_descriptor(dev, raw_desc, user_data);
-	if (r < 0)
-		goto err;
 
-	usbi_parse_descriptor(raw_desc, "bbWbbbbWWWbbbb", &dev->desc);
-
-	if (dev->desc.bNumConfigurations > USB_MAXCONFIG) {
+	num_configurations = raw_desc[DEVICE_DESC_LENGTH - 1];
+	if (num_configurations > USB_MAXCONFIG) {
 		usbi_err("too many configurations");
-		r = LIBUSB_ERROR_IO;
-		goto err;
-	}
-
-	if (dev->desc.bNumConfigurations < 1) {
+		return LIBUSB_ERROR_IO;
+	} else if (num_configurations < 1) {
 		usbi_dbg("no configurations?");
-		r = LIBUSB_ERROR_IO;
-		goto err;
+		return LIBUSB_ERROR_IO;
 	}
 
-	alloc_size = dev->desc.bNumConfigurations
-		* sizeof(struct libusb_config_descriptor);
-	dev->config = malloc(alloc_size);
-	if (!dev->config) {
-		r = LIBUSB_ERROR_NO_MEM;
-		goto err;
-	}
-
-	memset(dev->config, 0, alloc_size);
-	for (i = 0; i < dev->desc.bNumConfigurations; i++) {
-		unsigned char tmp[8];
-		unsigned char *bigbuffer;
-		struct libusb_config_descriptor config;
-
-		r = usbi_backend->get_config_descriptor(dev, i, tmp, sizeof(tmp),
-			user_data);
-		if (r < 0)
-			goto err;
-
-		usbi_parse_descriptor(tmp, "bbw", &config);
-
-		bigbuffer = malloc(config.wTotalLength);
-		if (!bigbuffer) {
-			r = LIBUSB_ERROR_NO_MEM;
-			goto err;
-		}
-
-		r = usbi_backend->get_config_descriptor(dev, i, bigbuffer,
-			config.wTotalLength, user_data);
-		if (r < 0) {
-			free(bigbuffer);
-			goto err;
-		}
-
-		r = usbi_parse_configuration(&dev->config[i], bigbuffer);
-		free(bigbuffer);
-		if (r < 0) {
-			usbi_err("parse_configuration failed with code %d", r);
-			goto err;
-		} else if (r > 0) {
-			usbi_warn("descriptor data still left\n");
-		}
-	}
-
-	usbi_backend->end_discovery(dev, user_data);
 	return 0;
-
-err:
-	if (dev->config) {
-		usbi_clear_configurations(dev);
-		free(dev->config);
-		dev->config = NULL;
-	}
-
-	usbi_backend->end_discovery(dev, user_data);
-	return r;
 }
 
 struct libusb_device *usbi_get_device_by_session_id(unsigned long session_id)
@@ -513,19 +446,27 @@
 
 /** \ingroup dev
  * Convenience function to retrieve the wMaxPacketSize value for a particular
- * endpoint. This is useful for setting up isochronous transfers.
+ * endpoint in the active device configuration. This is useful for setting up
+ * isochronous transfers.
  *
  * \param dev a device
  * \param endpoint address of the endpoint in question
- * \returns the wMaxPacketSize value, or LIBUSB_ERROR_NOT_FOUND if the endpoint
- * does not exist.
+ * \returns the wMaxPacketSize value
+ * \returns LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist
+ * \returns LIBUSB_ERROR_OTHER on other failure
  */
 API_EXPORTED int libusb_get_max_packet_size(libusb_device *dev,
 	unsigned char endpoint)
 {
 	int iface_idx;
-	/* FIXME: active config considerations? */
-	struct libusb_config_descriptor *config = dev->config;
+	struct libusb_config_descriptor *config =
+		libusb_get_active_config_descriptor(dev);
+	int r = LIBUSB_ERROR_NOT_FOUND;
+
+	if (!config) {
+		usbi_err("could not retrieve active config descriptor");
+		return LIBUSB_ERROR_OTHER;
+	}
 
 	for (iface_idx = 0; iface_idx < config->bNumInterfaces; iface_idx++) {
 		const struct libusb_interface *iface = &config->interface[iface_idx];
@@ -540,13 +481,17 @@
 			for (ep_idx = 0; ep_idx < altsetting->bNumEndpoints; ep_idx++) {
 				const struct libusb_endpoint_descriptor *ep =
 					&altsetting->endpoint[ep_idx];
-				if (ep->bEndpointAddress == endpoint)
-					return ep->wMaxPacketSize;
+				if (ep->bEndpointAddress == endpoint) {
+					r = ep->wMaxPacketSize;
+					goto out;
+				}
 			}
 		}
 	}
 
-	return LIBUSB_ERROR_NOT_FOUND;
+out:
+	libusb_free_config_descriptor(config);
+	return r;
 }
 
 /** \ingroup dev
@@ -579,8 +524,7 @@
 	pthread_mutex_unlock(&dev->lock);
 
 	if (refcnt == 0) {
-		usbi_dbg("destroy device %04x:%04x", dev->desc.idVendor,
-			dev->desc.idProduct);
+		usbi_dbg("destroy device %d.%d", dev->bus_number, dev->device_address);
 
 		if (usbi_backend->destroy_device)
 			usbi_backend->destroy_device(dev);
@@ -589,10 +533,6 @@
 		list_del(&dev->list);
 		pthread_mutex_unlock(&usb_devs_lock);
 
-		if (dev->config) {
-			usbi_clear_configurations(dev);
-			free(dev->config);
-		}
 		free(dev);
 	}
 }
@@ -615,7 +555,7 @@
 	struct libusb_device_handle *handle;
 	size_t priv_size = usbi_backend->device_handle_priv_size;
 	int r;
-	usbi_dbg("open %04x:%04x", dev->desc.idVendor, dev->desc.idProduct);
+	usbi_dbg("open %d.%d", dev->bus_number, dev->device_address);
 
 	handle = malloc(sizeof(*handle) + priv_size);
 	if (!handle)
@@ -670,9 +610,11 @@
 		return NULL;
 
 	while ((dev = devs[i++]) != NULL) {
-		const struct libusb_device_descriptor *desc =
-			libusb_get_device_descriptor(dev);
-		if (desc->idVendor == vendor_id && desc->idProduct == product_id) {
+		struct libusb_device_descriptor desc;
+		int r = libusb_get_device_descriptor(dev, &desc);
+		if (r < 0)
+			goto out;
+		if (desc.idVendor == vendor_id && desc.idProduct == product_id) {
 			found = dev;
 			break;
 		}
@@ -681,6 +623,7 @@
 	if (found)
 		handle = libusb_open(found);
 
+out:
 	libusb_free_device_list(devs, 1);
 	return handle;
 }
diff --git a/libusb/descriptor.c b/libusb/descriptor.c
index 822306d..e07711a 100644
--- a/libusb/descriptor.c
+++ b/libusb/descriptor.c
@@ -325,14 +325,7 @@
 		free((void *) config->extra);
 }
 
-void usbi_clear_configurations(struct libusb_device *dev)
-{
-	int i;
-	for (i = 0; i < dev->desc.bNumConfigurations; i++)
-		clear_configuration(dev->config + i);
-}
-
-int usbi_parse_configuration(struct libusb_config_descriptor *config,
+static int parse_configuration(struct libusb_config_descriptor *config,
 	unsigned char *buffer)
 {
 	int i;
@@ -430,23 +423,149 @@
  * This is a non-blocking function; the device descriptor is cached in memory.
  *
  * \param dev the device
- * \returns the USB device descriptor
+ * \param desc output location for the descriptor data
+ * \returns 0 on success or a LIBUSB_ERROR code on failure
  */
-API_EXPORTED const struct libusb_device_descriptor *libusb_get_device_descriptor(
-	libusb_device *dev)
+API_EXPORTED int libusb_get_device_descriptor(libusb_device *dev,
+	struct libusb_device_descriptor *desc)
 {
-	return &dev->desc;
+	unsigned char raw_desc[DEVICE_DESC_LENGTH];
+	int r;
+
+	r = usbi_backend->get_device_descriptor(dev, raw_desc);
+	if (r < 0)
+		return r;
+
+	usbi_parse_descriptor(raw_desc, "bbWbbbbWWWbbbb", desc);
+	return 0;
 }
 
 /** \ingroup desc
- * Get the USB configuration descriptor for a given device.
- * \param dev the device
- * \returns the USB configuration descriptor
+ * Get the USB configuration descriptor for the currently active configuration.
+ * This is a non-blocking function which does not involve any requests being
+ * sent to the device.
+ *
+ * \param dev a device
+ * \returns the USB configuration descriptor which must be freed with
+ * libusb_free_config_descriptor() when done
+ * \returns NULL on error
+ * \see libusb_get_config_descriptor
  */
-API_EXPORTED const struct libusb_config_descriptor *libusb_get_config_descriptor(
+API_EXPORTED
+struct libusb_config_descriptor *libusb_get_active_config_descriptor(
 	libusb_device *dev)
 {
-	return dev->config;
+	struct libusb_config_descriptor *config = malloc(sizeof(*config));
+	unsigned char tmp[8];
+	unsigned char *buf = NULL;
+	int r;
+
+	if (!config)
+		return NULL;
+
+	r = usbi_backend->get_active_config_descriptor(dev, tmp, sizeof(tmp));
+	if (r < 0)
+		goto err;
+
+	usbi_parse_descriptor(tmp, "bbw", &config);
+	buf = malloc(config->wTotalLength);
+	if (!buf)
+		goto err;
+
+	r = usbi_backend->get_active_config_descriptor(dev, buf,
+		config->wTotalLength);
+	if (r < 0)
+		goto err;
+
+	r = parse_configuration(config, buf);
+	if (r < 0) {
+		usbi_err("parse_configuration failed with error %d", r);
+		goto err;
+	} else if (r > 0) {
+		usbi_warn("descriptor data still left");
+	}
+
+	return config;
+
+err:
+	free(config);
+	if (buf)
+		free(buf);
+	return NULL;
+}
+
+/** \ingroup desc
+ * Get the USB configuration descriptor for the currently active configuration.
+ * This is a non-blocking function which does not involve any requests being
+ * sent to the device.
+ *
+ * \param dev a device
+ * \param bConfigurationValue the bConfigurationValue of the configuration
+ * you wish to retreive
+ * \returns the USB configuration descriptor which must be freed with
+ * libusb_free_config_descriptor() when done
+ * \returns NULL on error
+ * \see libusb_get_active_config_descriptor()
+ */
+API_EXPORTED struct libusb_config_descriptor *libusb_get_config_descriptor(
+	libusb_device *dev, uint8_t bConfigurationValue)
+{
+	struct libusb_config_descriptor *config = malloc(sizeof(*config));
+	unsigned char tmp[8];
+	unsigned char *buf = NULL;
+	int r;
+
+	if (!config)
+		return NULL;
+
+	r = usbi_backend->get_config_descriptor(dev, bConfigurationValue, tmp,
+		sizeof(tmp));
+	if (r < 0)
+		goto err;
+
+	usbi_parse_descriptor(tmp, "bbw", &config);
+	buf = malloc(config->wTotalLength);
+	if (!buf)
+		goto err;
+
+	r = usbi_backend->get_config_descriptor(dev, bConfigurationValue, buf,
+		config->wTotalLength);
+	if (r < 0)
+		goto err;
+
+	r = parse_configuration(config, buf);
+	if (r < 0) {
+		usbi_err("parse_configuration failed with error %d", r);
+		goto err;
+	} else if (r > 0) {
+		usbi_warn("descriptor data still left");
+	}
+
+	return config;
+
+err:
+	free(config);
+	if (buf)
+		free(buf);
+	return NULL;
+}
+
+/** \ingroup desc
+ * Free a configuration descriptor obtained from
+ * libusb_get_active_config_descriptor() or libusb_get_config_descriptor().
+ * It is safe to call this function with a NULL config parameter, in which
+ * case the function simply returns.
+ *
+ * \param config the configuration descriptor to free
+ */
+API_EXPORTED void libusb_free_config_descriptor(
+	struct libusb_config_descriptor *config)
+{
+	if (!config)
+		return;
+
+	clear_configuration(config);
+	free(config);
 }
 
 /** \ingroup desc
diff --git a/libusb/libusb.h b/libusb/libusb.h
index 07847bc..62ed0ea 100644
--- a/libusb/libusb.h
+++ b/libusb/libusb.h
@@ -662,12 +662,16 @@
 
 ssize_t libusb_get_device_list(libusb_device ***list);
 void libusb_free_device_list(libusb_device **list, int unref_devices);
-const struct libusb_device_descriptor *libusb_get_device_descriptor(
-	libusb_device *dev);
-const struct libusb_config_descriptor *libusb_get_config_descriptor(
-	libusb_device *dev);
 libusb_device *libusb_ref_device(libusb_device *dev);
 void libusb_unref_device(libusb_device *dev);
+
+int libusb_get_device_descriptor(libusb_device *dev,
+	struct libusb_device_descriptor *desc);
+struct libusb_config_descriptor *libusb_get_active_config_descriptor(
+	libusb_device *dev);
+struct libusb_config_descriptor *libusb_get_config_descriptor(
+	libusb_device *dev, uint8_t config);
+void libusb_free_config_descriptor(struct libusb_config_descriptor *config);
 uint8_t libusb_get_bus_number(libusb_device *dev);
 uint8_t libusb_get_device_address(libusb_device *dev);
 int libusb_get_max_packet_size(libusb_device *dev, unsigned char endpoint);
diff --git a/libusb/libusbi.h b/libusb/libusbi.h
index ca44512..e596b86 100644
--- a/libusb/libusbi.h
+++ b/libusb/libusbi.h
@@ -153,8 +153,6 @@
 
 	struct list_head list;
 	unsigned long session_data;
-	struct libusb_device_descriptor desc;
-	struct libusb_config_descriptor *config;
 	unsigned char os_priv[0];
 };
 
@@ -223,16 +221,13 @@
 
 struct libusb_device *usbi_alloc_device(unsigned long session_id);
 struct libusb_device *usbi_get_device_by_session_id(unsigned long session_id);
-int usbi_discover_device(struct libusb_device *dev);
+int usbi_sanitize_device(struct libusb_device *dev);
 
 void usbi_handle_transfer_completion(struct usbi_transfer *itransfer,
 	enum libusb_transfer_status status);
 void usbi_handle_transfer_cancellation(struct usbi_transfer *transfer);
 
 int usbi_parse_descriptor(unsigned char *source, char *descriptor, void *dest);
-int usbi_parse_configuration(struct libusb_config_descriptor *config,
-		unsigned char *buffer);
-void usbi_clear_configurations(struct libusb_device *dev);
 
 /* polling */
 
@@ -274,12 +269,12 @@
 	int (*open)(struct libusb_device_handle *handle);
 	void (*close)(struct libusb_device_handle *handle);
 
-	int (*begin_discovery)(struct libusb_device *device, void **user_data);
 	int (*get_device_descriptor)(struct libusb_device *device,
-		unsigned char *buffer, void *user_data);
-	int (*get_config_descriptor)(struct libusb_device *device, int index,
-		unsigned char *buffer, size_t len, void *user_data);
-	void (*end_discovery)(struct libusb_device *device, void *user_data);
+		unsigned char *buffer);
+	int (*get_active_config_descriptor)(struct libusb_device *device,
+		unsigned char *buffer, size_t len);
+	int (*get_config_descriptor)(struct libusb_device *device, uint8_t config,
+		unsigned char *buffer, size_t len);
 
 	int (*set_configuration)(struct libusb_device_handle *handle, int config);
 
diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c
index 820100f..6efaaa1 100644
--- a/libusb/os/linux_usbfs.c
+++ b/libusb/os/linux_usbfs.c
@@ -41,9 +41,31 @@
 static const char *usbfs_path = NULL;
 static int have_sysfs;
 
+/* sysfs vs usbfs:
+ * opening a usbfs node causes the device to be resumed, so we attempt to
+ * avoid this during enumeration.
+ *
+ * sysfs allows us to read the kernel's in-memory copies of device descriptors
+ * and so forth, avoiding the need to open the device:
+ *  - The binary "descriptors" file was added in 2.6.23.
+ *  - The "busnum" file was added in 2.6.22
+ *  - The "devnum" file has been present since pre-2.6.18
+ * Hence we check for the existance of a descriptors file to determine whether
+ * sysfs provides all the information we need. We effectively require 2.6.23
+ * in order to avoid waking suspended devices during enumeration.
+ */
+
 struct linux_device_priv {
+	/* FIXME remove this, infer from dev->busnum etc */
 	char *nodepath;
-	char sysfs_dir[SYSFS_DIR_LENGTH];
+
+	union {
+		char sysfs_dir[SYSFS_DIR_LENGTH];
+		struct {
+			unsigned char *dev_descriptor;
+			unsigned char *config_descriptor;
+		};
+	};
 };
 
 struct linux_device_handle_priv {
@@ -149,71 +171,167 @@
 	return 0;
 }
 
-struct discovery_data {
-	int fd;
-};
-
-static int op_begin_discovery(struct libusb_device *dev, void **user_data)
+static int usbfs_get_device_descriptor(struct libusb_device *dev,
+	unsigned char *buffer)
 {
 	struct linux_device_priv *priv = __device_priv(dev);
-	struct discovery_data *ddata = malloc(sizeof(*ddata));
-	if (!ddata)
-		return LIBUSB_ERROR_NO_MEM;
 
-	if (have_sysfs) {
-		char filename[PATH_MAX + 1];
-		snprintf(filename, PATH_MAX, "%s/%s/descriptors",
-			SYSFS_DEVICE_PATH, priv->sysfs_dir);
-		ddata->fd = open(filename, O_RDONLY);
-	} else {
-		ddata->fd = open(priv->nodepath, O_RDONLY);
-	}
-
-	if (ddata->fd < 0) {
-		usbi_dbg("open '%s' failed, ret=%d errno=%d", priv->nodepath,
-			ddata->fd, errno);
-		free(ddata);
-		return LIBUSB_ERROR_IO;
-	}
-
-	*user_data = ddata;
+	/* return cached copy */
+	memcpy(buffer, priv->dev_descriptor, DEVICE_DESC_LENGTH);
 	return 0;
 }
 
-static int op_get_device_descriptor(struct libusb_device *device,
-	unsigned char *buffer, void *user_data)
+static int open_sysfs_descriptors(struct libusb_device *dev)
 {
-	struct discovery_data *ddata = user_data;
-	int r = read(ddata->fd, buffer, DEVICE_DESC_LENGTH);
+	struct linux_device_priv *priv = __device_priv(dev);
+	char filename[PATH_MAX + 1];
+	int fd;
+
+	snprintf(filename, PATH_MAX, "%s/%s/descriptors", SYSFS_DEVICE_PATH,
+		priv->sysfs_dir);
+	fd = open(filename, O_RDONLY);
+	if (fd < 0) {
+		usbi_err("open '%s' failed, ret=%d errno=%d", filename, fd, errno);
+		return LIBUSB_ERROR_IO;
+	}
+
+	return fd;
+}
+
+static int sysfs_get_device_descriptor(struct libusb_device *dev,
+	unsigned char *buffer)
+{
+	int fd;
+	ssize_t r;
+
+	/* sysfs provides access to an in-memory copy of the device descriptor,
+	 * so we use that rather than keeping our own copy */
+
+	fd = open_sysfs_descriptors(dev);
+	if (fd < 0)
+		return fd;
+
+	r = read(fd, buffer, DEVICE_DESC_LENGTH);;
+	close(fd);
 	if (r < 0) {
-		usbi_err("read failed ret=%d errno=%d", r, errno);
+		usbi_err("read failed, ret=%d errno=%d", fd, errno);
 		return LIBUSB_ERROR_IO;
 	} else if (r < DEVICE_DESC_LENGTH) {
-		usbi_err("short descriptor read %d/%d", r, DEVICE_DESC_LENGTH);
+		usbi_err("short read %d/%d", r, DEVICE_DESC_LENGTH);
 		return LIBUSB_ERROR_IO;
 	}
 
 	return 0;
 }
 
-static int op_get_config_descriptor(struct libusb_device *device,
-	int config_index, unsigned char *buffer, size_t len, void *user_data)
+static int op_get_device_descriptor(struct libusb_device *dev,
+	unsigned char *buffer)
 {
-	struct discovery_data *ddata = user_data;
-	off_t off;
+	if (have_sysfs)
+		return sysfs_get_device_descriptor(dev, buffer);
+	else
+		return usbfs_get_device_descriptor(dev, buffer);
+}
+
+static int usbfs_get_active_config_descriptor(struct libusb_device *dev,
+	unsigned char *buffer, size_t len)
+{
+	struct linux_device_priv *priv = __device_priv(dev);
+	/* retrieve cached copy */
+	memcpy(buffer, priv->config_descriptor, len);
+	return 0;
+}
+
+static int sysfs_get_active_config_descriptor(struct libusb_device *dev,
+	unsigned char *buffer, size_t len)
+{
+	int fd;
 	ssize_t r;
-	int fd = ddata->fd;
+	off_t off;
+
+	/* sysfs provides access to an in-memory copy of the device descriptor,
+	 * so we use that rather than keeping our own copy */
+
+	fd = open_sysfs_descriptors(dev);
+	if (fd < 0)
+		return fd;
 
 	off = lseek(fd, DEVICE_DESC_LENGTH, SEEK_SET);
 	if (off < 0) {
-		usbi_err("seek failed ret=%d errno=%d", off, errno);
+		usbi_err("seek failed, ret=%d errno=%d", off, errno);
+		close(fd);
+		return LIBUSB_ERROR_IO;
+	}
+
+	r = read(fd, buffer, len);
+	close(fd);
+	if (r < 0) {
+		usbi_err("read failed, ret=%d errno=%d", fd, errno);
+		return LIBUSB_ERROR_IO;
+	} else if (r < len) {
+		usbi_err("short read %d/%d", r, len);
+		return LIBUSB_ERROR_IO;
+	}
+
+	return 0;
+}
+
+static int op_get_active_config_descriptor(struct libusb_device *dev,
+	unsigned char *buffer, size_t len)
+{
+	if (have_sysfs)
+		return sysfs_get_active_config_descriptor(dev, buffer, len);
+	else
+		return usbfs_get_active_config_descriptor(dev, buffer, len);
+}
+
+
+/* takes a usbfs fd, attempts to find the requested config and copy a certain
+ * amount of it into an output buffer. a bConfigurationValue of -1 indicates
+ * that the first config should be retreived. */
+static int get_config_descriptor(int fd, int bConfigurationValue,
+	unsigned char *buffer, size_t len)
+{
+	unsigned char tmp[8];
+	uint8_t num_configurations;
+	off_t off;
+	ssize_t r;
+
+	if (bConfigurationValue == -1) {
+		/* read first configuration */
+		off = lseek(fd, DEVICE_DESC_LENGTH, SEEK_SET);
+		if (off < 0) {
+			usbi_err("seek failed, ret=%d errno=%d", off, errno);
+			return LIBUSB_ERROR_IO;
+		}
+		r = read(fd, buffer, len);
+		if (r < 0) {
+			usbi_err("read failed ret=%d errno=%d", r, errno);
+			return LIBUSB_ERROR_IO;
+		} else if (r < len) {
+			usbi_err("short output read %d/%d", r, len);
+			return LIBUSB_ERROR_IO;
+		}
+		return 0;
+	}
+
+	/* seek to last byte of device descriptor to determine number of
+	 * configurations */
+	off = lseek(fd, DEVICE_DESC_LENGTH - 1, SEEK_SET);
+	if (off < 0) {
+		usbi_err("seek failed, ret=%d errno=%d", off, errno);
+		return LIBUSB_ERROR_IO;
+	}
+
+	r = read(fd, &num_configurations, 1);
+	if (r < 0) {
+		usbi_err("read num_configurations failed, ret=%d errno=%d", off, errno);
 		return LIBUSB_ERROR_IO;
 	}
 
 	/* might need to skip some configuration descriptors to reach the
-	 * requested index */
-	while (config_index > 0) {
-		unsigned char tmp[8];
+	 * requested configuration */
+	while (num_configurations) {
 		struct libusb_config_descriptor config;
 
 		/* read first 8 bytes of descriptor */
@@ -225,8 +343,10 @@
 			usbi_err("short descriptor read %d/%d", r, sizeof(tmp));
 			return LIBUSB_ERROR_IO;
 		}
-	
-		usbi_parse_descriptor(buffer, "bbw", &config);
+
+		usbi_parse_descriptor(tmp, "bbwbb", &config);
+		if (config.bConfigurationValue == bConfigurationValue)
+			break;
 
 		/* seek forward to end of config */
 		off = lseek(fd, config.wTotalLength - sizeof(tmp), SEEK_CUR);
@@ -235,27 +355,80 @@
 			return LIBUSB_ERROR_IO;
 		}
 
-		config_index--;
+		num_configurations--;
 	}
 
-	/* read the actual config */
-	r = read(fd, buffer, len);
+	if (num_configurations == 0)
+		return LIBUSB_ERROR_NOT_FOUND;
+
+	/* copy config-so-far */
+	memcpy(buffer, tmp, sizeof(tmp));
+
+	/* read the rest of the descriptor */
+	r = read(fd, buffer + sizeof(tmp), len - sizeof(tmp));
 	if (r < 0) {
 		usbi_err("read failed ret=%d errno=%d", r, errno);
 		return LIBUSB_ERROR_IO;
-	} else if (r < len) {
-		usbi_err("short descriptor read %d/%d", r, len);
+	} else if (r < (len - sizeof(tmp))) {
+		usbi_err("short output read %d/%d", r, len);
 		return LIBUSB_ERROR_IO;
 	}
 
 	return 0;
 }
 
-static void op_end_discovery(struct libusb_device *device, void *user_data)
+static int op_get_config_descriptor(struct libusb_device *dev, uint8_t config,
+	unsigned char *buffer, size_t len)
 {
-	struct discovery_data *ddata = user_data;
-	close(ddata->fd);
-	free(ddata);
+	struct linux_device_priv *priv = __device_priv(dev);
+	int fd;
+	int r;
+
+	/* always read from usbfs: sysfs only has the active descriptor
+	 * this will involve waking the device up, but oh well! */
+
+	fd = open(priv->nodepath, O_RDONLY);
+	if (fd < 0) {
+		usbi_err("open '%s' failed, ret=%d errno=%d",
+			priv->nodepath, fd, errno);
+		return LIBUSB_ERROR_IO;
+	}
+
+	r = get_config_descriptor(fd, config, buffer, len);
+	close(fd);
+	return r;
+}
+
+static int cache_active_config(struct libusb_device *dev, int fd,
+	int active_config)
+{
+	struct linux_device_priv *priv = __device_priv(dev);
+	struct libusb_config_descriptor config;
+	unsigned char tmp[8];
+	unsigned char *buf;
+	int r;
+
+	r = get_config_descriptor(fd, active_config, tmp, sizeof(tmp));
+	if (r < 0) {
+		usbi_err("first read error %d", r);
+		return r;
+	}
+
+	usbi_parse_descriptor(tmp, "bbw", &config);
+	buf = malloc(config.wTotalLength);
+	if (!buf)
+		return LIBUSB_ERROR_NO_MEM;
+
+	r = get_config_descriptor(fd, active_config, buf, config.wTotalLength);
+	if (r < 0) {
+		free(buf);
+		return r;
+	}
+
+	if (priv->config_descriptor)
+		free(priv->config_descriptor);
+	priv->config_descriptor = buf;
+	return 0;
 }
 
 static int initialize_device(struct libusb_device *dev, uint8_t busnum,
@@ -271,6 +444,82 @@
 	snprintf(path, PATH_MAX, "%s/%03d/%03d", usbfs_path, busnum, devaddr);
 	usbi_dbg("%s", path);
 
+	if (!have_sysfs) {
+		/* cache device descriptor in memory so that we can retrieve it later
+		 * without waking the device up (op_get_device_descriptor) */
+		unsigned char *dev_buf = malloc(DEVICE_DESC_LENGTH);
+		int fd;
+		ssize_t r;
+		int tmp;
+		int active_config = 0;
+
+		struct usbfs_ctrltransfer ctrl = {
+			.bmRequestType = LIBUSB_ENDPOINT_IN,
+			.bRequest = LIBUSB_REQUEST_GET_CONFIGURATION,
+			.wValue = 0,
+			.wIndex = 0,
+			.wLength = 1,
+			.timeout = 1000,
+			.data = &active_config
+		};
+		
+		priv->dev_descriptor = NULL;
+		priv->config_descriptor = NULL;
+		if (!dev_buf)
+			return LIBUSB_ERROR_NO_MEM;
+
+		fd = open(path, O_RDWR);
+		if (fd < 0 && errno == EACCES) {
+			usbi_dbg("sysfs unavailable and read-only access to usbfs --> "
+				"cannot determine which configuration is active");
+			fd = open(path, O_RDONLY);
+			/* if we only have read-only access to the device, we cannot
+			 * send a control message to determine the active config. just
+			 * assume the first one is active. */
+			active_config = -1;
+		}
+
+		if (fd < 0) {
+			usbi_err("open failed, ret=%d errno=%d", fd, errno);
+			free(dev_buf);
+			return LIBUSB_ERROR_IO;
+		}
+
+		r = read(fd, dev_buf, DEVICE_DESC_LENGTH);
+		if (r < 0) {
+			usbi_err("read descriptor failed ret=%d errno=%d", fd, errno);
+			free(dev_buf);
+			close(fd);
+			return LIBUSB_ERROR_IO;
+		} else if (r < DEVICE_DESC_LENGTH) {
+			usbi_err("short descriptor read (%d)", r);
+			free(dev_buf);
+			close(fd);
+			return LIBUSB_ERROR_IO;
+		}
+
+		if (active_config == 0) {
+			/* determine active configuration and cache the descriptor */
+			tmp = ioctl(fd, IOCTL_USBFS_CONTROL, &ctrl);
+			if (tmp < 0) {
+				usbi_err("get_configuration failed ret=%d errno=%d", tmp, errno);
+				free(dev_buf);
+				close(fd);
+				return LIBUSB_ERROR_IO;
+			}
+		}
+
+		r = cache_active_config(dev, fd, active_config);
+		if (r < 0) {
+			free(dev_buf);
+			close(fd);
+			return r;
+		}
+
+		priv->dev_descriptor = dev_buf;
+		close(fd);
+	}
+
 	if (sysfs_dir)
 		strncpy(priv->sysfs_dir, sysfs_dir, SYSFS_DIR_LENGTH);
 
@@ -311,7 +560,7 @@
 		r = initialize_device(dev, busnum, devaddr, sysfs_dir);
 		if (r < 0)
 			goto out;
-		r = usbi_discover_device(dev);
+		r = usbi_sanitize_device(dev);
 		if (r < 0)
 			goto out;
 	}
@@ -542,6 +791,14 @@
 		usbi_err("failed, error %d errno %d", r, errno);
 		return LIBUSB_ERROR_OTHER;
 	}
+
+	if (!have_sysfs) {
+		/* update our cached active config descriptor */
+		r = cache_active_config(handle->dev, fd, config);
+		if (r < 0)
+			usbi_warn("failed to update cached config descriptor, error %d", r);
+	}
+
 	return 0;
 }
 
@@ -672,9 +929,16 @@
 
 static void op_destroy_device(struct libusb_device *dev)
 {
-	unsigned char *nodepath = __device_priv(dev)->nodepath;
-	if (nodepath)
-		free(nodepath);
+	struct linux_device_priv *priv = __device_priv(dev);
+	if (priv->nodepath)
+		free(priv->nodepath);
+
+	if (!have_sysfs) {
+		if (priv->dev_descriptor)
+			free(priv->dev_descriptor);
+		if (priv->config_descriptor)
+			free(priv->config_descriptor);
+	}
 }
 
 static void free_iso_urbs(struct linux_transfer_priv *tpriv)
@@ -1321,10 +1585,9 @@
 	.init = op_init,
 	.exit = NULL,
 	.get_device_list = op_get_device_list,
-	.begin_discovery = op_begin_discovery,
 	.get_device_descriptor = op_get_device_descriptor,
+	.get_active_config_descriptor = op_get_active_config_descriptor,
 	.get_config_descriptor = op_get_config_descriptor,
-	.end_discovery = op_end_discovery,
 
 	.open = op_open,
 	.close = op_close,