Package: libusb Version: 2:0.1.12-25 Severity: normal Tags: patch User: ubuntu-de...@lists.ubuntu.com Usertags: origin-ubuntu wily ubuntu-patch
Dear Maintainer, I'm trying to make g15daemon correctly recover after suspend/resume and found that it occasionally hangs after resume in libusb's usb_urb_transfer() (linux.c) on this line: ioctl(dev->fd, IOCTL_USB_REAPURB, &context); After some research I found that linux kernel internally has lists of pending and completed requests. All requests - successful or not - are moved from pending list to completed list, no requests should be lost. But it looks like after suspend/resume some request may be lost (may be a kernel bug). It that case IOCTL_USB_REAPURB would hang forever since request it's waiting for will never appear on completed list. There is simple workaround. After preceding IOCTL_USB_DISCARDURB call our request is guaranteed to be completed, so there is no need to use blocking IOCTL_USB_DISCARDURB, we may use non-blocking IOCTL_USB_REAPURBNDELAY instead. I've attached patch with this workaround. It also fixes race condition existed in original code, which may cause corruption of stack. * Added debian/patches/12_hang_after_resume.diff to fix occasional hang after suspend/resume. (LP: #1455924) Thanks for considering the patch. -- System Information: Debian Release: jessie/sid APT prefers vivid-updates APT policy: (500, 'vivid-updates'), (500, 'vivid-security'), (500, 'vivid'), (100, 'vivid-backports') Architecture: amd64 (x86_64) Foreign Architectures: i386 Kernel: Linux 3.19.0-16-generic (SMP w/4 CPU cores) Locale: LANG=ru_RU.utf8, LC_CTYPE=ru_RU.utf8 (charmap=UTF-8) Shell: /bin/sh linked to /bin/bash Init: systemd (via /run/systemd/system)
=== added directory '.pc/12_hang_after_resume.diff' === added file '.pc/12_hang_after_resume.diff/linux.c' --- .pc/12_hang_after_resume.diff/linux.c 1970-01-01 00:00:00 +0000 +++ .pc/12_hang_after_resume.diff/linux.c 2015-05-17 14:25:02 +0000 @@ -0,0 +1,742 @@ +/* + * Linux USB support + * + * Copyright (c) 2000-2003 Johannes Erdfelt <johan...@erdfelt.com> + * + * This library is covered by the LGPL, read LICENSE for details. + */ + +#include <stdlib.h> /* getenv, etc */ +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/time.h> +#include <dirent.h> + +#include "linux.h" +#include "usbi.h" + +static char usb_path[PATH_MAX + 1] = ""; + +static int device_open(struct usb_device *dev) +{ + char filename[PATH_MAX + 1]; + int fd; + + snprintf(filename, sizeof(filename) - 1, "%s/%s/%s", + usb_path, dev->bus->dirname, dev->filename); + + fd = open(filename, O_RDWR); + if (fd < 0) { + fd = open(filename, O_RDONLY); + if (fd < 0) + USB_ERROR_STR(-errno, "failed to open %s: %s", + filename, strerror(errno)); + } + + return fd; +} + +int usb_os_open(usb_dev_handle *dev) +{ + dev->fd = device_open(dev->device); + + return 0; +} + +int usb_os_close(usb_dev_handle *dev) +{ + if (dev->fd < 0) + return 0; + + if (close(dev->fd) == -1) + /* Failing trying to close a file really isn't an error, so return 0 */ + USB_ERROR_STR(0, "tried to close device fd %d: %s", dev->fd, + strerror(errno)); + + return 0; +} + +int usb_set_configuration(usb_dev_handle *dev, int configuration) +{ + int ret; + + ret = ioctl(dev->fd, IOCTL_USB_SETCONFIG, &configuration); + if (ret < 0) + USB_ERROR_STR(-errno, "could not set config %d: %s", configuration, + strerror(errno)); + + dev->config = configuration; + + return 0; +} + +int usb_claim_interface(usb_dev_handle *dev, int interface) +{ + int ret; + + ret = ioctl(dev->fd, IOCTL_USB_CLAIMINTF, &interface); + if (ret < 0) { + if (errno == EBUSY && usb_debug > 0) + fprintf(stderr, "Check that you have permissions to write to %s/%s and, if you don't, that you set up hotplug (http://linux-hotplug.sourceforge.net/) correctly.\n", dev->bus->dirname, dev->device->filename); + + USB_ERROR_STR(-errno, "could not claim interface %d: %s", interface, + strerror(errno)); + } + + dev->interface = interface; + + return 0; +} + +int usb_release_interface(usb_dev_handle *dev, int interface) +{ + int ret; + + ret = ioctl(dev->fd, IOCTL_USB_RELEASEINTF, &interface); + if (ret < 0) + USB_ERROR_STR(-errno, "could not release intf %d: %s", interface, + strerror(errno)); + + dev->interface = -1; + + return 0; +} + +int usb_set_altinterface(usb_dev_handle *dev, int alternate) +{ + int ret; + struct usb_setinterface setintf; + + if (dev->interface < 0) + USB_ERROR(-EINVAL); + + setintf.interface = dev->interface; + setintf.altsetting = alternate; + + ret = ioctl(dev->fd, IOCTL_USB_SETINTF, &setintf); + if (ret < 0) + USB_ERROR_STR(-errno, "could not set alt intf %d/%d: %s", + dev->interface, alternate, strerror(errno)); + + dev->altsetting = alternate; + + return 0; +} + +/* + * Linux usbfs has a limit of one page size for synchronous bulk read/write. + * 4096 is the most portable maximum we can do for now. + * Linux usbfs has a limit of 16KB for the URB interface. We use this now + * to get better performance for USB 2.0 devices. + */ +#define MAX_READ_WRITE (16 * 1024) + +int usb_control_msg(usb_dev_handle *dev, int requesttype, int request, + int value, int index, char *bytes, int size, int timeout) +{ + struct usb_ctrltransfer ctrl; + int ret; + + ctrl.bRequestType = requesttype; + ctrl.bRequest = request; + ctrl.wValue = value; + ctrl.wIndex = index; + ctrl.wLength = size; + + ctrl.data = bytes; + ctrl.timeout = timeout; + + ret = ioctl(dev->fd, IOCTL_USB_CONTROL, &ctrl); + if (ret < 0) + USB_ERROR_STR(-errno, "error sending control message: %s", strerror(errno)); + + return ret; +} + +#define URB_USERCONTEXT_COOKIE ((void *)0x1) + +/* Reading and writing are the same except for the endpoint */ +static int usb_urb_transfer(usb_dev_handle *dev, int ep, int urbtype, + char *bytes, int size, int timeout) +{ + struct usb_urb urb; + int bytesdone = 0, requested; + struct timeval tv, tv_ref, tv_now; + struct usb_urb *context; + int ret, waiting; + + /* + * HACK: The use of urb.usercontext is a hack to get threaded applications + * sort of working again. Threaded support is still not recommended, but + * this should allow applications to work in the common cases. Basically, + * if we get the completion for an URB we're not waiting for, then we update + * the usercontext pointer to 1 for the other threads URB and it will see + * the change after it wakes up from the the timeout. Ugly, but it works. + */ + + /* + * Get actual time, and add the timeout value. The result is the absolute + * time where we have to quit waiting for an message. + */ + gettimeofday(&tv_ref, NULL); + tv_ref.tv_sec = tv_ref.tv_sec + timeout / 1000; + tv_ref.tv_usec = tv_ref.tv_usec + (timeout % 1000) * 1000; + + if (tv_ref.tv_usec > 1000000) { + tv_ref.tv_usec -= 1000000; + tv_ref.tv_sec++; + } + + waiting = 1; + + do { + fd_set writefds; + + requested = size - bytesdone; + if (requested > MAX_READ_WRITE) + requested = MAX_READ_WRITE; + + urb.type = urbtype; + urb.endpoint = ep; + urb.flags = 0; + urb.buffer = bytes + bytesdone; + urb.buffer_length = requested; + urb.signr = 0; + urb.actual_length = 0; + urb.number_of_packets = 0; /* don't do isochronous yet */ + urb.usercontext = NULL; + + ret = ioctl(dev->fd, IOCTL_USB_SUBMITURB, &urb); + if (ret < 0) { + USB_ERROR_STR(-errno, "error submitting URB: %s", strerror(errno)); + return ret; + } + +restart: + context = NULL; + while (!urb.usercontext && ((ret = ioctl(dev->fd, IOCTL_USB_REAPURBNDELAY, &context)) == -1) && waiting) { + if (ret == -1) + { + if (errno == ENODEV) + { + return -ENODEV; + } + } + tv.tv_sec = 0; + tv.tv_usec = 1000; // 1 msec + FD_ZERO(&writefds); + FD_SET(dev->fd, &writefds); + select(dev->fd + 1, NULL, &writefds, NULL, &tv); //sub second wait + + if (timeout) { + /* compare with actual time, as the select timeout is not that precise */ + gettimeofday(&tv_now, NULL); + + if ((tv_now.tv_sec > tv_ref.tv_sec) || + ((tv_now.tv_sec == tv_ref.tv_sec) && (tv_now.tv_usec >= tv_ref.tv_usec))) + waiting = 0; + } + } + + if (context && context != &urb) { + context->usercontext = URB_USERCONTEXT_COOKIE; + /* We need to restart since we got a successful URB, but not ours */ + waiting = 1; + goto restart; + } + + /* + * If there was an error, that wasn't EAGAIN (no completion), then + * something happened during the reaping and we should return that + * error now + */ + if (ret < 0 && !urb.usercontext && errno != EAGAIN) + USB_ERROR_STR(-errno, "error reaping URB: %s", strerror(errno)); + + bytesdone += urb.actual_length; + } while ((ret == 0 || urb.usercontext) && bytesdone < size && urb.actual_length == requested && waiting); + + /* If the URB didn't complete in success or error, then let's unlink it */ + if ((ret < 0 && !urb.usercontext) || (!waiting && bytesdone < size)) { + int rc; + + if (!waiting) + rc = -ETIMEDOUT; + else + rc = urb.status; + + ret = ioctl(dev->fd, IOCTL_USB_DISCARDURB, &urb); + if (ret < 0 && errno != EINVAL && usb_debug >= 1) + fprintf(stderr, "error discarding URB: %s", strerror(errno)); + + /* + * When the URB is unlinked, it gets moved to the completed list and + * then we need to reap it or else the next time we call this function, + * we'll get the previous completion and exit early + */ + ioctl(dev->fd, IOCTL_USB_REAPURB, &context); + + return rc; + } + + return bytesdone; +} + +int usb_bulk_write(usb_dev_handle *dev, int ep, const char *bytes, int size, + int timeout) +{ + /* Ensure the endpoint address is correct */ + return usb_urb_transfer(dev, ep, USB_URB_TYPE_BULK, (char *)bytes, size, + timeout); +} + +int usb_bulk_read(usb_dev_handle *dev, int ep, char *bytes, int size, + int timeout) +{ + /* Ensure the endpoint address is correct */ + ep |= USB_ENDPOINT_IN; + return usb_urb_transfer(dev, ep, USB_URB_TYPE_BULK, bytes, size, + timeout); +} + +/* + * FIXME: Packetize large buffers here. 2.4 HCDs (atleast, haven't checked + * 2.5 HCDs yet) don't handle multi-packet Interrupt transfers. So we need + * to lookup the endpoint packet size and packetize appropriately here. + */ +int usb_interrupt_write(usb_dev_handle *dev, int ep, const char *bytes, int size, + int timeout) +{ + /* Ensure the endpoint address is correct */ + return usb_urb_transfer(dev, ep, USB_URB_TYPE_INTERRUPT, (char *)bytes, size, + timeout); +} + +int usb_interrupt_read(usb_dev_handle *dev, int ep, char *bytes, int size, + int timeout) +{ + /* Ensure the endpoint address is correct */ + ep |= USB_ENDPOINT_IN; + return usb_urb_transfer(dev, ep, USB_URB_TYPE_INTERRUPT, bytes, size, + timeout); +} + +int usb_os_find_busses(struct usb_bus **busses) +{ + struct usb_bus *fbus = NULL; + DIR *dir; + struct dirent *entry; + + dir = opendir(usb_path); + if (!dir) + USB_ERROR_STR(-errno, "couldn't opendir(%s): %s", usb_path, + strerror(errno)); + + while ((entry = readdir(dir)) != NULL) { + struct usb_bus *bus; + + /* Skip anything starting with a . */ + if (entry->d_name[0] == '.') + continue; + + if (!strchr("0123456789", entry->d_name[strlen(entry->d_name) - 1])) { + if (usb_debug >= 2) + fprintf(stderr, "usb_os_find_busses: Skipping non bus directory %s\n", + entry->d_name); + continue; + } + + bus = malloc(sizeof(*bus)); + if (!bus) + USB_ERROR(-ENOMEM); + + memset((void *)bus, 0, sizeof(*bus)); + + strncpy(bus->dirname, entry->d_name, sizeof(bus->dirname) - 1); + bus->dirname[sizeof(bus->dirname) - 1] = 0; + bus->location = atoi(bus->dirname); + + LIST_ADD(fbus, bus); + + if (usb_debug >= 2) + fprintf(stderr, "usb_os_find_busses: Found %s\n", bus->dirname); + } + + closedir(dir); + + *busses = fbus; + + return 0; +} + +int usb_os_find_devices(struct usb_bus *bus, struct usb_device **devices) +{ + struct usb_device *fdev = NULL; + DIR *dir; + struct dirent *entry; + char dirpath[PATH_MAX + 1]; + + snprintf(dirpath, PATH_MAX, "%s/%s", usb_path, bus->dirname); + + dir = opendir(dirpath); + if (!dir) + USB_ERROR_STR(-errno, "couldn't opendir(%s): %s", dirpath, + strerror(errno)); + + while ((entry = readdir(dir)) != NULL) { + unsigned char device_desc[DEVICE_DESC_LENGTH]; + char filename[PATH_MAX + 1]; + struct usb_device *dev; + struct usb_connectinfo connectinfo; + int i, fd, ret; + + /* Skip anything starting with a . */ + if (entry->d_name[0] == '.') + continue; + + dev = malloc(sizeof(*dev)); + if (!dev) + USB_ERROR(-ENOMEM); + + memset((void *)dev, 0, sizeof(*dev)); + + dev->bus = bus; + + strncpy(dev->filename, entry->d_name, sizeof(dev->filename) - 1); + dev->filename[sizeof(dev->filename) - 1] = 0; + + snprintf(filename, sizeof(filename) - 1, "%s/%s", dirpath, entry->d_name); + fd = open(filename, O_RDWR); + if (fd < 0) { + fd = open(filename, O_RDONLY); + if (fd < 0) { + if (usb_debug >= 2) + fprintf(stderr, "usb_os_find_devices: Couldn't open %s\n", + filename); + + free(dev); + continue; + } + } + + /* Get the device number */ + ret = ioctl(fd, IOCTL_USB_CONNECTINFO, &connectinfo); + if (ret < 0) { + if (usb_debug) + fprintf(stderr, "usb_os_find_devices: couldn't get connect info\n"); + } else + dev->devnum = connectinfo.devnum; + + ret = read(fd, (void *)device_desc, DEVICE_DESC_LENGTH); + if (ret < 0) { + if (usb_debug) + fprintf(stderr, "usb_os_find_devices: Couldn't read descriptor\n"); + + free(dev); + + goto err; + } + + /* + * Linux kernel converts the words in this descriptor to CPU endian, so + * we use the undocumented W character for usb_parse_descriptor() that + * doesn't convert endianess when parsing the descriptor + */ + usb_parse_descriptor(device_desc, "bbWbbbbWWWbbbb", &dev->descriptor); + + LIST_ADD(fdev, dev); + + if (usb_debug >= 2) + fprintf(stderr, "usb_os_find_devices: Found %s on %s\n", + dev->filename, bus->dirname); + + /* Now try to fetch the rest of the descriptors */ + if (dev->descriptor.bNumConfigurations > USB_MAXCONFIG) + /* Silent since we'll try again later */ + goto err; + + if (dev->descriptor.bNumConfigurations < 1) + /* Silent since we'll try again later */ + goto err; + + dev->config = (struct usb_config_descriptor *)malloc(dev->descriptor.bNumConfigurations * sizeof(struct usb_config_descriptor)); + if (!dev->config) + /* Silent since we'll try again later */ + goto err; + + memset(dev->config, 0, dev->descriptor.bNumConfigurations * + sizeof(struct usb_config_descriptor)); + + for (i = 0; i < dev->descriptor.bNumConfigurations; i++) { + unsigned char buffer[8], *bigbuffer; + struct usb_config_descriptor config; + + /* Get the first 8 bytes so we can figure out what the total length is */ + ret = read(fd, (void *)buffer, 8); + if (ret < 8) { + if (usb_debug >= 1) { + if (ret < 0) + fprintf(stderr, "Unable to get descriptor (%d)\n", ret); + else + fprintf(stderr, "Config descriptor too short (expected %d, got %d)\n", 8, ret); + } + + goto err; + } + + usb_parse_descriptor(buffer, "bbw", &config); + + bigbuffer = malloc(config.wTotalLength); + if (!bigbuffer) { + if (usb_debug >= 1) + fprintf(stderr, "Unable to allocate memory for descriptors\n"); + goto err; + } + + /* Read the rest of the config descriptor */ + memcpy(bigbuffer, buffer, 8); + + ret = read(fd, (void *)(bigbuffer + 8), config.wTotalLength - 8); + if (ret < config.wTotalLength - 8) { + if (usb_debug >= 1) { + if (ret < 0) + fprintf(stderr, "Unable to get descriptor (%d)\n", ret); + else + fprintf(stderr, "Config descriptor too short (expected %d, got %d)\n", config.wTotalLength, ret); + } + + free(bigbuffer); + goto err; + } + + ret = usb_parse_configuration(&dev->config[i], bigbuffer); + if (usb_debug >= 2) { + if (ret > 0) + fprintf(stderr, "Descriptor data still left\n"); + else if (ret < 0) + fprintf(stderr, "Unable to parse descriptors\n"); + } + + free(bigbuffer); + } + +err: + close(fd); + } + + closedir(dir); + + *devices = fdev; + + return 0; +} + +int usb_os_determine_children(struct usb_bus *bus) +{ + struct usb_device *dev, *devices[256]; + struct usb_ioctl command; + int ret, i, i1; + + /* Create a list of devices first */ + memset(devices, 0, sizeof(devices)); + for (dev = bus->devices; dev; dev = dev->next) + if (dev->devnum) + devices[dev->devnum] = dev; + + /* Now fetch the children for each device */ + for (dev = bus->devices; dev; dev = dev->next) { + struct usb_hub_portinfo portinfo; + int fd; + + fd = device_open(dev); + if (fd < 0) + continue; + + /* Query the hub driver for the children of this device */ + if (dev->config && dev->config->interface && dev->config->interface->altsetting) + command.ifno = dev->config->interface->altsetting->bInterfaceNumber; + else + command.ifno = 0; + command.ioctl_code = IOCTL_USB_HUB_PORTINFO; + command.data = &portinfo; + ret = ioctl(fd, IOCTL_USB_IOCTL, &command); + if (ret < 0) { + /* errno == ENOSYS means the device probably wasn't a hub */ + if (errno != ENOSYS && usb_debug > 1) + fprintf(stderr, "error obtaining child information: %s\n", + strerror(errno)); + + close(fd); + continue; + } + + dev->num_children = 0; + for (i = 0; i < portinfo.numports; i++) + if (portinfo.port[i]) + dev->num_children++; + + /* Free any old children first */ + free(dev->children); + + dev->children = malloc(sizeof(struct usb_device *) * dev->num_children); + if (!dev->children) { + if (usb_debug > 1) + fprintf(stderr, "error allocating %zu bytes memory for dev->children\n", + sizeof(struct usb_device *) * dev->num_children); + + dev->num_children = 0; + close(fd); + continue; + } + + for (i = 0, i1 = 0; i < portinfo.numports; i++) { + if (!portinfo.port[i]) + continue; + + dev->children[i1++] = devices[portinfo.port[i]]; + + devices[portinfo.port[i]] = NULL; + } + + close(fd); + } + + /* + * There should be one device left in the devices list and that should be + * the root device + */ + for (i = 0; i < sizeof(devices) / sizeof(devices[0]); i++) { + if (devices[i]) + bus->root_dev = devices[i]; + } + + return 0; +} + +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) { + /* Skip anything starting with a . */ + 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; +} + +void usb_os_init(void) +{ + /* Find the path to the virtual filesystem */ + if (getenv("USB_DEVFS_PATH")) { + if (check_usb_vfs(getenv("USB_DEVFS_PATH"))) { + strncpy(usb_path, getenv("USB_DEVFS_PATH"), sizeof(usb_path) - 1); + usb_path[sizeof(usb_path) - 1] = 0; + } else if (usb_debug) + fprintf(stderr, "usb_os_init: couldn't find USB VFS in USB_DEVFS_PATH\n"); + } + + if (!usb_path[0]) { + if (check_usb_vfs("/dev/bus/usb")) { + strncpy(usb_path, "/dev/bus/usb", sizeof(usb_path) - 1); + usb_path[sizeof(usb_path) - 1] = 0; + } else if (check_usb_vfs("/proc/bus/usb")) { + strncpy(usb_path, "/proc/bus/usb", sizeof(usb_path) - 1); + usb_path[sizeof(usb_path) - 1] = 0; + } else + usb_path[0] = 0; /* No path, no USB support */ + } + + if (usb_debug) { + if (usb_path[0]) + fprintf(stderr, "usb_os_init: Found USB VFS at %s\n", usb_path); + else + fprintf(stderr, "usb_os_init: No USB VFS found, is it mounted?\n"); + } +} + +int usb_resetep(usb_dev_handle *dev, unsigned int ep) +{ + int ret; + + ret = ioctl(dev->fd, IOCTL_USB_RESETEP, &ep); + if (ret) + USB_ERROR_STR(-errno, "could not reset ep %d: %s", ep, + strerror(errno)); + + return 0; +} + +int usb_clear_halt(usb_dev_handle *dev, unsigned int ep) +{ + int ret; + + ret = ioctl(dev->fd, IOCTL_USB_CLEAR_HALT, &ep); + if (ret) + USB_ERROR_STR(-errno, "could not clear/halt ep %d: %s", ep, + strerror(errno)); + + return 0; +} + +int usb_reset(usb_dev_handle *dev) +{ + int ret; + + ret = ioctl(dev->fd, IOCTL_USB_RESET, NULL); + if (ret) + USB_ERROR_STR(-errno, "could not reset: %s", strerror(errno)); + + return 0; +} + +int usb_get_driver_np(usb_dev_handle *dev, int interface, char *name, + unsigned int namelen) +{ + struct usb_getdriver getdrv; + int ret; + + getdrv.interface = interface; + ret = ioctl(dev->fd, IOCTL_USB_GETDRIVER, &getdrv); + if (ret) + USB_ERROR_STR(-errno, "could not get bound driver: %s", strerror(errno)); + + strncpy(name, getdrv.driver, namelen - 1); + name[namelen - 1] = 0; + + return 0; +} + +int usb_detach_kernel_driver_np(usb_dev_handle *dev, int interface) +{ + struct usb_ioctl command; + int ret; + + command.ifno = interface; + command.ioctl_code = IOCTL_USB_DISCONNECT; + command.data = NULL; + + ret = ioctl(dev->fd, IOCTL_USB_IOCTL, &command); + if (ret) + USB_ERROR_STR(-errno, "could not detach kernel driver from interface %d: %s", + interface, strerror(errno)); + + return 0; +} + === modified file '.pc/applied-patches' --- .pc/applied-patches 2014-07-03 20:17:03 +0000 +++ .pc/applied-patches 2015-05-17 16:46:05 +0000 @@ -10,4 +10,5 @@ 09_dummy.diff 10_hurd.diff 11_transfer_timeout.diff +12_hang_after_resume.diff 91_ac_prog_cxx.diff === modified file 'debian/changelog' === added file 'debian/patches/12_hang_after_resume.diff' --- debian/patches/12_hang_after_resume.diff 1970-01-01 00:00:00 +0000 +++ debian/patches/12_hang_after_resume.diff 2015-05-17 16:45:30 +0000 @@ -0,0 +1,47 @@ +Description: Fix occasional hang after suspend/resume. + After suspend/resume linux kernel may loose our requests, and + usb_urb_transfer() will wait forever for lost requests to complete. With this + patch lost request are properly handled. + Also fixes race condition leading to stack corruption. +Author: Alexander Ponyatykh <lazyra...@gmail.com> +Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/libusb/+bug/1455924 +Forwarded: no +Last-Update: 2015-05-17 +=================================================================== +--- a/linux.c ++++ b/linux.c +@@ -166,7 +166,7 @@ + int bytesdone = 0, requested; + struct timeval tv, tv_ref, tv_now; + struct usb_urb *context; +- int ret, waiting; ++ int ret, waiting, our_urb_dequeued; + + /* + * HACK: The use of urb.usercontext is a hack to get threaded applications +@@ -277,7 +277,24 @@ + * then we need to reap it or else the next time we call this function, + * we'll get the previous completion and exit early + */ +- ioctl(dev->fd, IOCTL_USB_REAPURB, &context); ++ our_urb_dequeued = 0; ++ do { ++ context = NULL; ++ ret = ioctl(dev->fd, IOCTL_USB_REAPURBNDELAY, &context); ++ if (context) { ++ if (context == &urb) { ++ our_urb_dequeued = 1; ++ } else { ++ /* We need to retry since we got completed URB, but not ours. ++ * Otherwise other thread may write to our already deallocated ++ * urb.usercontext */ ++ context->usercontext = URB_USERCONTEXT_COOKIE; ++ } ++ } ++ } while (context && !our_urb_dequeued); ++ ++ if (usb_debug > 0 && !our_urb_dequeued && !urb.usercontext) ++ fprintf(stderr, "usb_urb_transfer: kernel may have lost our URB! (or other thread dequeued it)\n"); + + return rc; + } === modified file 'debian/patches/series' --- debian/patches/series 2014-07-03 20:17:03 +0000 +++ debian/patches/series 2015-05-17 16:40:43 +0000 @@ -10,4 +10,5 @@ 09_dummy.diff 10_hurd.diff 11_transfer_timeout.diff +12_hang_after_resume.diff 91_ac_prog_cxx.diff === modified file 'linux.c' --- linux.c 2014-07-03 20:17:03 +0000 +++ linux.c 2015-05-17 14:25:02 +0000 @@ -166,7 +166,7 @@ int bytesdone = 0, requested; struct timeval tv, tv_ref, tv_now; struct usb_urb *context; - int ret, waiting; + int ret, waiting, our_urb_dequeued; /* * HACK: The use of urb.usercontext is a hack to get threaded applications @@ -277,7 +277,24 @@ * then we need to reap it or else the next time we call this function, * we'll get the previous completion and exit early */ - ioctl(dev->fd, IOCTL_USB_REAPURB, &context); + our_urb_dequeued = 0; + do { + context = NULL; + ret = ioctl(dev->fd, IOCTL_USB_REAPURBNDELAY, &context); + if (context) { + if (context == &urb) { + our_urb_dequeued = 1; + } else { + /* We need to retry since we got completed URB, but not ours. + * Otherwise other thread may write to our already deallocated + * urb.usercontext */ + context->usercontext = URB_USERCONTEXT_COOKIE; + } + } + } while (context && !our_urb_dequeued); + + if (usb_debug > 0 && !our_urb_dequeued && !urb.usercontext) + fprintf(stderr, "usb_urb_transfer: kernel may have lost our URB! (or other thread dequeued it)\n"); return rc; }