Beginnings of cross-platform abstraction

This also includes a libusb_get_pollfds API change
diff --git a/TODO b/TODO
index 8506952..de81ef9 100644
--- a/TODO
+++ b/TODO
@@ -4,7 +4,6 @@
 API docs
 isochronous endpoint I/O
 thread safety
-abstraction for cross-platform-ness
 error codes
 fixme review
 review functionality missing over 0.1
diff --git a/configure.ac b/configure.ac
index 58b4aa2..e8ae187 100644
--- a/configure.ac
+++ b/configure.ac
@@ -10,6 +10,20 @@
 AM_PROG_CC_C_O
 AC_DEFINE([_GNU_SOURCE], [], [Use GNU extensions])
 
+AC_MSG_CHECKING([operating system])
+case $host in
+*-linux*)
+	AC_DEFINE(OS_LINUX, [], [Linux backend])
+	AC_SUBST(OS_LINUX)
+	AC_MSG_RESULT([Linux])
+	backend="linux"
+	;;
+*)
+	AC_MSG_ERROR([unsupported operating system])
+esac
+
+AM_CONDITIONAL([OS_LINUX], [test "x$backend" == "xlinux"])
+
 # Library versioning
 lt_major="0"
 lt_revision="0"
diff --git a/examples/dpfp.c b/examples/dpfp.c
index 70944f8..47d9fc6 100644
--- a/examples/dpfp.c
+++ b/examples/dpfp.c
@@ -270,7 +270,7 @@
 		printf("unrecognised state %d\n", state);
 	}
 	if (r < 0) {
-		fprintf(stderr, "error detected changing state");
+		fprintf(stderr, "error detected changing state\n");
 		return r;
 	}
 
diff --git a/libusb/Makefile.am b/libusb/Makefile.am
index 462bd2f..fb5d033 100644
--- a/libusb/Makefile.am
+++ b/libusb/Makefile.am
@@ -1,7 +1,15 @@
 lib_LTLIBRARIES = libusb-1.0.la
 
+LINUX_USBFS_SRC = os/linux_usbfs.h os/linux_usbfs.c
+
+EXTRA_DIST = $(LINUX_USBFS_SRC)
+
+if OS_LINUX
+OS_SRC = $(LINUX_USBFS_SRC)
+endif
+
 libusb_1_0_la_CFLAGS = -fvisibility=hidden $(AM_CFLAGS)
-libusb_1_0_la_SOURCES = libusbi.h usbfs.h core.c descriptor.c io.c sync.c
+libusb_1_0_la_SOURCES = libusbi.h core.c descriptor.c io.c sync.c $(OS_SRC)
 libusb_1_0_la_LIBADD = -lrt
 
 hdrdir = $(includedir)/libusb-1.0
diff --git a/libusb/core.c b/libusb/core.c
index 8d7abcf..68d32ce 100644
--- a/libusb/core.c
+++ b/libusb/core.c
@@ -20,66 +20,24 @@
 
 #include <config.h>
 
-#include <dirent.h>
 #include <errno.h>
-#include <fcntl.h>
-#include <features.h>
 #include <poll.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
 
 #include "libusb.h"
 #include "libusbi.h"
 
-struct list_head usb_devs;
-struct list_head open_devs;
-static const char *usbfs_path = NULL;
+#ifdef OS_LINUX
+const struct usbi_os_backend * const usbi_backend = &linux_usbfs_backend;
+#else
+#error "Unsupported OS"
+#endif
 
-static int check_usb_vfs(const char *dirname)
-{
-	DIR *dir;
-	struct dirent *entry;
-	int found = 0;
-
-	dir = opendir(dirname);
-	if (!dir)
-		return 0;
-
-	while ((entry = readdir(dir)) != NULL) {
-		if (entry->d_name[0] == '.')
-			continue;
-
-		/* We assume if we find any files that it must be the right place */
-		found = 1;
-		break;
-	}
-
-	closedir(dir);
-	return found;
-}
-
-static const char *find_usbfs_path(void)
-{
-	const char *path = "/dev/bus/usb";
-	const char *ret = NULL;
-
-	if (check_usb_vfs(path)) {
-		ret = path;
-	} else {
-		path = "/proc/bus/usb";
-		if (check_usb_vfs(path))
-			ret = path;
-	}
-
-	usbi_dbg("found usbfs at %s", ret);
-	return ret;
-}
+static struct list_head usb_devs;
+struct list_head usbi_open_devs;
 
 /* we traverse usbfs without knowing how many devices we are going to find.
  * so we create this discovered_devs model which is similar to a linked-list
@@ -87,11 +45,6 @@
  * eliminating the need for a list node in the libusb_device structure
  * itself. */
 #define DISCOVERED_DEVICES_SIZE_STEP 8
-struct discovered_devs {
-	size_t len;
-	size_t capacity;
-	struct libusb_device *devices[0];
-};
 
 static struct discovered_devs *discovered_devs_alloc(void)
 {
@@ -107,7 +60,7 @@
 
 /* append a device to the discovered devices collection. may realloc itself,
  * returning new discdevs. returns NULL on realloc failure. */
-static struct discovered_devs *discovered_devs_append(
+struct discovered_devs *discovered_devs_append(
 	struct discovered_devs *discdevs, struct libusb_device *dev)
 {
 	size_t len = discdevs->len;
@@ -144,115 +97,21 @@
 	free(discdevs);
 }
 
-static struct libusb_device *device_new(uint8_t busnum, uint8_t devaddr)
+struct libusb_device *usbi_alloc_device(unsigned long session_id)
 {
-	char path[PATH_MAX + 1];
-	unsigned char raw_desc[DEVICE_DESC_LENGTH];
-	struct libusb_device *dev = malloc(sizeof(*dev));
-	int fd = 0;
-	int i;
-	int r;
-	int tmp;
-
+	size_t priv_size = usbi_backend->device_priv_size;
+	struct libusb_device *dev = malloc(sizeof(*dev) + priv_size);
 	if (!dev)
 		return NULL;
 
 	dev->refcnt = 1;
-	dev->nodepath = NULL;
-	dev->config = NULL;
-
-	snprintf(path, PATH_MAX, "%s/%03d/%03d", usbfs_path, busnum, devaddr);
-	usbi_dbg("%s", path);
-	fd = open(path, O_RDWR);
-	if (!fd) {
-		usbi_dbg("open '%s' failed, ret=%d errno=%d", path, fd, errno);
-		/* FIXME this might not be an error if the file has gone away due
-		 * to unplugging */
-		goto err;
-	}
-
-	r = read(fd, raw_desc, DEVICE_DESC_LENGTH);
-	if (r < 0) {
-		usbi_err("read failed ret=%d errno=%d", r, errno);
-		goto err;
-	}
-	/* FIXME: short read handling? */
-
-	usbi_parse_descriptor(raw_desc, "bbWbbbbWWWbbbb", &dev->desc);
-
-	/* Now try to fetch the rest of the descriptors */
-	if (dev->desc.bNumConfigurations > USB_MAXCONFIG) {
-		usbi_err("too many configurations");
-		goto err;
-	}
-
-	if (dev->desc.bNumConfigurations < 1) {
-		usbi_dbg("no configurations?");
-		goto err;
-	}
-
-	tmp = dev->desc.bNumConfigurations * sizeof(struct libusb_config_descriptor);
-	dev->config = malloc(tmp);
-	if (!dev->config)
-		goto err;
-
-	memset(dev->config, 0, tmp);
-	for (i = 0; i < dev->desc.bNumConfigurations; i++) {
-		unsigned char buffer[8], *bigbuffer;
-		struct libusb_config_descriptor config;
-
-		/* Get the first 8 bytes to figure out what the total length is */
-		r = read(fd, buffer, sizeof(buffer));
-		if (r < sizeof(buffer)) {
-			usbi_err("short descriptor read (%d/%d)", r, sizeof(buffer));
-			goto err;
-		}
-
-		usbi_parse_descriptor(buffer, "bbw", &config);
-
-		bigbuffer = malloc(config.wTotalLength);
-		if (!bigbuffer)
-			goto err;
-
-		/* Read the rest of the config descriptor */
-		memcpy(bigbuffer, buffer, sizeof(buffer));
-
-		tmp = config.wTotalLength - 8;
-		r = read(fd, bigbuffer + 8, tmp);
-		if (r < tmp) {
-			usbi_err("short descriptor read (%d/%d)", r, tmp);
-			free(bigbuffer);
-			goto err;
-		}
-
-		r = usbi_parse_configuration(&dev->config[i], bigbuffer);
-		if (r > 0)
-			usbi_warn("descriptor data still left\n");
-		free(bigbuffer);
-	}
-
-	dev->nodepath = strdup(path);
-	if (!dev->nodepath)
-		goto err;
-
-	dev->session_data = busnum << 8 | devaddr;
+	dev->session_data = session_id;
 	list_add(&dev->list, &usb_devs);
-	close(fd);
+	memset(&dev->os_priv, 0, priv_size);
 	return dev;
-
-err:
-	if (fd)
-		close(fd);
-	if (dev->config)
-		free(dev->config);
-	if (dev->nodepath)
-		free(dev->nodepath);
-	if (dev)
-		free(dev);
-	return NULL;
 }
 
-static struct libusb_device *get_device_by_session_id(unsigned long session_id)
+struct libusb_device *usbi_get_device_by_session_id(unsigned long session_id)
 {
 	struct libusb_device *dev;
 
@@ -263,103 +122,9 @@
 	return NULL;
 }
 
-/* open a device file, set up the libusb_device structure for it, and add it to
- * discdevs. on failure (non-zero return) the pre-existing discdevs should
- * be destroyed (and devices freed). on success, the new discdevs pointer
- * should be used it may have been moved. */
-static int scan_device(struct discovered_devs **_discdevs, uint8_t busnum,
-	uint8_t devaddr)
-{
-	struct discovered_devs *discdevs;
-	unsigned long session_id;
-	struct libusb_device *dev;
-	int need_unref = 0;
-	int r = 0;
-
-	/* FIXME: session ID is not guaranteed unique as addresses can wrap and
-	 * will be reused. instead we should add a simple sysfs attribute with
-	 * a session ID. */
-	session_id = busnum << 8 | devaddr;
-	usbi_dbg("busnum %d devaddr %d session_id %ld", busnum, devaddr,
-		session_id);
-
-	dev = get_device_by_session_id(session_id);
-	if (dev) {
-		usbi_dbg("using existing device for %d/%d (session %ld)",
-			busnum, devaddr, session_id);
-	} else {
-		usbi_dbg("allocating new device for %d/%d (session %ld)",
-			busnum, devaddr, session_id);
-		dev = device_new(busnum, devaddr);
-		if (!dev) {
-			r = -EIO;
-			goto out;
-		}
-		need_unref = 1;
-	}
-
-	discdevs = discovered_devs_append(*_discdevs, dev);
-	if (!discdevs)
-		r = -ENOMEM;
-	else
-		*_discdevs = discdevs;
-
-out:
-	if (need_unref)
-		libusb_device_unref(dev);
-	return r;
-}
-
-/* open a bus directory and adds all discovered devices to discdevs. on
- * failure (non-zero return) the pre-existing discdevs should be destroyed
- * (and devices freed). on success, the new discdevs pointer should be used
- * as it may have been moved. */
-static int scan_busdir(struct discovered_devs **_discdevs, uint8_t busnum)
-{
-	DIR *dir;
-	char dirpath[PATH_MAX + 1];
-	struct dirent *entry;
-	struct discovered_devs *discdevs = *_discdevs;
-	int r = 0;
-
-	snprintf(dirpath, PATH_MAX, "%s/%03d", usbfs_path, busnum);
-	usbi_dbg("%s", dirpath);
-	dir = opendir(dirpath);
-	if (!dir) {
-		usbi_err("opendir '%s' failed, errno=%d", dirpath, errno);
-		/* FIXME: should handle valid race conditions like hub unplugged
-		 * during directory iteration - this is not an error */
-		return -1;
-	}
-
-	while ((entry = readdir(dir))) {
-		int devaddr;
-
-		if (entry->d_name[0] == '.')
-			continue;
-
-		devaddr = atoi(entry->d_name);
-		if (devaddr == 0) {
-			usbi_dbg("unknown dir entry %s", entry->d_name);
-			continue;
-		}
-
-		r = scan_device(&discdevs, busnum, (uint8_t) devaddr);
-		if (r < 0)
-			goto out;
-	}
-
-	*_discdevs = discdevs;
-out:
-	closedir(dir);
-	return r;
-}
-
 API_EXPORTED int libusb_get_device_list(struct libusb_device ***list)
 {
-	DIR *buses;
 	struct discovered_devs *discdevs = discovered_devs_alloc();
-	struct dirent *entry;
 	struct libusb_device **ret;
 	int r = 0;
 	size_t i;
@@ -369,30 +134,9 @@
 	if (!discdevs)
 		return -ENOMEM;
 
-	buses = opendir(usbfs_path);
-	if (!buses) {
-		usbi_err("opendir buses failed errno=%d", errno);
-		return -1;
-	}
-
-	while ((entry = readdir(buses))) {
-		struct discovered_devs *discdevs_new = discdevs;
-		int busnum;
-
-		if (entry->d_name[0] == '.')
-			continue;
-
-		busnum = atoi(entry->d_name);
-		if (busnum == 0) {
-			usbi_dbg("unknown dir entry %s", entry->d_name);
-			continue;
-		}
-
-		r = scan_busdir(&discdevs_new, busnum);
-		if (r < 0)
-			goto out;
-		discdevs = discdevs_new;
-	}
+	r = usbi_backend->get_device_list(discdevs);
+	if (r < 0)
+		goto out;
 
 	/* convert discovered_devs into a list */
 	len = discdevs->len;
@@ -411,7 +155,6 @@
 
 out:
 	discovered_devs_free(discdevs);
-	closedir(buses);
 	return r;
 }
 
@@ -445,9 +188,13 @@
 	if (--dev->refcnt == 0) {
 		usbi_dbg("destroy device %04x:%04x", dev->desc.idVendor,
 			dev->desc.idProduct);
+
+		if (usbi_backend->destroy_device)
+			usbi_backend->destroy_device(dev);
+
 		list_del(&dev->list);
-		free(dev->config);
-		free(dev->nodepath);
+		if (dev->config)
+			free(dev->config);
 		free(dev);
 	}
 }
@@ -466,27 +213,26 @@
 
 API_EXPORTED struct libusb_device_handle *libusb_open(struct libusb_device *dev)
 {
-	struct libusb_device_handle *devh;
-	int fd;
+	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);
 
-	fd = open(dev->nodepath, O_RDWR);
-	if (!fd) {
-		usbi_err("open failed, code %d errno %d", fd, errno);
+	handle = malloc(sizeof(*handle) + priv_size);
+	if (!handle)
+		return NULL;
+
+	handle->dev = libusb_device_ref(dev);
+	memset(&handle->os_priv, 0, priv_size);
+	r = usbi_backend->open(handle);
+	if (r < 0) {
+		libusb_device_unref(dev);
+		free(handle);
 		return NULL;
 	}
 
-	devh = malloc(sizeof(*devh));
-	if (!devh) {
-		close(fd);
-		return NULL;
-	}
-
-	devh->fd = fd;
-	devh->dev = libusb_device_ref(dev);
-	list_add(&devh->list, &open_devs);
-	usbi_add_pollfd(fd, POLLOUT);
-	return devh;
+	list_add(&handle->list, &usbi_open_devs);
+	return handle;
 }
 
 /* convenience function for finding a device with a particular vendor/product
@@ -499,7 +245,7 @@
 	struct libusb_device **devs;
 	struct libusb_device *found = NULL;
 	struct libusb_device *dev;
-	struct libusb_device_handle *devh;
+	struct libusb_device_handle *handle = NULL;
 	size_t i = 0;
 
 	if (libusb_get_device_list(&devs) < 0)
@@ -515,17 +261,16 @@
 	}
 
 	if (found)
-		devh = libusb_open(found);
+		handle = libusb_open(found);
 
 	libusb_free_device_list(devs, 1);
-	return devh;
+	return handle;
 }
 
-static void do_close(struct libusb_device_handle *devh)
+static void do_close(struct libusb_device_handle *dev_handle)
 {
-	usbi_remove_pollfd(devh->fd);
-	close(devh->fd);
-	libusb_device_unref(devh->dev);
+	usbi_backend->close(dev_handle);
+	libusb_device_unref(dev_handle->dev);
 }
 
 API_EXPORTED void libusb_close(struct libusb_device_handle *dev_handle)
@@ -548,38 +293,29 @@
 API_EXPORTED int libusb_claim_interface(struct libusb_device_handle *dev,
 	int iface)
 {
-	int r;
 	usbi_dbg("interface %d", iface);
-	
-	r = ioctl(dev->fd, IOCTL_USB_CLAIMINTF, &iface);
-	if (r < 0)
-		usbi_err("claim interface failed, error %d", r);
-	return r;
+	return usbi_backend->claim_interface(dev, iface);
 }
 
 API_EXPORTED int libusb_release_interface(struct libusb_device_handle *dev,
 	int iface)
 {
-	int r;
 	usbi_dbg("interface %d", iface);
-
-	r = ioctl(dev->fd, IOCTL_USB_RELEASEINTF, &iface);
-	if (r < 0)
-		usbi_err("release interface failed, error %d", r);
-	return r;
+	return usbi_backend->release_interface(dev, iface);
 }
 
 API_EXPORTED int libusb_init(void)
 {
 	usbi_dbg("");
-	usbfs_path = find_usbfs_path();
-	if (!usbfs_path) {
-		usbi_err("could not find usbfs");
-		return -ENODEV;
+
+	if (usbi_backend->init) {
+		int r = usbi_backend->init();
+		if (r < 0)
+			return r;
 	}
 
 	list_init(&usb_devs);
-	list_init(&open_devs);
+	list_init(&usbi_open_devs);
 	usbi_io_init();
 	return 0;
 }
@@ -588,37 +324,14 @@
 {
 	struct libusb_device_handle *devh;
 	usbi_dbg("");
-	if (!list_empty(&open_devs)) {
+	if (!list_empty(&usbi_open_devs)) {
 		usbi_dbg("naughty app left some devices open!\n");
-		list_for_each_entry(devh, &open_devs, list)
+		list_for_each_entry(devh, &usbi_open_devs, list)
 			do_close(devh);
+		/* FIXME where do the open handles get freed? */
 	}
-}
-
-API_EXPORTED size_t libusb_get_pollfds(struct libusb_pollfd **pollfds)
-{
-	struct libusb_device_handle *devh;
-	struct libusb_pollfd *ret;
-	size_t cnt = 0;
-	size_t i = 0;
-
-	/* count number of open devices */
-	list_for_each_entry(devh, &open_devs, list)
-		cnt++;
-
-	/* create array */
-	ret = calloc(cnt, sizeof(struct libusb_pollfd));
-	if (!ret)
-		return -ENOMEM;
-
-	/* add fds */
-	list_for_each_entry(devh, &open_devs, list) {
-		ret[i++].fd = devh->fd;
-		ret[i].events = POLLOUT;
-	}
-	
-	*pollfds = ret;
-	return cnt;
+	if (usbi_backend->exit)
+		usbi_backend->exit();
 }
 
 void usbi_log(enum usbi_log_level level, const char *function,
diff --git a/libusb/io.c b/libusb/io.c
index 118c8b0..36f5ceb 100644
--- a/libusb/io.c
+++ b/libusb/io.c
@@ -20,6 +20,7 @@
 
 #include <config.h>
 #include <errno.h>
+#include <poll.h>
 #include <signal.h>
 #include <stdint.h>
 #include <stdlib.h>
@@ -39,6 +40,9 @@
  * are always placed at the very end. */
 static struct list_head flying_transfers;
 
+/* list of poll fd's */
+static struct list_head pollfds;
+
 /* user callbacks for pollfd changes */
 static libusb_pollfd_added_cb fd_added_cb = NULL;
 static libusb_pollfd_removed_cb fd_removed_cb = NULL;
@@ -46,6 +50,7 @@
 void usbi_io_init()
 {
 	list_init(&flying_transfers);
+	list_init(&pollfds);
 	fd_added_cb = NULL;
 	fd_removed_cb = NULL;
 }
@@ -113,43 +118,9 @@
 
 static int submit_transfer(struct usbi_transfer *itransfer)
 {
-	int r;
-	struct usb_urb *urb = &itransfer->urb;
-	struct libusb_transfer *transfer = &itransfer->pub;
-	int to_be_transferred = transfer->length - itransfer->transferred;
-
-	switch (transfer->endpoint_type) {
-	case LIBUSB_ENDPOINT_TYPE_CONTROL:
-		urb->type = USB_URB_TYPE_CONTROL;
-		break;
-	case LIBUSB_ENDPOINT_TYPE_BULK:
-		urb->type = USB_URB_TYPE_BULK;
-		break;
-	case LIBUSB_ENDPOINT_TYPE_INTERRUPT:
-		urb->type = USB_URB_TYPE_INTERRUPT;
-		break;
-	default:
-		usbi_err("unknown endpoint type %d", transfer->endpoint_type);
-		return -EINVAL;
-	}
-
-	urb->endpoint = transfer->endpoint;
-	urb->buffer = transfer->buffer + itransfer->transferred;
-	urb->buffer_length = MIN(to_be_transferred, MAX_URB_BUFFER_LENGTH);
-
-	/* FIXME: for requests that we have to split into multiple URBs, we should
-	 * submit all the URBs instantly: submit, submit, submit, reap, reap, reap
-	 * rather than: submit, reap, submit, reap, submit, reap
-	 * this will improve performance and fix bugs concerning behaviour when
-	 * the user submits two similar multiple-urb requests */
-	usbi_dbg("transferring %d from %d bytes", urb->buffer_length,
-		to_be_transferred);
-
-	r = ioctl(transfer->dev_handle->fd, IOCTL_USB_SUBMITURB, urb);
-	if (r < 0) {
-		usbi_err("submiturb failed error %d errno=%d", r, errno);
+	int r = usbi_backend->submit_transfer(itransfer);
+	if (r < 0)
 		return r;
-	}
 
 	add_to_flying_list(itransfer);
 	return 0;
@@ -157,7 +128,7 @@
 
 API_EXPORTED size_t libusb_get_transfer_alloc_size(void)
 {
-	return sizeof(struct usbi_transfer);
+	return sizeof(struct usbi_transfer) + usbi_backend->transfer_priv_size;
 }
 
 void __init_transfer(struct usbi_transfer *transfer)
@@ -172,7 +143,8 @@
 
 API_EXPORTED struct libusb_transfer *libusb_alloc_transfer(void)
 {
-	struct usbi_transfer *transfer = malloc(sizeof(*transfer));
+	struct usbi_transfer *transfer =
+		malloc(sizeof(*transfer) + usbi_backend->transfer_priv_size);
 	if (!transfer)
 		return NULL;
 
@@ -212,7 +184,7 @@
 	int r;
 
 	usbi_dbg("");
-	r = ioctl(transfer->dev_handle->fd, IOCTL_USB_DISCARDURB, &itransfer->urb);
+	r = usbi_backend->cancel_transfer(itransfer);
 	if (r < 0)
 		usbi_err("cancel transfer failed error %d", r);
 	return r;
@@ -224,7 +196,7 @@
 	int r;
 
 	usbi_dbg("");
-	r = ioctl(transfer->dev_handle->fd, IOCTL_USB_DISCARDURB, &itransfer->urb);
+	r = usbi_backend->cancel_transfer(itransfer);
 	if (r < 0) {
 		usbi_err("cancel transfer failed error %d", r);
 		return r;
@@ -240,7 +212,7 @@
 	return 0;
 }
 
-static void handle_transfer_completion(struct usbi_transfer *itransfer,
+void usbi_handle_transfer_completion(struct usbi_transfer *itransfer,
 	enum libusb_transfer_status status)
 {
 	struct libusb_transfer *transfer = &itransfer->pub;
@@ -267,7 +239,7 @@
 		libusb_free_transfer(transfer);
 }
 
-static void handle_transfer_cancellation(struct usbi_transfer *transfer)
+void usbi_handle_transfer_cancellation(struct usbi_transfer *transfer)
 {
 	/* if the URB is being cancelled synchronously, raise cancellation
 	 * completion event by unsetting flag, and ensure that user callback does
@@ -276,83 +248,20 @@
 	if (transfer->flags & USBI_TRANSFER_SYNC_CANCELLED) {
 		transfer->flags &= ~USBI_TRANSFER_SYNC_CANCELLED;
 		usbi_dbg("detected sync. cancel");
-		handle_transfer_completion(transfer, LIBUSB_TRANSFER_SILENT_COMPLETION);
+		usbi_handle_transfer_completion(transfer,
+			LIBUSB_TRANSFER_SILENT_COMPLETION);
 		return;
 	}
 
 	/* if the URB was cancelled due to timeout, report timeout to the user */
 	if (transfer->flags & USBI_TRANSFER_TIMED_OUT) {
 		usbi_dbg("detected timeout cancellation");
-		handle_transfer_completion(transfer, LIBUSB_TRANSFER_TIMED_OUT);
+		usbi_handle_transfer_completion(transfer, LIBUSB_TRANSFER_TIMED_OUT);
 		return;
 	}
 
 	/* otherwise its a normal async cancel */
-	handle_transfer_completion(transfer, LIBUSB_TRANSFER_CANCELLED);
-}
-
-static int reap_for_devh(struct libusb_device_handle *devh)
-{
-	int r;
-	struct usb_urb *urb;
-	struct usbi_transfer *itransfer;
-	struct libusb_transfer *transfer;
-	int trf_requested;
-	int length;
-
-	r = ioctl(devh->fd, IOCTL_USB_REAPURBNDELAY, &urb);
-	if (r == -1 && errno == EAGAIN)
-		return r;
-	if (r < 0) {
-		usbi_err("reap failed error %d errno=%d", r, errno);
-		return r;
-	}
-
-	itransfer = container_of(urb, struct usbi_transfer, urb);
-	transfer = &itransfer->pub;
-
-	usbi_dbg("urb type=%d status=%d transferred=%d", urb->type, urb->status,
-		urb->actual_length);
-	list_del(&itransfer->list);
-
-	if (urb->status == -2) {
-		handle_transfer_cancellation(itransfer);
-		return 0;
-	}
-
-	/* FIXME: research what other status codes may exist */
-	if (urb->status != 0)
-		usbi_warn("unrecognised urb status %d", urb->status);
-
-	/* determine how much data was asked for */
-	length = transfer->length;
-	if (transfer->endpoint_type == LIBUSB_ENDPOINT_TYPE_CONTROL)
-		length -= LIBUSB_CONTROL_SETUP_SIZE;
-	trf_requested = MIN(length - itransfer->transferred,
-		MAX_URB_BUFFER_LENGTH);
-
-	itransfer->transferred += urb->actual_length;
-
-	/* if we were provided less data than requested, then our transfer is
-	 * done */
-	if (urb->actual_length < trf_requested) {
-		usbi_dbg("less data than requested (%d/%d) --> all done",
-			urb->actual_length, trf_requested);
-		handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED);
-		return 0;
-	}
-
-	/* if we've transferred all data, we're done */
-	if (itransfer->transferred == length) {
-		usbi_dbg("transfer complete --> all done");
-		handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED);
-		return 0;
-	}
-
-	/* otherwise, we have more data to transfer */
-	usbi_dbg("more data to transfer...");
-	memset(urb, 0, sizeof(*urb));
-	return submit_transfer(itransfer);
+	usbi_handle_transfer_completion(transfer, LIBUSB_TRANSFER_CANCELLED);
 }
 
 static void handle_timeout(struct usbi_transfer *itransfer)
@@ -416,10 +325,14 @@
 
 static int poll_io(struct timeval *tv)
 {
-	struct libusb_device_handle *devh;
 	int r;
 	int maxfd = 0;
-	fd_set writefds;
+	fd_set readfds, writefds;
+	fd_set *_readfds = NULL;
+	fd_set *_writefds = NULL;
+	struct usbi_pollfd *ipollfd;
+	int have_readfds = 0;
+	int have_writefds = 0;
 	struct timeval select_timeout;
 	struct timeval timeout;
 
@@ -438,19 +351,33 @@
 		select_timeout = *tv;
 	}
 
+	FD_ZERO(&readfds);
 	FD_ZERO(&writefds);
-	list_for_each_entry(devh, &open_devs, list) {
-		int fd = devh->fd;
-		FD_SET(fd, &writefds);
+	list_for_each_entry(ipollfd, &pollfds, list) {
+		struct libusb_pollfd *pollfd = &ipollfd->pollfd;
+		int fd = pollfd->fd;
+		if (pollfd->events & POLLIN) {
+			have_readfds = 1;
+			FD_SET(fd, &readfds);
+		}
+		if (pollfd->events & POLLOUT) {
+			have_writefds = 1;
+			FD_SET(fd, &writefds);
+		}
 		if (fd > maxfd)
 			maxfd = fd;
 	}
 
+	if (have_readfds)
+		_readfds = &readfds;
+	if (have_writefds)
+		_writefds = &writefds;
+
 	usbi_dbg("select() with timeout in %d.%06ds", select_timeout.tv_sec,
 		select_timeout.tv_usec);
-	r = select(maxfd + 1, NULL, &writefds, NULL, &select_timeout);
-	usbi_dbg("select() returned %d with %d.%06ds remaining", r, select_timeout.tv_sec,
-		select_timeout.tv_usec);
+	r = select(maxfd + 1, _readfds, _writefds, NULL, &select_timeout);
+	usbi_dbg("select() returned %d with %d.%06ds remaining",
+		r, select_timeout.tv_sec, select_timeout.tv_usec);
 	if (r == 0) {
 		*tv = select_timeout;
 		return handle_timeouts();
@@ -461,15 +388,9 @@
 		return r;
 	}
 
-	list_for_each_entry(devh, &open_devs, list) {
-		if (!FD_ISSET(devh->fd, &writefds))
-			continue;
-		r = reap_for_devh(devh);
-		if (r == -1 && errno == EAGAIN)
-			continue;
-		if (r < 0)
-			return r;
-	}
+	r = usbi_backend->handle_events(_readfds, _writefds);
+	if (r < 0)
+		return r;
 
 	/* FIXME check return value? */
 	return handle_timeouts();
@@ -561,17 +482,63 @@
 	fd_removed_cb = removed_cb;
 }
 
-void usbi_add_pollfd(int fd, short events)
+int usbi_add_pollfd(int fd, short events)
 {
+	struct usbi_pollfd *ipollfd = malloc(sizeof(*ipollfd));
+	if (!ipollfd)
+		return -ENOMEM;
+
 	usbi_dbg("add fd %d events %d", fd, events);
+	ipollfd->pollfd.fd = fd;
+	ipollfd->pollfd.events = events;
+	list_add(&ipollfd->list, &pollfds);
+
 	if (fd_added_cb)
 		fd_added_cb(fd, events);
+	return 0;
 }
 
 void usbi_remove_pollfd(int fd)
 {
+	struct usbi_pollfd *ipollfd;
+	int found = 0;
+
 	usbi_dbg("remove fd %d", fd);
+	list_for_each_entry(ipollfd, &pollfds, list)
+		if (ipollfd->pollfd.fd == fd) {
+			found = 1;
+			break;
+		}
+
+	if (!found) {
+		usbi_err("couldn't find fd %d to remove", fd);
+		return;
+	}
+
+	list_del(&ipollfd->list);
+	free(ipollfd);
 	if (fd_removed_cb)
 		fd_removed_cb(fd);
 }
 
+API_EXPORTED struct libusb_pollfd **libusb_get_pollfds(void)
+{
+	struct libusb_pollfd **ret;
+	struct usbi_pollfd *ipollfd;
+	size_t i = 0;
+	size_t cnt = 0;
+
+	list_for_each_entry(ipollfd, &pollfds, list)
+		cnt++;
+
+	ret = calloc(cnt + 1, sizeof(struct libusb_pollfd *));
+	if (!ret)
+		return NULL;
+
+	list_for_each_entry(ipollfd, &pollfds, list)
+		ret[i++] = (struct libusb_pollfd *) ipollfd;
+	ret[cnt] = NULL;
+
+	return ret;
+}
+
diff --git a/libusb/libusb.h b/libusb/libusb.h
index 4f38ddf..414803c 100644
--- a/libusb/libusb.h
+++ b/libusb/libusb.h
@@ -351,7 +351,7 @@
 int libusb_poll_timeout(struct timeval *tv);
 int libusb_poll(void);
 int libusb_get_next_timeout(struct timeval *tv);
-size_t libusb_get_pollfds(struct libusb_pollfd **pollfds);
+struct libusb_pollfd **libusb_get_pollfds(void);
 
 typedef void (*libusb_pollfd_added_cb)(int fd, short events);
 typedef void (*libusb_pollfd_removed_cb)(int fd);
diff --git a/libusb/libusbi.h b/libusb/libusbi.h
index 2ef2573..2d56d64 100644
--- a/libusb/libusbi.h
+++ b/libusb/libusbi.h
@@ -25,11 +25,10 @@
 
 #include <endian.h>
 #include <stddef.h>
-#include <sys/ioctl.h>
+#include <sys/select.h>
 #include <time.h>
 
 #include <libusb.h>
-#include <usbfs.h>
 
 #define DEVICE_DESC_LENGTH		18
 
@@ -146,15 +145,15 @@
 	struct list_head list;
 	int refcnt;
 	unsigned long session_data;
-	char *nodepath;
 	struct libusb_device_descriptor desc;
 	struct libusb_config_descriptor *config;
+	unsigned char os_priv[0];
 };
 
 struct libusb_device_handle {
 	struct list_head list;
 	struct libusb_device *dev;
-	int fd;
+	unsigned char os_priv[0];
 };
 
 #define USBI_TRANSFER_SYNC_CANCELLED 		(1<<0)
@@ -164,11 +163,12 @@
 	/* must come first */
 	struct libusb_transfer pub;
 
-	struct usb_urb urb;
 	struct list_head list;
 	struct timeval timeout;
 	int transferred;
 	uint8_t flags;
+
+	unsigned char os_priv[0];
 };
 
 /* bus structures */
@@ -181,15 +181,84 @@
 
 /* shared data and functions */
 
-extern struct list_head open_devs;
+extern struct list_head usbi_open_devs;
 
 void usbi_io_init(void);
-void usbi_add_pollfd(int fd, short events);
-void usbi_remove_pollfd(int fd);
+
+struct libusb_device *usbi_alloc_device(unsigned long session_id);
+struct libusb_device *usbi_get_device_by_session_id(unsigned long session_id);
+
+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);
 
+/* polling */
+
+struct usbi_pollfd {
+	/* must come first */
+	struct libusb_pollfd pollfd;
+
+	struct list_head list;
+};
+
+int usbi_add_pollfd(int fd, short events);
+void usbi_remove_pollfd(int fd);
+
+/* device discovery */
+
+/* we traverse usbfs without knowing how many devices we are going to find.
+ * so we create this discovered_devs model which is similar to a linked-list
+ * which grows when required. it can be freed once discovery has completed,
+ * eliminating the need for a list node in the libusb_device structure
+ * itself. */
+struct discovered_devs {
+	size_t len;
+	size_t capacity;
+	struct libusb_device *devices[0];
+};
+
+struct discovered_devs *discovered_devs_append(
+	struct discovered_devs *discdevs, struct libusb_device *dev);
+
+/* OS abstraction */
+
+struct usbi_os_backend {
+	const char *name;
+	int (*init)(void);
+	void (*exit)(void);
+
+	int (*get_device_list)(struct discovered_devs *discdevs);
+
+	int (*open)(struct libusb_device_handle *handle);
+	void (*close)(struct libusb_device_handle *handle);
+
+	int (*claim_interface)(struct libusb_device_handle *handle, int iface);
+	int (*release_interface)(struct libusb_device_handle *handle, int iface);
+
+	void (*destroy_device)(struct libusb_device *dev);
+
+	int (*submit_transfer)(struct usbi_transfer *itransfer);
+	int (*cancel_transfer)(struct usbi_transfer *itransfer);
+
+	int (*handle_events)(fd_set *readfds, fd_set *writefds);
+
+	/* number of bytes to reserve for libusb_device.os_priv */
+	size_t device_priv_size;
+
+	/* number of bytes to reserve for libusb_device_handle.os_priv */
+	size_t device_handle_priv_size;
+
+	/* number of bytes to reserve for usbi_transfer.os_priv */
+	size_t transfer_priv_size;
+};
+
+extern const struct usbi_os_backend * const usbi_backend;
+
+extern const struct usbi_os_backend linux_usbfs_backend;
+
 #endif
 
diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c
new file mode 100644
index 0000000..bfd526b
--- /dev/null
+++ b/libusb/os/linux_usbfs.c
@@ -0,0 +1,584 @@
+/*
+ * Linux usbfs backend for libusb
+ * Copyright (C) 2007-2008 Daniel Drake <dsd@gentoo.org>
+ * Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "libusb.h"
+#include "libusbi.h"
+#include "linux_usbfs.h"
+
+static const char *usbfs_path = NULL;
+
+struct linux_device_priv {
+	char *nodepath;	
+};
+
+struct linux_device_handle_priv {
+	int fd;
+};
+
+struct linux_transfer_priv {
+	struct usbfs_urb urb;
+};
+
+static struct linux_device_priv *__device_priv(struct libusb_device *dev)
+{
+	return (struct linux_device_priv *) dev->os_priv;
+}
+
+static struct linux_device_handle_priv *__device_handle_priv(
+	struct libusb_device_handle *handle)
+{
+	return (struct linux_device_handle_priv *) handle->os_priv;
+}
+
+static struct linux_transfer_priv *__transfer_priv(
+	struct usbi_transfer *transfer)
+{
+	return (struct linux_transfer_priv *) transfer->os_priv;
+}
+
+#define TRANSFER_PRIV_GET_ITRANSFER(tpriv) \
+	((struct usbi_transfer *) \
+		container_of((tpriv), struct usbi_transfer, os_priv))
+
+static int check_usb_vfs(const char *dirname)
+{
+	DIR *dir;
+	struct dirent *entry;
+	int found = 0;
+
+	dir = opendir(dirname);
+	if (!dir)
+		return 0;
+
+	while ((entry = readdir(dir)) != NULL) {
+		if (entry->d_name[0] == '.')
+			continue;
+
+		/* We assume if we find any files that it must be the right place */
+		found = 1;
+		break;
+	}
+
+	closedir(dir);
+	return found;
+}
+
+static const char *find_usbfs_path(void)
+{
+	const char *path = "/dev/bus/usb";
+	const char *ret = NULL;
+
+	if (check_usb_vfs(path)) {
+		ret = path;
+	} else {
+		path = "/proc/bus/usb";
+		if (check_usb_vfs(path))
+			ret = path;
+	}
+
+	usbi_dbg("found usbfs at %s", ret);
+	return ret;
+}
+
+static int op_init(void)
+{
+	usbfs_path = find_usbfs_path();
+	if (!usbfs_path) {
+		usbi_err("could not find usbfs");
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static int initialize_device(struct libusb_device *dev, uint8_t busnum,
+	uint8_t devaddr)
+{
+	struct linux_device_priv *priv = __device_priv(dev);
+	char path[PATH_MAX + 1];
+	unsigned char raw_desc[DEVICE_DESC_LENGTH];
+	int fd = 0;
+	int i;
+	int r;
+	int tmp;
+
+	priv->nodepath = NULL;
+	dev->config = NULL;
+
+	snprintf(path, PATH_MAX, "%s/%03d/%03d", usbfs_path, busnum, devaddr);
+	usbi_dbg("%s", path);
+	fd = open(path, O_RDWR);
+	if (!fd) {
+		usbi_dbg("open '%s' failed, ret=%d errno=%d", path, fd, errno);
+		/* FIXME this might not be an error if the file has gone away due
+		 * to unplugging */
+		r = -EIO;
+		goto err;
+	}
+
+	/* FIXME: move config parsing into main lib */
+	r = read(fd, raw_desc, DEVICE_DESC_LENGTH);
+	if (r < 0) {
+		usbi_err("read failed ret=%d errno=%d", r, errno);
+		goto err;
+	}
+	/* FIXME: short read handling? */
+
+	usbi_parse_descriptor(raw_desc, "bbWbbbbWWWbbbb", &dev->desc);
+
+	/* Now try to fetch the rest of the descriptors */
+	if (dev->desc.bNumConfigurations > USB_MAXCONFIG) {
+		usbi_err("too many configurations");
+		r = -EINVAL;
+		goto err;
+	}
+
+	if (dev->desc.bNumConfigurations < 1) {
+		usbi_dbg("no configurations?");
+		r = -EINVAL;
+		goto err;
+	}
+
+	tmp = dev->desc.bNumConfigurations * sizeof(struct libusb_config_descriptor);
+	dev->config = malloc(tmp);
+	if (!dev->config) {
+		r = -ENOMEM;
+		goto err;
+	}
+
+	memset(dev->config, 0, tmp);
+	for (i = 0; i < dev->desc.bNumConfigurations; i++) {
+		unsigned char buffer[8], *bigbuffer;
+		struct libusb_config_descriptor config;
+
+		/* Get the first 8 bytes to figure out what the total length is */
+		r = read(fd, buffer, sizeof(buffer));
+		if (r < sizeof(buffer)) {
+			usbi_err("short descriptor read (%d/%d)", r, sizeof(buffer));
+			r = -EIO;
+			goto err;
+		}
+
+		usbi_parse_descriptor(buffer, "bbw", &config);
+
+		bigbuffer = malloc(config.wTotalLength);
+		if (!bigbuffer) {
+			r = -ENOMEM;
+			goto err;
+		}
+
+		/* Read the rest of the config descriptor */
+		memcpy(bigbuffer, buffer, sizeof(buffer));
+
+		tmp = config.wTotalLength - 8;
+		r = read(fd, bigbuffer + 8, tmp);
+		if (r < tmp) {
+			usbi_err("short descriptor read (%d/%d)", r, tmp);
+			free(bigbuffer);
+			r = -EIO;
+			goto err;
+		}
+
+		r = usbi_parse_configuration(&dev->config[i], bigbuffer);
+		if (r > 0)
+			usbi_warn("descriptor data still left\n");
+		free(bigbuffer);
+	}
+
+	priv->nodepath = strdup(path);
+	if (!priv->nodepath) {
+		r = -ENOMEM;
+		goto err;
+	}
+
+	close(fd);
+	return 0;
+
+err:
+	if (fd)
+		close(fd);
+	if (dev->config) {
+		free(dev->config);
+		dev->config = NULL;
+	}
+	if (priv->nodepath) {
+		free(priv->nodepath);
+		priv->nodepath = NULL;
+	}
+	return r;
+}
+
+/* open a device file, set up the libusb_device structure for it, and add it to
+ * discdevs. on failure (non-zero return) the pre-existing discdevs should
+ * be destroyed (and devices freed). on success, the new discdevs pointer
+ * should be used it may have been moved. */
+static int scan_device(struct discovered_devs **_discdevs, uint8_t busnum,
+	uint8_t devaddr)
+{
+	struct discovered_devs *discdevs;
+	unsigned long session_id;
+	struct libusb_device *dev;
+	int need_unref = 0;
+	int r = 0;
+
+	/* FIXME: session ID is not guaranteed unique as addresses can wrap and
+	 * will be reused. instead we should add a simple sysfs attribute with
+	 * a session ID. */
+	session_id = busnum << 8 | devaddr;
+	usbi_dbg("busnum %d devaddr %d session_id %ld", busnum, devaddr,
+		session_id);
+
+	dev = usbi_get_device_by_session_id(session_id);
+	if (dev) {
+		usbi_dbg("using existing device for %d/%d (session %ld)",
+			busnum, devaddr, session_id);
+	} else {
+		usbi_dbg("allocating new device for %d/%d (session %ld)",
+			busnum, devaddr, session_id);
+		dev = usbi_alloc_device(session_id);
+		if (!dev) {
+			r = -ENOMEM;
+			goto out;
+		}
+		need_unref = 1;
+		r = initialize_device(dev, busnum, devaddr);
+		if (r < 0)
+			goto out;
+	}
+
+	discdevs = discovered_devs_append(*_discdevs, dev);
+	if (!discdevs)
+		r = -ENOMEM;
+	else
+		*_discdevs = discdevs;
+
+out:
+	if (need_unref)
+		libusb_device_unref(dev);
+	return r;
+}
+
+/* open a bus directory and adds all discovered devices to discdevs. on
+ * failure (non-zero return) the pre-existing discdevs should be destroyed
+ * (and devices freed). on success, the new discdevs pointer should be used
+ * as it may have been moved. */
+static int scan_busdir(struct discovered_devs **_discdevs, uint8_t busnum)
+{
+	DIR *dir;
+	char dirpath[PATH_MAX + 1];
+	struct dirent *entry;
+	struct discovered_devs *discdevs = *_discdevs;
+	int r = 0;
+
+	snprintf(dirpath, PATH_MAX, "%s/%03d", usbfs_path, busnum);
+	usbi_dbg("%s", dirpath);
+	dir = opendir(dirpath);
+	if (!dir) {
+		usbi_err("opendir '%s' failed, errno=%d", dirpath, errno);
+		/* FIXME: should handle valid race conditions like hub unplugged
+		 * during directory iteration - this is not an error */
+		return -1;
+	}
+
+	while ((entry = readdir(dir))) {
+		int devaddr;
+
+		if (entry->d_name[0] == '.')
+			continue;
+
+		devaddr = atoi(entry->d_name);
+		if (devaddr == 0) {
+			usbi_dbg("unknown dir entry %s", entry->d_name);
+			continue;
+		}
+
+		r = scan_device(&discdevs, busnum, (uint8_t) devaddr);
+		if (r < 0)
+			goto out;
+	}
+
+	*_discdevs = discdevs;
+out:
+	closedir(dir);
+	return r;
+}
+
+static int op_get_device_list(struct discovered_devs *discdevs)
+{
+	struct dirent *entry;
+	int r = 0;
+	DIR *buses = opendir(usbfs_path);
+	if (!buses) {
+		usbi_err("opendir buses failed errno=%d", errno);
+		return -1;
+	}
+
+	while ((entry = readdir(buses))) {
+		struct discovered_devs *discdevs_new = discdevs;
+		int busnum;
+
+		if (entry->d_name[0] == '.')
+			continue;
+
+		busnum = atoi(entry->d_name);
+		if (busnum == 0) {
+			usbi_dbg("unknown dir entry %s", entry->d_name);
+			continue;
+		}
+
+		r = scan_busdir(&discdevs_new, busnum);
+		if (r < 0)
+			goto out;
+		discdevs = discdevs_new;
+	}
+
+out:
+	closedir(buses);
+	return r;
+}
+
+static int op_open(struct libusb_device_handle *handle)
+{
+	struct linux_device_priv *dpriv = __device_priv(handle->dev);
+	struct linux_device_handle_priv *hpriv = __device_handle_priv(handle);
+
+	hpriv->fd = open(dpriv->nodepath, O_RDWR);
+	if (hpriv->fd < 0) {
+		usbi_err("open failed, code %d errno %d", hpriv->fd, errno);
+		return -EIO;
+	}
+
+	return usbi_add_pollfd(hpriv->fd, POLLOUT);
+}
+
+static void op_close(struct libusb_device_handle *dev_handle)
+{
+	int fd = __device_handle_priv(dev_handle)->fd;
+	usbi_remove_pollfd(fd);
+	close(fd);
+}
+
+static int op_claim_interface(struct libusb_device_handle *handle, int iface)
+{
+	int fd = __device_handle_priv(handle)->fd;
+	int r = ioctl(fd, IOCTL_USBFS_CLAIMINTF, &iface);
+	if (r < 0)
+		usbi_err("claim interface failed, error %d", r);
+	return r;
+}
+
+static int op_release_interface(struct libusb_device_handle *handle, int iface)
+{
+	int fd = __device_handle_priv(handle)->fd;
+	int r = ioctl(fd, IOCTL_USBFS_RELEASEINTF, &iface);
+	if (r < 0)
+		usbi_err("release interface failed, error %d", r);
+	return r;
+}
+
+static void op_destroy_device(struct libusb_device *dev)
+{
+	unsigned char *nodepath = __device_priv(dev)->nodepath;
+	if (nodepath)
+		free(nodepath);
+}
+
+static int submit_transfer(struct usbi_transfer *itransfer)
+{
+	struct libusb_transfer *transfer = &itransfer->pub;
+	struct usbfs_urb *urb = &__transfer_priv(itransfer)->urb;
+	struct linux_device_handle_priv *dpriv =
+		__device_handle_priv(transfer->dev_handle);
+	int to_be_transferred = transfer->length - itransfer->transferred;
+	int r;
+
+	urb->buffer = transfer->buffer + itransfer->transferred;
+	urb->buffer_length = MIN(to_be_transferred, MAX_URB_BUFFER_LENGTH);
+
+	/* FIXME: for requests that we have to split into multiple URBs, we should
+	 * submit all the URBs instantly: submit, submit, submit, reap, reap, reap
+	 * rather than: submit, reap, submit, reap, submit, reap
+	 * this will improve performance and fix bugs concerning behaviour when
+	 * the user submits two similar multiple-urb requests */
+	usbi_dbg("transferring %d from %d bytes", urb->buffer_length,
+		to_be_transferred);
+
+	r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb);
+	if (r < 0)
+		usbi_err("submiturb failed error %d errno=%d", r, errno);
+
+	return r;
+}
+
+static int op_submit_transfer(struct usbi_transfer *itransfer)
+{
+	struct usbfs_urb *urb = &__transfer_priv(itransfer)->urb;
+	struct libusb_transfer *transfer = &itransfer->pub;
+
+	memset(urb, 0, sizeof(*urb));
+	switch (transfer->endpoint_type) {
+	case LIBUSB_ENDPOINT_TYPE_CONTROL:
+		urb->type = USBFS_URB_TYPE_CONTROL;
+		break;
+	case LIBUSB_ENDPOINT_TYPE_BULK:
+		urb->type = USBFS_URB_TYPE_BULK;
+		break;
+	case LIBUSB_ENDPOINT_TYPE_INTERRUPT:
+		urb->type = USBFS_URB_TYPE_INTERRUPT;
+		break;
+	default:
+		usbi_err("unknown endpoint type %d", transfer->endpoint_type);
+		return -EINVAL;
+	}
+
+	urb->endpoint = transfer->endpoint;
+	return submit_transfer(itransfer);
+}
+
+static int op_cancel_transfer(struct usbi_transfer *itransfer)
+{
+	struct usbfs_urb *urb = &__transfer_priv(itransfer)->urb;
+	struct libusb_transfer *transfer = &itransfer->pub;
+	struct linux_device_handle_priv *dpriv =
+		__device_handle_priv(transfer->dev_handle);
+
+	return ioctl(dpriv->fd, IOCTL_USBFS_DISCARDURB, urb);
+}
+
+static int reap_for_handle(struct libusb_device_handle *handle)
+{
+	struct linux_device_handle_priv *hpriv = __device_handle_priv(handle);
+	int r;
+	struct usbfs_urb *urb;
+	struct linux_transfer_priv *tpriv;
+	struct usbi_transfer *itransfer;
+	struct libusb_transfer *transfer;
+	int trf_requested;
+	int length;
+
+	r = ioctl(hpriv->fd, IOCTL_USBFS_REAPURBNDELAY, &urb);
+	if (r == -1 && errno == EAGAIN)
+		return r;
+	if (r < 0) {
+		usbi_err("reap failed error %d errno=%d", r, errno);
+		return r;
+	}
+
+	tpriv = container_of(urb, struct linux_transfer_priv, urb);
+	itransfer = TRANSFER_PRIV_GET_ITRANSFER(tpriv);
+	transfer = &itransfer->pub;
+
+	usbi_dbg("urb type=%d status=%d transferred=%d", urb->type, urb->status,
+		urb->actual_length);
+	list_del(&itransfer->list);
+
+	if (urb->status == -2) {
+		usbi_handle_transfer_cancellation(itransfer);
+		return 0;
+	}
+
+	/* FIXME: research what other status codes may exist */
+	if (urb->status != 0)
+		usbi_warn("unrecognised urb status %d", urb->status);
+
+	/* determine how much data was asked for */
+	length = transfer->length;
+	if (transfer->endpoint_type == LIBUSB_ENDPOINT_TYPE_CONTROL)
+		length -= LIBUSB_CONTROL_SETUP_SIZE;
+	trf_requested = MIN(length - itransfer->transferred,
+		MAX_URB_BUFFER_LENGTH);
+
+	itransfer->transferred += urb->actual_length;
+
+	/* if we were provided less data than requested, then our transfer is
+	 * done */
+	if (urb->actual_length < trf_requested) {
+		usbi_dbg("less data than requested (%d/%d) --> all done",
+			urb->actual_length, trf_requested);
+		usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED);
+		return 0;
+	}
+
+	/* if we've transferred all data, we're done */
+	if (itransfer->transferred == length) {
+		usbi_dbg("transfer complete --> all done");
+		usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED);
+		return 0;
+	}
+
+	/* otherwise, we have more data to transfer */
+	usbi_dbg("more data to transfer...");
+	return submit_transfer(itransfer);
+}
+
+static int op_handle_events(fd_set *readfds, fd_set *writefds)
+{
+	struct libusb_device_handle *handle;
+	int r;
+
+	list_for_each_entry(handle, &usbi_open_devs, list) {
+		struct linux_device_handle_priv *hpriv = __device_handle_priv(handle);
+		if (!FD_ISSET(hpriv->fd, writefds))
+			continue;
+		r = reap_for_handle(handle);
+		if (r == -1 && errno == EAGAIN)
+			continue;
+		if (r < 0)
+			return r;
+	}
+
+	return 0;
+}
+
+const struct usbi_os_backend linux_usbfs_backend = {
+	.name = "Linux usbfs",
+	.init = op_init,
+	.exit = NULL,
+	.get_device_list = op_get_device_list,
+	.open = op_open,
+	.close = op_close,
+	.claim_interface = op_claim_interface,
+	.release_interface = op_release_interface,
+
+	.destroy_device = op_destroy_device,
+
+	.submit_transfer = op_submit_transfer,
+	.cancel_transfer = op_cancel_transfer,
+
+	.handle_events = op_handle_events,
+
+	.device_priv_size = sizeof(struct linux_device_priv),
+	.device_handle_priv_size = sizeof(struct linux_device_handle_priv),
+	.transfer_priv_size = sizeof(struct linux_transfer_priv),
+};
+
diff --git a/libusb/os/linux_usbfs.h b/libusb/os/linux_usbfs.h
new file mode 100644
index 0000000..1642959
--- /dev/null
+++ b/libusb/os/linux_usbfs.h
@@ -0,0 +1,133 @@
+/*
+ * usbfs header structures
+ * Copyright (C) 2007 Daniel Drake <dsd@gentoo.org>
+ * Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __LIBUSB_USBFS_H__
+#define __LIBUSB_USBFS_H__
+
+struct usbfs_ctrltransfer {
+	/* keep in sync with usbdevice_fs.h:usbdevfs_ctrltransfer */
+	uint8_t  bRequestType;
+	uint8_t  bRequest;
+	uint16_t wValue;
+	uint16_t wIndex;
+	uint16_t wLength;
+
+	uint32_t timeout;	/* in milliseconds */
+
+	/* pointer to data */
+	void *data;
+};
+
+struct usbfs_bulktransfer {
+	/* keep in sync with usbdevice_fs.h:usbdevfs_bulktransfer */
+	unsigned int ep;
+	unsigned int len;
+	unsigned int timeout;	/* in milliseconds */
+
+	/* pointer to data */
+	void *data;
+};
+
+struct usbfs_setinterface {
+	/* keep in sync with usbdevice_fs.h:usbdevfs_setinterface */
+	unsigned int interface;
+	unsigned int altsetting;
+};
+
+#define USBFS_MAXDRIVERNAME 255
+
+struct usbfs_getdriver {
+	unsigned int interface;
+	char driver[USBFS_MAXDRIVERNAME + 1];
+};
+
+#define USBFS_URB_DISABLE_SPD	1
+#define USBFS_URB_ISO_ASAP	2
+#define USBFS_URB_QUEUE_BULK	0x10
+
+enum usbfs_urb_type {
+	USBFS_URB_TYPE_ISO = 0,
+	USBFS_URB_TYPE_INTERRUPT = 1,
+	USBFS_URB_TYPE_CONTROL = 2,
+	USBFS_URB_TYPE_BULK = 3,
+};
+
+struct usbfs_iso_packet_desc {
+	unsigned int length;
+	unsigned int actual_length;
+	unsigned int status;
+};
+
+#define MAX_URB_BUFFER_LENGTH		16384
+
+struct usbfs_urb {
+	unsigned char type;
+	unsigned char endpoint;
+	int status;
+	unsigned int flags;
+	void *buffer;
+	int buffer_length;
+	int actual_length;
+	int start_frame;
+	int number_of_packets;
+	int error_count;
+	unsigned int signr;
+	void *usercontext;
+	struct usbfs_iso_packet_desc iso_frame_desc[0];
+};
+
+struct usbfs_connectinfo {
+	unsigned int devnum;
+	unsigned char slow;
+};
+
+struct usbfs_ioctl {
+	int ifno;	/* interface 0..N ; negative numbers reserved */
+	int ioctl_code;	/* MUST encode size + direction of data so the
+			 * macros in <asm/ioctl.h> give correct values */
+	void *data;	/* param buffer (in, or out) */
+};
+
+struct usbfs_hub_portinfo {
+	unsigned char numports;
+	unsigned char port[127];	/* port to device num mapping */
+};
+
+#define IOCTL_USBFS_CONTROL	_IOWR('U', 0, struct usbfs_ctrltransfer)
+#define IOCTL_USBFS_BULK		_IOWR('U', 2, struct usbfs_bulktransfer)
+#define IOCTL_USBFS_RESETEP	_IOR('U', 3, unsigned int)
+#define IOCTL_USBFS_SETINTF	_IOR('U', 4, struct usbfs_setinterface)
+#define IOCTL_USBFS_SETCONFIG	_IOR('U', 5, unsigned int)
+#define IOCTL_USBFS_GETDRIVER	_IOW('U', 8, struct usbfs_getdriver)
+#define IOCTL_USBFS_SUBMITURB	_IOR('U', 10, struct usbfs_urb)
+#define IOCTL_USBFS_DISCARDURB	_IO('U', 11)
+#define IOCTL_USBFS_REAPURB	_IOW('U', 12, void *)
+#define IOCTL_USBFS_REAPURBNDELAY	_IOW('U', 13, void *)
+#define IOCTL_USBFS_CLAIMINTF	_IOR('U', 15, unsigned int)
+#define IOCTL_USBFS_RELEASEINTF	_IOR('U', 16, unsigned int)
+#define IOCTL_USBFS_CONNECTINFO	_IOW('U', 17, struct usbfs_connectinfo)
+#define IOCTL_USBFS_IOCTL         _IOWR('U', 18, struct usbfs_ioctl)
+#define IOCTL_USBFS_HUB_PORTINFO	_IOR('U', 19, struct usbfs_hub_portinfo)
+#define IOCTL_USBFS_RESET		_IO('U', 20)
+#define IOCTL_USBFS_CLEAR_HALT	_IOR('U', 21, unsigned int)
+#define IOCTL_USBFS_DISCONNECT	_IO('U', 22)
+#define IOCTL_USBFS_CONNECT	_IO('U', 23)
+
+#endif
diff --git a/libusb/usbfs.h b/libusb/usbfs.h
deleted file mode 100644
index 9daf0cc..0000000
--- a/libusb/usbfs.h
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * usbfs header structures
- * Copyright (C) 2007 Daniel Drake <dsd@gentoo.org>
- * Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#ifndef __LIBUSB_USBFS_H__
-#define __LIBUSB_USBFS_H__
-
-struct usb_ctrltransfer {
-	/* keep in sync with usbdevice_fs.h:usbdevfs_ctrltransfer */
-	uint8_t  bRequestType;
-	uint8_t  bRequest;
-	uint16_t wValue;
-	uint16_t wIndex;
-	uint16_t wLength;
-
-	uint32_t timeout;	/* in milliseconds */
-
-	/* pointer to data */
-	void *data;
-};
-
-struct usb_bulktransfer {
-	/* keep in sync with usbdevice_fs.h:usbdevfs_bulktransfer */
-	unsigned int ep;
-	unsigned int len;
-	unsigned int timeout;	/* in milliseconds */
-
-	/* pointer to data */
-	void *data;
-};
-
-struct usb_setinterface {
-	/* keep in sync with usbdevice_fs.h:usbdevfs_setinterface */
-	unsigned int interface;
-	unsigned int altsetting;
-};
-
-#define USB_MAXDRIVERNAME 255
-
-struct usb_getdriver {
-	unsigned int interface;
-	char driver[USB_MAXDRIVERNAME + 1];
-};
-
-#define USB_URB_DISABLE_SPD	1
-#define USB_URB_ISO_ASAP	2
-#define USB_URB_QUEUE_BULK	0x10
-
-enum usb_urb_type {
-	USB_URB_TYPE_ISO = 0,
-	USB_URB_TYPE_INTERRUPT = 1,
-	USB_URB_TYPE_CONTROL = 2,
-	USB_URB_TYPE_BULK = 3,
-};
-
-struct usb_iso_packet_desc {
-	unsigned int length;
-	unsigned int actual_length;
-	unsigned int status;
-};
-
-#define MAX_URB_BUFFER_LENGTH		16384
-
-struct usb_urb {
-	unsigned char type;
-	unsigned char endpoint;
-	int status;
-	unsigned int flags;
-	void *buffer;
-	int buffer_length;
-	int actual_length;
-	int start_frame;
-	int number_of_packets;
-	int error_count;
-	unsigned int signr;
-	void *usercontext;
-	struct usb_iso_packet_desc iso_frame_desc[0];
-};
-
-struct usb_connectinfo {
-	unsigned int devnum;
-	unsigned char slow;
-};
-
-struct usb_ioctl {
-	int ifno;	/* interface 0..N ; negative numbers reserved */
-	int ioctl_code;	/* MUST encode size + direction of data so the
-			 * macros in <asm/ioctl.h> give correct values */
-	void *data;	/* param buffer (in, or out) */
-};
-
-struct usb_hub_portinfo {
-	unsigned char numports;
-	unsigned char port[127];	/* port to device num mapping */
-};
-
-#define IOCTL_USB_CONTROL	_IOWR('U', 0, struct usb_ctrltransfer)
-#define IOCTL_USB_BULK		_IOWR('U', 2, struct usb_bulktransfer)
-#define IOCTL_USB_RESETEP	_IOR('U', 3, unsigned int)
-#define IOCTL_USB_SETINTF	_IOR('U', 4, struct usb_setinterface)
-#define IOCTL_USB_SETCONFIG	_IOR('U', 5, unsigned int)
-#define IOCTL_USB_GETDRIVER	_IOW('U', 8, struct usb_getdriver)
-#define IOCTL_USB_SUBMITURB	_IOR('U', 10, struct usb_urb)
-#define IOCTL_USB_DISCARDURB	_IO('U', 11)
-#define IOCTL_USB_REAPURB	_IOW('U', 12, void *)
-#define IOCTL_USB_REAPURBNDELAY	_IOW('U', 13, void *)
-#define IOCTL_USB_CLAIMINTF	_IOR('U', 15, unsigned int)
-#define IOCTL_USB_RELEASEINTF	_IOR('U', 16, unsigned int)
-#define IOCTL_USB_CONNECTINFO	_IOW('U', 17, struct usb_connectinfo)
-#define IOCTL_USB_IOCTL         _IOWR('U', 18, struct usb_ioctl)
-#define IOCTL_USB_HUB_PORTINFO	_IOR('U', 19, struct usb_hub_portinfo)
-#define IOCTL_USB_RESET		_IO('U', 20)
-#define IOCTL_USB_CLEAR_HALT	_IOR('U', 21, unsigned int)
-#define IOCTL_USB_DISCONNECT	_IO('U', 22)
-#define IOCTL_USB_CONNECT	_IO('U', 23)
-
-#endif