/*
 * LIBUSB interface code for CUPS.
 *
 * Copyright © 2021 by OpenPrinting.
 * Copyright © 2007-2019 by Apple Inc.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more
 * information.
 */

/*
 * Include necessary headers...
 */

#include <libusb.h>
#include <cups/cups-private.h>
#include <cups/ppd-private.h>
#include <cups/dir.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>


/*
 * WAIT_EOF_DELAY is number of seconds we'll wait for responses from
 * the printer after we've finished sending all the data
 */

#define WAIT_EOF			0
#define WAIT_EOF_DELAY			7
#define WAIT_SIDE_DELAY			3
#define DEFAULT_TIMEOUT			5000L


/*
 * Local types...
 */

typedef struct usb_printer_s		/**** USB Printer Data ****/
{
  struct libusb_device	*device;	/* Device info */
  int			conf,		/* Configuration */
			origconf,	/* Original configuration */
			iface,		/* Interface */
			altset,		/* Alternate setting */
			write_endp,	/* Write endpoint */
			read_endp,	/* Read endpoint */
			protocol,	/* Protocol: 1 = Uni-di, 2 = Bi-di. */
			usblp_attached,	/* "usblp" kernel module attached? */
			reset_after_job;/* Set to 1 by print_device() */
  unsigned		quirks;		/* Quirks flags */
  struct libusb_device_handle *handle;	/* Open handle to device */
} usb_printer_t;

typedef int (*usb_cb_t)(usb_printer_t *, const char *, const char *,
                        const void *);

typedef struct usb_globals_s		/* Global USB printer information */
{
  usb_printer_t		*printer;	/* Printer */

  pthread_mutex_t	read_thread_mutex;
  pthread_cond_t	read_thread_cond;
  int			read_thread_stop;
  int			read_thread_done;

  pthread_mutex_t	readwrite_lock_mutex;
  pthread_cond_t	readwrite_lock_cond;
  int			readwrite_lock;

  int			print_fd;	/* File descriptor to print */
  ssize_t		print_bytes;	/* Print bytes read */

  int			wait_eof;
  int			drain_output;	/* Drain all pending output */
  int			bidi_flag;	/* 0=unidirectional, 1=bidirectional */

  pthread_mutex_t	sidechannel_thread_mutex;
  pthread_cond_t	sidechannel_thread_cond;
  int			sidechannel_thread_stop;
  int			sidechannel_thread_done;
} usb_globals_t;

/*
 * Quirks: various printer quirks are handled by this structure and its flags.
 *
 * The quirks table used to be compiled into the backend but is now loaded from
 * one or more files in the /usr/share/cups/usb directory.
 */

#define USB_QUIRK_BLACKLIST	0x0001	/* Does not conform to the spec */
#define USB_QUIRK_NO_REATTACH	0x0002	/* After printing we cannot re-attach
					   the usblp kernel module */
#define USB_QUIRK_SOFT_RESET	0x0004	/* After printing do a soft reset
					   for clean-up */
#define USB_QUIRK_UNIDIR	0x0008	/* Requires unidirectional mode */
#define USB_QUIRK_USB_INIT	0x0010	/* Needs vendor USB init string */
#define USB_QUIRK_VENDOR_CLASS	0x0020	/* Descriptor uses vendor-specific
					   Class or SubClass */
#define USB_QUIRK_DELAY_CLOSE	0x0040	/* Delay close */
#define USB_QUIRK_WHITELIST	0x0000	/* no quirks */


typedef struct usb_quirk_s		/* USB "quirk" information */
{
  int		vendor_id,		/* Affected vendor ID */
		product_id;		/* Affected product ID or 0 for all */
  unsigned	quirks;			/* Quirks bitfield */
} usb_quirk_t;




/*
 * Globals...
 */

cups_array_t		*all_quirks;	/* Array of printer quirks */
usb_globals_t		g = { 0 };	/* Globals */
libusb_device		**all_list;	/* List of connected USB devices */


/*
 * Local functions...
 */

static int		close_device(usb_printer_t *printer);
static int		compare_quirks(usb_quirk_t *a, usb_quirk_t *b);
static usb_printer_t	*find_device(usb_cb_t cb, const void *data);
static unsigned		find_quirks(int vendor_id, int product_id);
static int		get_device_id(usb_printer_t *printer, char *buffer,
			              size_t bufsize);
static int		list_cb(usb_printer_t *printer, const char *device_uri,
			        const char *device_id, const void *data);
static void		load_quirks(void);
static char		*make_device_uri(usb_printer_t *printer,
			                 const char *device_id,
					 char *uri, size_t uri_size);
static int		open_device(usb_printer_t *printer, int verbose);
static int		print_cb(usb_printer_t *printer, const char *device_uri,
			         const char *device_id, const void *data);
static void		*read_thread(void *reference);
static void		*sidechannel_thread(void *reference);
static void		soft_reset(void);
static int		soft_reset_printer(usb_printer_t *printer);


/*
 * 'list_devices()' - List the available printers.
 */

void
list_devices(void)
{
  load_quirks();

  fputs("DEBUG: list_devices\n", stderr);
  find_device(list_cb, NULL);
}


/*
 * 'print_device()' - Print a file to a USB device.
 */

int					/* O - Exit status */
print_device(const char *uri,		/* I - Device URI */
             const char *hostname,	/* I - Hostname/manufacturer */
             const char *resource,	/* I - Resource/modelname */
	     char       *options,	/* I - Device options/serial number */
	     int        print_fd,	/* I - File descriptor to print */
	     int        copies,		/* I - Copies to print */
	     int	argc,		/* I - Number of command-line arguments (6 or 7) */
	     char	*argv[])	/* I - Command-line arguments */
{
  int	        bytes;			/* Bytes written */
  ssize_t	total_bytes;		/* Total bytes written */
  struct sigaction action;		/* Actions for POSIX signals */
  int		status = CUPS_BACKEND_OK,
					/* Function results */
		iostatus;		/* Current IO status */
  pthread_t	read_thread_id,		/* Read thread */
		sidechannel_thread_id;	/* Side-channel thread */
  int		have_sidechannel = 0,	/* Was the side-channel thread started? */
		have_backchannel = 0;   /* Do we have a back channel? */
  struct stat   sidechannel_info;	/* Side-channel file descriptor info */
  unsigned char	print_buffer[8192],	/* Print data buffer */
		*print_ptr;		/* Pointer into print data buffer */
  fd_set	input_set;		/* Input set for select() */
  int		nfds;			/* Number of file descriptors */
  struct timeval *timeout,		/* Timeout pointer */
		tv;			/* Time value */
  struct timespec cond_timeout;		/* pthread condition timeout */
  int		num_opts;		/* Number of options */
  cups_option_t	*opts;			/* Options */
  const char	*val;			/* Option value */


  load_quirks();

 /*
  * See if the side-channel descriptor is valid...
  */

  have_sidechannel = !fstat(CUPS_SC_FD, &sidechannel_info) &&
                     S_ISSOCK(sidechannel_info.st_mode);

  g.wait_eof = WAIT_EOF;

 /*
  * Connect to the printer...
  */

  fprintf(stderr, "DEBUG: Printing on printer with URI: %s\n", uri);
  while ((g.printer = find_device(print_cb, uri)) == NULL)
  {
    _cupsLangPrintFilter(stderr, "INFO",
			 _("Waiting for printer to become available."));
    sleep(5);
  }

  g.print_fd = print_fd;

 /*
  * Some devices need a reset after finishing a job, these devices are
  * marked with the USB_QUIRK_SOFT_RESET quirk.
  */
  g.printer->reset_after_job = (g.printer->quirks & USB_QUIRK_SOFT_RESET ? 1 : 0);

 /*
  * If we are printing data from a print driver on stdin, ignore SIGTERM
  * so that the driver can finish out any page data, e.g. to eject the
  * current page.  We only do this for stdin printing as otherwise there
  * is no way to cancel a raw print job...
  */

  if (!print_fd)
  {
    memset(&action, 0, sizeof(action));

    sigemptyset(&action.sa_mask);
    action.sa_handler = SIG_IGN;
    sigaction(SIGTERM, &action, NULL);
  }

 /*
  * Start the side channel thread if the descriptor is valid...
  */

  pthread_mutex_init(&g.readwrite_lock_mutex, NULL);
  pthread_cond_init(&g.readwrite_lock_cond, NULL);
  g.readwrite_lock = 1;

  if (have_sidechannel)
  {
    g.sidechannel_thread_stop = 0;
    g.sidechannel_thread_done = 0;

    pthread_cond_init(&g.sidechannel_thread_cond, NULL);
    pthread_mutex_init(&g.sidechannel_thread_mutex, NULL);

    if (pthread_create(&sidechannel_thread_id, NULL, sidechannel_thread, NULL))
    {
      fprintf(stderr, "DEBUG: Fatal USB error.\n");
      _cupsLangPrintFilter(stderr, "ERROR",
			   _("There was an unrecoverable USB error."));
      fputs("DEBUG: Couldn't create side-channel thread.\n", stderr);
      close_device(g.printer);
      return (CUPS_BACKEND_STOP);
    }
  }

 /*
  * Debug mode: If option "usb-unidir" is given, always deactivate
  * backchannel
  */

  num_opts = cupsParseOptions(argv[5], 0, &opts);
  val = cupsGetOption("usb-unidir", num_opts, opts);
  if (val && strcasecmp(val, "no") && strcasecmp(val, "off") &&
      strcasecmp(val, "false"))
  {
    g.printer->read_endp = -1;
    fprintf(stderr, "DEBUG: Forced uni-directional communication "
	    "via \"usb-unidir\" option.\n");
  }

 /*
  * Debug mode: If option "usb-no-reattach" is given, do not re-attach
  * the usblp kernel module after the job has completed.
  */

  val = cupsGetOption("usb-no-reattach", num_opts, opts);
  if (val && strcasecmp(val, "no") && strcasecmp(val, "off") &&
      strcasecmp(val, "false"))
  {
    g.printer->usblp_attached = 0;
    fprintf(stderr, "DEBUG: Forced not re-attaching the usblp kernel module "
	    "after the job via \"usb-no-reattach\" option.\n");
  }

 /*
  * Get the read thread going...
  */

  if (g.printer->read_endp != -1)
  {
    have_backchannel = 1;

    g.read_thread_stop = 0;
    g.read_thread_done = 0;

    pthread_cond_init(&g.read_thread_cond, NULL);
    pthread_mutex_init(&g.read_thread_mutex, NULL);

    if (pthread_create(&read_thread_id, NULL, read_thread, NULL))
    {
      fprintf(stderr, "DEBUG: Fatal USB error.\n");
      _cupsLangPrintFilter(stderr, "ERROR",
			   _("There was an unrecoverable USB error."));
      fputs("DEBUG: Couldn't create read thread.\n", stderr);
      close_device(g.printer);
      return (CUPS_BACKEND_STOP);
    }
  }
  else
    fprintf(stderr, "DEBUG: Uni-directional device/mode, back channel "
	    "deactivated.\n");

 /*
  * The main thread sends the print file...
  */

  g.drain_output = 0;
  g.print_bytes	 = 0;
  total_bytes	 = 0;
  print_ptr	 = print_buffer;

  while (status == CUPS_BACKEND_OK && copies-- > 0)
  {
    _cupsLangPrintFilter(stderr, "INFO", _("Sending data to printer."));

    if (print_fd != STDIN_FILENO)
    {
      fputs("PAGE: 1 1\n", stderr);
      lseek(print_fd, 0, SEEK_SET);
    }

    while (status == CUPS_BACKEND_OK)
    {
      FD_ZERO(&input_set);

      if (!g.print_bytes)
	FD_SET(print_fd, &input_set);

     /*
      * Calculate select timeout...
      *   If we have data waiting to send timeout is 100ms.
      *   else if we're draining print_fd timeout is 0.
      *   else we're waiting forever...
      */

      if (g.print_bytes)
      {
	tv.tv_sec  = 0;
	tv.tv_usec = 100000;		/* 100ms */
	timeout    = &tv;
      }
      else if (g.drain_output)
      {
	tv.tv_sec  = 0;
	tv.tv_usec = 0;
	timeout    = &tv;
      }
      else
	timeout = NULL;

     /*
      * I/O is unlocked around select...
      */

      pthread_mutex_lock(&g.readwrite_lock_mutex);
      g.readwrite_lock = 0;
      pthread_cond_signal(&g.readwrite_lock_cond);
      pthread_mutex_unlock(&g.readwrite_lock_mutex);

      nfds = select(print_fd + 1, &input_set, NULL, NULL, timeout);

     /*
      * Reacquire the lock...
      */

      pthread_mutex_lock(&g.readwrite_lock_mutex);
      while (g.readwrite_lock)
	pthread_cond_wait(&g.readwrite_lock_cond, &g.readwrite_lock_mutex);
      g.readwrite_lock = 1;
      pthread_mutex_unlock(&g.readwrite_lock_mutex);

      if (nfds < 0)
      {
	if (errno == EINTR && total_bytes == 0)
	{
	  fputs("DEBUG: Received an interrupt before any bytes were "
	        "written, aborting.\n", stderr);
	  close_device(g.printer);
          return (CUPS_BACKEND_OK);
	}
	else if (errno != EAGAIN && errno != EINTR)
	{
	  _cupsLangPrintFilter(stderr, "ERROR",
	                       _("Unable to read print data."));
	  perror("DEBUG: select");
	  close_device(g.printer);
          return (CUPS_BACKEND_FAILED);
	}
      }

     /*
      * If drain output has finished send a response...
      */

      if (g.drain_output && !nfds && !g.print_bytes)
      {
	/* Send a response... */
	cupsSideChannelWrite(CUPS_SC_CMD_DRAIN_OUTPUT, CUPS_SC_STATUS_OK, NULL, 0, 1.0);
	g.drain_output = 0;
      }

     /*
      * Check if we have print data ready...
      */

      if (FD_ISSET(print_fd, &input_set))
      {
	g.print_bytes = read(print_fd, print_buffer, sizeof(print_buffer));

	if (g.print_bytes < 0)
	{
	 /*
	  * Read error - bail if we don't see EAGAIN or EINTR...
	  */

	  if (errno != EAGAIN && errno != EINTR)
	  {
	    _cupsLangPrintFilter(stderr, "ERROR",
				 _("Unable to read print data."));
	    perror("DEBUG: read");
	    close_device(g.printer);
	    return (CUPS_BACKEND_FAILED);
	  }

	  g.print_bytes = 0;
	}
	else if (g.print_bytes == 0)
	{
	 /*
	  * End of file, break out of the loop...
	  */

	  break;
	}

	print_ptr = print_buffer;

	fprintf(stderr, "DEBUG: Read %d bytes of print data...\n",
		(int)g.print_bytes);
      }

      if (g.print_bytes)
      {
	iostatus = libusb_bulk_transfer(g.printer->handle,
					g.printer->write_endp,
					print_buffer, g.print_bytes,
					&bytes, 0);
       /*
	* Ignore timeout errors, but retain the number of bytes written to
	* avoid sending duplicate data...
	*/

	if (iostatus == LIBUSB_ERROR_TIMEOUT)
	{
	  fputs("DEBUG: Got USB transaction timeout during write.\n", stderr);
	  iostatus = 0;
	}

       /*
        * If we've stalled, retry the write...
	*/

	else if (iostatus == LIBUSB_ERROR_PIPE)
	{
	  fputs("DEBUG: Got USB pipe stalled during write.\n", stderr);

	  iostatus = libusb_bulk_transfer(g.printer->handle,
					  g.printer->write_endp,
					  print_buffer, g.print_bytes,
					  &bytes, 0);
	}

       /*
	* Retry a write after an aborted write since we probably just got
	* SIGTERM...
	*/

	else if (iostatus == LIBUSB_ERROR_INTERRUPTED)
	{
	  fputs("DEBUG: Got USB return aborted during write.\n", stderr);

	  iostatus = libusb_bulk_transfer(g.printer->handle,
					  g.printer->write_endp,
					  print_buffer, g.print_bytes,
					  &bytes, 0);
        }

	if (iostatus)
	{
	 /*
	  * Write error - bail if we don't see an error we can retry...
	  */

	  _cupsLangPrintFilter(stderr, "ERROR",
	                       _("Unable to send data to printer."));
	  fprintf(stderr, "DEBUG: libusb write operation returned %x.\n",
	          iostatus);

	  status = CUPS_BACKEND_FAILED;
	  break;
	}
	else if (bytes > 0)
	{
	  fprintf(stderr, "DEBUG: Wrote %d bytes of print data...\n",
	          (int)bytes);

	  g.print_bytes -= bytes;
	  print_ptr   += bytes;
	  total_bytes += bytes;
	}
      }

      if (print_fd != 0 && status == CUPS_BACKEND_OK)
	fprintf(stderr, "DEBUG: Sending print file, " CUPS_LLFMT " bytes...\n",
		CUPS_LLCAST total_bytes);
    }
  }

  fprintf(stderr, "DEBUG: Sent " CUPS_LLFMT " bytes...\n",
          CUPS_LLCAST total_bytes);

 /*
  * Signal the side channel thread to exit...
  */

  if (have_sidechannel)
  {
    close(CUPS_SC_FD);
    pthread_mutex_lock(&g.readwrite_lock_mutex);
    g.readwrite_lock = 0;
    pthread_cond_signal(&g.readwrite_lock_cond);
    pthread_mutex_unlock(&g.readwrite_lock_mutex);

    g.sidechannel_thread_stop = 1;
    pthread_mutex_lock(&g.sidechannel_thread_mutex);

    if (!g.sidechannel_thread_done)
    {
      gettimeofday(&tv, NULL);
      cond_timeout.tv_sec  = tv.tv_sec + WAIT_SIDE_DELAY;
      cond_timeout.tv_nsec = tv.tv_usec * 1000;

      while (!g.sidechannel_thread_done)
      {
	if (pthread_cond_timedwait(&g.sidechannel_thread_cond,
				   &g.sidechannel_thread_mutex,
				   &cond_timeout) != 0)
	  break;
      }
    }

    pthread_mutex_unlock(&g.sidechannel_thread_mutex);
  }

 /*
  * Signal the read thread to exit then wait 7 seconds for it to complete...
  */

  if (have_backchannel)
  {
    g.read_thread_stop = 1;

    pthread_mutex_lock(&g.read_thread_mutex);

    if (!g.read_thread_done)
    {
      fputs("DEBUG: Waiting for read thread to exit...\n", stderr);

      gettimeofday(&tv, NULL);
      cond_timeout.tv_sec  = tv.tv_sec + WAIT_EOF_DELAY;
      cond_timeout.tv_nsec = tv.tv_usec * 1000;

      while (!g.read_thread_done)
      {
	if (pthread_cond_timedwait(&g.read_thread_cond, &g.read_thread_mutex,
				   &cond_timeout) != 0)
	  break;
      }

      /*
       * If it didn't exit abort the pending read and wait an additional
       * second...
       */

      if (!g.read_thread_done)
      {
	fputs("DEBUG: Read thread still active, aborting the pending read...\n",
	      stderr);

	g.wait_eof = 0;

	gettimeofday(&tv, NULL);
	cond_timeout.tv_sec  = tv.tv_sec + 1;
	cond_timeout.tv_nsec = tv.tv_usec * 1000;

	while (!g.read_thread_done)
	{
	  if (pthread_cond_timedwait(&g.read_thread_cond, &g.read_thread_mutex,
				     &cond_timeout) != 0)
	    break;
	}
      }
    }

    pthread_mutex_unlock(&g.read_thread_mutex);
  }

 /*
  * Close the connection and input file and general clean up...
  */

  if (g.printer->quirks & USB_QUIRK_DELAY_CLOSE)
    sleep(1);

  close_device(g.printer);

 /*
  * Clean up ....
  */

  libusb_free_device_list(all_list, 1);
  libusb_exit(NULL);

  return (status);
}


/*
 * 'close_device()' - Close the connection to the USB printer.
 */

static int				/* I - 0 on success, -1 on failure */
close_device(usb_printer_t *printer)	/* I - Printer */
{
  struct libusb_device_descriptor devdesc;
                                        /* Current device descriptor */
  struct libusb_config_descriptor *confptr;
                                        /* Pointer to current configuration */


  if (printer->handle)
  {
   /*
    * Release interfaces before closing so that we know all data is written
    * to the device...
    */

    int errcode;			/* Return value of libusb function */
    int number1,			/* Interface number */
	number2;			/* Configuration number */

    errcode =
      libusb_get_config_descriptor(printer->device, printer->conf, &confptr);
    if (errcode >= 0)
    {
      number1 = confptr->interface[printer->iface].
	altsetting[printer->altset].bInterfaceNumber;
      libusb_release_interface(printer->handle, number1);

      number2 = confptr->bConfigurationValue;

      libusb_free_config_descriptor(confptr);

     /*
      * If we have changed the configuration from one valid configuration
      * to another, restore the old one
      */
      if (printer->origconf > 0 && printer->origconf != number2)
      {
	fprintf(stderr, "DEBUG: Restoring USB device configuration: %d -> %d\n",
		number2, printer->origconf);
	if ((errcode = libusb_set_configuration(printer->handle,
						printer->origconf)) < 0)
	{
	  if (errcode != LIBUSB_ERROR_BUSY)
	  {
	    errcode =
	      libusb_get_device_descriptor (printer->device, &devdesc);
	    if (errcode < 0)
	      fprintf(stderr,
		      "DEBUG: Failed to set configuration %d\n",
		      printer->origconf);
	    else
	      fprintf(stderr,
		      "DEBUG: Failed to set configuration %d for %04x:%04x\n",
		      printer->origconf, devdesc.idVendor, devdesc.idProduct);
	  }
	}
      }

     /*
      * Re-attach "usblp" kernel module if it was attached before using this
      * device
      */
      if (printer->usblp_attached == 1)
	if (libusb_attach_kernel_driver(printer->handle, number1) < 0)
	{
	  errcode = libusb_get_device_descriptor (printer->device, &devdesc);
	  if (errcode < 0)
	    fprintf(stderr,
		    "DEBUG: Failed to re-attach \"usblp\" kernel module\n");
	  else
	    fprintf(stderr,
		    "DEBUG: Failed to re-attach \"usblp\" kernel module to "
		    "%04x:%04x\n", devdesc.idVendor, devdesc.idProduct);
	}
    }
    else
      fprintf(stderr,
	      "DEBUG: Failed to get configuration descriptor %d\n",
	      printer->conf);

   /*
    * Reset the device to clean up after the job
    */

    if (printer->reset_after_job == 1)
    {
      if ((errcode = libusb_reset_device(printer->handle)) < 0)
	fprintf(stderr,
		"DEBUG: Device reset failed, error code: %d\n",
		errcode);
      else
	fprintf(stderr,
		"DEBUG: Resetting printer.\n");
    }

   /*
    * Close the interface and return...
    */

    libusb_close(printer->handle);
    printer->handle = NULL;
  }

  return (0);
}


/*
 * 'compare_quirks()' - Compare two quirks entries.
 */

static int				/* O - Result of comparison */
compare_quirks(usb_quirk_t *a,		/* I - First quirk entry */
               usb_quirk_t *b)		/* I - Second quirk entry */
{
  int result;				/* Result of comparison */

  if ((result = b->vendor_id - a->vendor_id) == 0)
    result = b->product_id - a->product_id;

  return (result);
}


/*
 * 'find_device()' - Find or enumerate USB printers.
 */

static usb_printer_t *			/* O - Found printer */
find_device(usb_cb_t   cb,		/* I - Callback function */
            const void *data)		/* I - User data for callback */
{
  libusb_device         **list;         /* List of connected USB devices */
  libusb_device         *device = NULL;	/* Current device */
  struct libusb_device_descriptor devdesc;
                                        /* Current device descriptor */
  struct libusb_config_descriptor *confptr = NULL;
                                        /* Pointer to current configuration */
  const struct libusb_interface *ifaceptr = NULL;
                                        /* Pointer to current interface */
  const struct libusb_interface_descriptor *altptr = NULL;
					/* Pointer to current alternate setting */
  const struct libusb_endpoint_descriptor *endpptr = NULL;
					/* Pointer to current endpoint */
  ssize_t               err = 0,	/* Error code */
                        numdevs,        /* number of connected devices */
                        i = 0;
  uint8_t		conf,		/* Current configuration */
			iface,		/* Current interface */
			altset,		/* Current alternate setting */
			protocol,	/* Current protocol */
			endp,		/* Current endpoint */
			read_endp,	/* Current read endpoint */
			write_endp;	/* Current write endpoint */
  char			device_id[1024],/* IEEE-1284 device ID */
			device_uri[1024];
					/* Device URI */
  static usb_printer_t	printer;	/* Current printer */


 /*
  * Initialize libusb...
  */

  err = libusb_init(NULL);
  if (err)
  {
    fprintf(stderr, "ERROR: Unable to initialize USB access via libusb, libusb error %i (%s)\n", (int)err, libusb_strerror((int)err));
    return (NULL);
  }

  numdevs = libusb_get_device_list(NULL, &list);
  fprintf(stderr, "DEBUG: libusb_get_device_list=%d\n", (int)numdevs);

 /*
  * Then loop through the devices it found...
  */

  if (numdevs > 0)
    for (i = 0; i < numdevs; i++)
    {
      device = list[i];

     /*
      * Ignore devices with no configuration data and anything that is not
      * a printer...
      */

      if (libusb_get_device_descriptor(device, &devdesc) < 0)
	continue;

      if (!devdesc.bNumConfigurations || !devdesc.idVendor ||
          !devdesc.idProduct)
	continue;

      printer.quirks = find_quirks(devdesc.idVendor, devdesc.idProduct);

     /*
      * Ignore blacklisted printers...
      */

      if (printer.quirks & USB_QUIRK_BLACKLIST)
        continue;

      for (conf = 0; conf < devdesc.bNumConfigurations; conf ++)
      {
	if (libusb_get_config_descriptor(device, conf, &confptr) < 0)
	  continue;
        for (iface = 0, ifaceptr = confptr->interface;
	     iface < confptr->bNumInterfaces;
	     iface ++, ifaceptr ++)
        {
	 /*
	  * Some printers offer multiple interfaces...
	  */

          protocol   = 0;

	  for (altset = 0, altptr = ifaceptr->altsetting;
	       altset < ifaceptr->num_altsetting; // lgtm [cpp/comparison-with-wider-type]
	       altset ++, altptr ++)
          {
	   /*
	    * Currently we only support unidirectional and bidirectional
	    * printers.  Future versions of this code will support the
	    * 1284.4 (packet mode) protocol as well.
	    */

	    if (((altptr->bInterfaceClass != LIBUSB_CLASS_PRINTER ||
		  altptr->bInterfaceSubClass != 1) &&
		 ((printer.quirks & USB_QUIRK_VENDOR_CLASS) == 0)) ||
		(altptr->bInterfaceProtocol != 1 &&	/* Unidirectional */
		 altptr->bInterfaceProtocol != 2) ||	/* Bidirectional */
		altptr->bInterfaceProtocol < protocol)
	      continue;

	    if (printer.quirks & USB_QUIRK_VENDOR_CLASS)
	      fprintf(stderr, "DEBUG: Printer does not report class 7 and/or "
		      "subclass 1 but works as a printer anyway\n");

	    read_endp  = 0xff;
	    write_endp = 0xff;

	    for (endp = 0, endpptr = altptr->endpoint;
	         endp < altptr->bNumEndpoints;
		 endp ++, endpptr ++)
              if ((endpptr->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) ==
	              LIBUSB_TRANSFER_TYPE_BULK)
	      {
	        if (endpptr->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
		  read_endp = endp;
		else
		  write_endp = endp;
	      }

            if (write_endp != 0xff)
	    {
	     /*
	      * Save the best match so far...
	      */

              protocol           = altptr->bInterfaceProtocol;
	      printer.altset     = altset;
	      printer.write_endp = write_endp;
	      if (protocol > 1)
		printer.read_endp = read_endp;
	      else
		printer.read_endp = -1;
	    }
	  }

	  if (protocol > 0)
	  {
	    printer.device   = device;
	    printer.conf     = conf;
	    printer.iface    = iface;
	    printer.protocol = protocol;
	    printer.handle   = NULL;

            if (!open_device(&printer, data != NULL))
	    {
	      get_device_id(&printer, device_id, sizeof(device_id));
	      make_device_uri(&printer, device_id, device_uri,
			      sizeof(device_uri));

	      fprintf(stderr, "DEBUG2: Printer found with device ID: %s "
		      "Device URI: %s\n",
		      device_id, device_uri);

	      if ((*cb)(&printer, device_uri, device_id, data))
	      {
		fprintf(stderr, "DEBUG: Device protocol: %d\n",
			printer.protocol);
		if (printer.quirks & USB_QUIRK_UNIDIR)
		{
		  printer.read_endp = -1;
		  fprintf(stderr, "DEBUG: Printer reports bi-di support "
			  "but in reality works only uni-directionally\n");
		}
		if (printer.read_endp != -1)
		{
		  printer.read_endp = confptr->interface[printer.iface].
					    altsetting[printer.altset].
					    endpoint[printer.read_endp].
					    bEndpointAddress;
		}
		else
		  fprintf(stderr, "DEBUG: Uni-directional USB communication "
			  "only!\n");
		printer.write_endp = confptr->interface[printer.iface].
					   altsetting[printer.altset].
					   endpoint[printer.write_endp].
					   bEndpointAddress;
		if (printer.quirks & USB_QUIRK_NO_REATTACH)
		{
		  printer.usblp_attached = 0;
		  fprintf(stderr, "DEBUG: Printer does not like usblp "
			  "kernel module to be re-attached after job\n");
		}
		libusb_free_config_descriptor(confptr);
		return (&printer);
              }

              close_device(&printer);
	    }
	  }
	}
	libusb_free_config_descriptor(confptr);
      }
    }

 /*
  * If we get this far without returning, then we haven't found a printer
  * to print to...
  */

 /*
  * Clean up ....
  */

  if (numdevs >= 0)
    libusb_free_device_list(list, 1);
  libusb_exit(NULL);

  return (NULL);
}


/*
 * 'find_quirks()' - Find the quirks for the given printer, if any.
 *
 * First looks for an exact match, then looks for the vendor ID wildcard match.
 */

static unsigned				/* O - Quirks flags */
find_quirks(int vendor_id,		/* I - Vendor ID */
            int product_id)		/* I - Product ID */
{
  usb_quirk_t	key,			/* Search key */
		*match;			/* Matching quirk entry */


  key.vendor_id  = vendor_id;
  key.product_id = product_id;

  if ((match = cupsArrayFind(all_quirks, &key)) != NULL)
    return (match->quirks);

  key.product_id = 0;

  if ((match = cupsArrayFind(all_quirks, &key)) != NULL)
    return (match->quirks);

  return (USB_QUIRK_WHITELIST);
}


/*
 * 'get_device_id()' - Get the IEEE-1284 device ID for the printer.
 */

static int				/* O - 0 on success, -1 on error */
get_device_id(usb_printer_t *printer,	/* I - Printer */
              char          *buffer,	/* I - String buffer */
              size_t        bufsize)	/* I - Number of bytes in buffer */
{
  int	length;				/* Length of device ID */


  if (libusb_control_transfer(printer->handle,
			      LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_ENDPOINT_IN |
			      LIBUSB_RECIPIENT_INTERFACE,
			      0, printer->conf,
			      (printer->iface << 8) | printer->altset,
			      (unsigned char *)buffer, bufsize, 5000) < 0)
  {
    *buffer = '\0';
    return (-1);
  }

 /*
  * Extract the length of the device ID string from the first two
  * bytes.  The 1284 spec says the length is stored MSB first...
  */

  length = (int)((((unsigned)buffer[0] & 255) << 8) | ((unsigned)buffer[1] & 255));

 /*
  * Check to see if the length is larger than our buffer or less than 14 bytes
  * (the minimum valid device ID is "MFG:x;MDL:y;" with 2 bytes for the length).
  *
  * If the length is out-of-range, assume that the vendor incorrectly
  * implemented the 1284 spec and re-read the length as LSB first,..
  */

  if (length > bufsize || length < 14)
    length = (int)((((unsigned)buffer[1] & 255) << 8) | ((unsigned)buffer[0] & 255));

  if (length > bufsize)
    length = bufsize;

  if (length < 14)
  {
   /*
    * Invalid device ID, clear it!
    */

    *buffer = '\0';
    return (-1);
  }

  length -= 2;

 /*
  * Copy the device ID text to the beginning of the buffer and
  * nul-terminate.
  */

  memmove(buffer, buffer + 2, (size_t)length);
  buffer[length] = '\0';

  return (0);
}


/*
 * 'list_cb()' - List USB printers for discovery.
 */

static int				/* O - 0 to continue, 1 to stop */
list_cb(usb_printer_t *printer,		/* I - Printer */
        const char    *device_uri,	/* I - Device URI */
        const char    *device_id,	/* I - IEEE-1284 device ID */
        const void    *data)		/* I - User data (not used) */
{
  char	make_model[1024];		/* Make and model */


 /*
  * Get the device URI and make/model strings...
  */

  if (backendGetMakeModel(device_id, make_model, sizeof(make_model)))
    strlcpy(make_model, "Unknown", sizeof(make_model));

 /*
  * Report the printer...
  */

  cupsBackendReport("direct", device_uri, make_model, make_model, device_id,
                    NULL);

 /*
  * Keep going...
  */

  return (0);
}


/*
 * 'load_quirks()' - Load all quirks files in the /usr/share/cups/usb directory.
 */

static void
load_quirks(void)
{
  const char	*datadir;		/* CUPS_DATADIR environment variable */
  char		filename[1024],		/* Filename */
		line[1024];		/* Line from file */
  cups_dir_t	*dir;			/* Directory */
  cups_dentry_t	*dent;			/* Directory entry */
  cups_file_t	*fp;			/* Quirks file */
  usb_quirk_t	*quirk;			/* New quirk */


  all_quirks = cupsArrayNew((cups_array_func_t)compare_quirks, NULL);

  if ((datadir = getenv("CUPS_DATADIR")) == NULL)
    datadir = CUPS_DATADIR;

  snprintf(filename, sizeof(filename), "%s/usb", datadir);
  if ((dir = cupsDirOpen(filename)) == NULL)
  {
    perror(filename);
    return;
  }

  fprintf(stderr, "DEBUG: Loading USB quirks from \"%s\".\n", filename);

  while ((dent = cupsDirRead(dir)) != NULL)
  {
    if (!S_ISREG(dent->fileinfo.st_mode))
      continue;

    snprintf(filename, sizeof(filename), "%s/usb/%s", datadir, dent->filename);
    if ((fp = cupsFileOpen(filename, "r")) == NULL)
    {
      perror(filename);
      continue;
    }

    while (cupsFileGets(fp, line, sizeof(line)))
    {
     /*
      * Skip blank and comment lines...
      */

      if (line[0] == '#' || !line[0])
        continue;

     /*
      * Add a quirk...
      */

      if ((quirk = calloc(1, sizeof(usb_quirk_t))) == NULL)
      {
        perror("DEBUG: Unable to allocate memory for quirk");
        break;
      }

      if (sscanf(line, "%x%x", &quirk->vendor_id, &quirk->product_id) < 1)
      {
        fprintf(stderr, "DEBUG: Bad line: %s\n", line);
        free(quirk);
        continue;
      }

      if (strstr(line, " blacklist"))
        quirk->quirks |= USB_QUIRK_BLACKLIST;

      if (strstr(line, " delay-close"))
        quirk->quirks |= USB_QUIRK_DELAY_CLOSE;

      if (strstr(line, " no-reattach"))
        quirk->quirks |= USB_QUIRK_NO_REATTACH;

      if (strstr(line, " soft-reset"))
        quirk->quirks |= USB_QUIRK_SOFT_RESET;

      if (strstr(line, " unidir"))
        quirk->quirks |= USB_QUIRK_UNIDIR;

      if (strstr(line, " usb-init"))
        quirk->quirks |= USB_QUIRK_USB_INIT;

      if (strstr(line, " vendor-class"))
        quirk->quirks |= USB_QUIRK_VENDOR_CLASS;

      cupsArrayAdd(all_quirks, quirk);
    }

    cupsFileClose(fp);
  }

  fprintf(stderr, "DEBUG: Loaded %d quirks.\n", cupsArrayCount(all_quirks));

  cupsDirClose(dir);
}


/*
 * 'make_device_uri()' - Create a device URI for a USB printer.
 */

static char *				/* O - Device URI */
make_device_uri(
    usb_printer_t *printer,		/* I - Printer */
    const char    *device_id,		/* I - IEEE-1284 device ID */
    char          *uri,			/* I - Device URI buffer */
    size_t        uri_size)		/* I - Size of device URI buffer */
{
  struct libusb_device_descriptor devdesc;
                                        /* Current device descriptor */
  char		options[1024];		/* Device URI options */
  int		num_values;		/* Number of 1284 parameters */
  cups_option_t	*values;		/* 1284 parameters */
  const char	*mfg,			/* Manufacturer */
		*mdl,			/* Model */
		*des = NULL,		/* Description */
		*sern = NULL;		/* Serial number */
  size_t	mfglen;			/* Length of manufacturer string */
  char		tempmdl[256],		/* Temporary model string */
		tempmfg[256],		/* Temporary manufacturer string */
		tempsern[256],		/* Temporary serial number string */
		*tempptr;		/* Pointer into temp string */


 /*
  * Get the make, model, and serial numbers...
  */

  num_values = _cupsGet1284Values(device_id, &values);

  memset(&devdesc, 0, sizeof(devdesc));

  if (libusb_get_device_descriptor(printer->device, &devdesc) >= 0 && devdesc.iSerialNumber)
  {
    // Try getting the serial number from the device itself...
    int length = libusb_get_string_descriptor_ascii(printer->handle, devdesc.iSerialNumber, (unsigned char *)tempsern, sizeof(tempsern) - 1);
    if (length > 0)
    {
      tempsern[length] = '\0';
      sern             = tempsern;

      fprintf(stderr, "DEBUG2: iSerialNumber=\"%s\"\n", tempsern);
    }
    else
      fputs("DEBUG2: iSerialNumber could not be read.\n", stderr);
  }
  else
    fputs("DEBUG2: iSerialNumber is not present.\n", stderr);

#if 0
  if (!sern)
  {
    // Fall back on serial number from IEEE-1284 device ID, which on some
    // printers (Issue #170) is a bogus hardcoded number.
    if ((sern = cupsGetOption("SERIALNUMBER", num_values, values)) == NULL)
      if ((sern = cupsGetOption("SERN", num_values, values)) == NULL)
	sern = cupsGetOption("SN", num_values, values);
  }
#endif // 0

  if ((mfg = cupsGetOption("MANUFACTURER", num_values, values)) == NULL)
  {
    if ((mfg = cupsGetOption("MFG", num_values, values)) == NULL && devdesc.iManufacturer)
    {
      int length = libusb_get_string_descriptor_ascii(printer->handle, devdesc.iManufacturer, (unsigned char *)tempmfg, sizeof(tempmfg) - 1);
      if (length > 0)
      {
	tempmfg[length] = '\0';
	mfg             = tempmfg;
      }
    }
  }

  if ((mdl = cupsGetOption("MODEL", num_values, values)) == NULL)
  {
    if ((mdl = cupsGetOption("MDL", num_values, values)) == NULL && devdesc.iProduct)
    {
      int length = libusb_get_string_descriptor_ascii(printer->handle, devdesc.iProduct, (unsigned char *)tempmdl, sizeof(tempmdl) - 1);
      if (length > 0)
      {
	tempmdl[length] = '\0';
	mdl             = tempmdl;
      }
    }
  }

 /*
  * To maintain compatibility with the original character device backend on
  * Linux and *BSD, map manufacturer names...
  */

  if (mfg)
  {
    if (!_cups_strcasecmp(mfg, "Hewlett-Packard"))
      mfg = "HP";
    else if (!_cups_strcasecmp(mfg, "Lexmark International"))
      mfg = "Lexmark";
  }
  else
  {
   /*
    * No manufacturer?  Use the model string or description...
    */

    if (mdl)
      _ppdNormalizeMakeAndModel(mdl, tempmfg, sizeof(tempmfg));
    else if ((des = cupsGetOption("DESCRIPTION", num_values, values)) != NULL ||
             (des = cupsGetOption("DES", num_values, values)) != NULL)
      _ppdNormalizeMakeAndModel(des, tempmfg, sizeof(tempmfg));
    else
      strlcpy(tempmfg, "Unknown", sizeof(tempmfg));

    if ((tempptr = strchr(tempmfg, ' ')) != NULL)
      *tempptr = '\0';

    mfg = tempmfg;
  }

  if (!mdl)
  {
   /*
    * No model?  Use description...
    */
    if (des)
      mdl = des; /* We remove the manufacturer name below */
    else if (!strncasecmp(mfg, "Unknown", 7))
      mdl = "Printer";
    else
      mdl = "Unknown Model";
  }

  mfglen = strlen(mfg);

  if (!strncasecmp(mdl, mfg, mfglen) && _cups_isspace(mdl[mfglen]))
  {
    mdl += mfglen + 1;

    while (_cups_isspace(*mdl))
      mdl ++;
  }

 /*
  * Generate the device URI from the manufacturer, model, serial number,
  * and interface number...
  */

  if (sern)
  {
    if (printer->iface > 0)
      snprintf(options, sizeof(options), "?serial=%s&interface=%d", sern,
               printer->iface);
    else
      snprintf(options, sizeof(options), "?serial=%s", sern);
  }
  else if (printer->iface > 0)
    snprintf(options, sizeof(options), "?interface=%d", printer->iface);
  else
    options[0] = '\0';

  httpAssembleURIf(HTTP_URI_CODING_ALL, uri, uri_size, "usb", NULL, mfg, 0,
		   "/%s%s", mdl, options);

  cupsFreeOptions(num_values, values);

  return (uri);
}


/*
 * 'open_device()' - Open a connection to the USB printer.
 */

static int				/* O - 0 on success, -1 on error */
open_device(usb_printer_t *printer,	/* I - Printer */
            int           verbose)	/* I - Update connecting-to-device state? */
{
  struct libusb_device_descriptor devdesc;
                                        /* Current device descriptor */
  struct libusb_config_descriptor *confptr = NULL;
                                        /* Pointer to current configuration */
  int	number1 = -1,			/* Configuration/interface/altset */
        number2 = -1,			/* numbers */
        errcode = 0;
  char	current;			/* Current configuration */


 /*
  * Return immediately if we are already connected...
  */

  if (printer->handle)
    return (0);

 /*
  * Try opening the printer...
  */

  if ((errcode = libusb_open(printer->device, &printer->handle)) < 0)
  {
    fprintf(stderr, "DEBUG: Failed to open device, code: %d\n",
	    errcode);
    return (-1);
  }

  printer->usblp_attached = 0;
  printer->reset_after_job = 0;

  if (verbose)
    fputs("STATE: +connecting-to-device\n", stderr);

  if ((errcode = libusb_get_device_descriptor(printer->device, &devdesc)) < 0)
  {
    fprintf(stderr, "DEBUG: Failed to get device descriptor, code: %d\n",
	    errcode);
    goto error;
  }

 /*
  * Get the "usblp" kernel module out of the way. This backend only
  * works without the module attached.
  */

  errcode = libusb_kernel_driver_active(printer->handle, printer->iface);
  if (errcode == 0)
    printer->usblp_attached = 0;
  else if (errcode == 1)
  {
    printer->usblp_attached = 1;
    if ((errcode =
	 libusb_detach_kernel_driver(printer->handle, printer->iface)) < 0)
    {
      fprintf(stderr, "DEBUG: Failed to detach \"usblp\" module from %04x:%04x\n",
	      devdesc.idVendor, devdesc.idProduct);
      goto error;
    }
  }
  else
  {
    printer->usblp_attached = 0;

    if (errcode != LIBUSB_ERROR_NOT_SUPPORTED)
    {
      fprintf(stderr,
              "DEBUG: Failed to check whether %04x:%04x has the \"usblp\" "
              "kernel module attached\n", devdesc.idVendor, devdesc.idProduct);
      goto error;
    }
  }

 /*
  * Set the desired configuration, but only if it needs changing. Some
  * printers (e.g., Samsung) don't like libusb_set_configuration. It will
  * succeed, but the following print job is sometimes silently lost by the
  * printer.
  */

  if (libusb_control_transfer(printer->handle,
                LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_ENDPOINT_IN |
		LIBUSB_RECIPIENT_DEVICE,
		8, /* GET_CONFIGURATION */
		0, 0, (unsigned char *)&current, 1, 5000) < 0)
    current = 0;			/* Assume not configured */

  printer->origconf = current;

  if ((errcode =
       libusb_get_config_descriptor (printer->device, printer->conf, &confptr))
      < 0)
  {
    fprintf(stderr, "DEBUG: Failed to get config descriptor for %04x:%04x\n",
	    devdesc.idVendor, devdesc.idProduct);
    goto error;
  }
  number1 = confptr->bConfigurationValue;

  if (number1 != current)
  {
    fprintf(stderr, "DEBUG: Switching USB device configuration: %d -> %d\n",
	    current, number1);
    if ((errcode = libusb_set_configuration(printer->handle, number1)) < 0)
    {
     /*
      * If the set fails, chances are that the printer only supports a
      * single configuration.  Technically these printers don't conform to
      * the USB printer specification, but otherwise they'll work...
      */

      if (errcode != LIBUSB_ERROR_BUSY)
        fprintf(stderr, "DEBUG: Failed to set configuration %d for %04x:%04x\n",
		number1, devdesc.idVendor, devdesc.idProduct);
    }
  }

 /*
  * Claim interfaces as needed...
  */

  number1 = confptr->interface[printer->iface].
    altsetting[printer->altset].bInterfaceNumber;

  while ((errcode = libusb_claim_interface(printer->handle, number1)) < 0)
  {
    if (errcode != LIBUSB_ERROR_BUSY)
    {
      fprintf(stderr,
              "DEBUG: Failed to claim interface %d for %04x:%04x: %s\n",
              number1, devdesc.idVendor, devdesc.idProduct, strerror(errno));

      goto error;
    }
    else if ((errcode = libusb_detach_kernel_driver(printer->handle, printer->iface)) < 0)
    {
      fprintf(stderr,
              "DEBUG: Failed to detach \"usblp\" module from %04x:%04x\n",
              devdesc.idVendor, devdesc.idProduct);

      goto error;
    }

    sleep (1);
  }

 /*
  * Set alternate setting, but only if there is more than one option.  Some
  * printers (e.g., Samsung) don't like usb_set_altinterface.
  */

  if (confptr->interface[printer->iface].num_altsetting > 1)
  {
    number1 = confptr->interface[printer->iface].
                 altsetting[printer->altset].bInterfaceNumber;
    number2 = confptr->interface[printer->iface].
                 altsetting[printer->altset].bAlternateSetting;

    while ((errcode =
	    libusb_set_interface_alt_setting(printer->handle, number1, number2))
	   < 0)
    {
      if (errcode != LIBUSB_ERROR_BUSY)
      {
        fprintf(stderr,
                "DEBUG: Failed to set alternate interface %d for %04x:%04x: "
                "%s\n",
                number2, devdesc.idVendor, devdesc.idProduct, strerror(errno));

	goto error;
      }
    }
  }

  libusb_free_config_descriptor(confptr);

  if (verbose)
    fputs("STATE: -connecting-to-device\n", stderr);

  return (0);

 /*
  * If we get here, there was a hard error...
  */

  error:

  if (verbose)
    fputs("STATE: -connecting-to-device\n", stderr);

  libusb_close(printer->handle);
  printer->handle = NULL;

  return (-1);
}


/*
 * 'print_cb()' - Find a USB printer for printing.
 */

static int				/* O - 0 to continue, 1 to stop (found) */
print_cb(usb_printer_t *printer,	/* I - Printer */
         const char    *device_uri,	/* I - Device URI */
         const char    *device_id,	/* I - IEEE-1284 device ID */
         const void    *data)		/* I - User data (make, model, S/N) */
{
  char	requested_uri[1024],		/* Requested URI */
	*requested_ptr,			/* Pointer into requested URI */
	detected_uri[1024],		/* Detected URI */
	*detected_ptr;			/* Pointer into detected URI */


 /*
  * If we have an exact match, stop now...
  */

  if (!strcmp((char *)data, device_uri))
    return (1);

 /*
  * Work on copies of the URIs...
  */

  strlcpy(requested_uri, (char *)data, sizeof(requested_uri));
  strlcpy(detected_uri, device_uri, sizeof(detected_uri));

 /*
  * libusb-discovered URIs can have an "interface" specification and this
  * never happens for usblp-discovered URIs, so remove the "interface"
  * specification from the URI which we are checking currently. This way a
  * queue for a usblp-discovered printer can now be accessed via libusb.
  *
  * Similarly, strip "?serial=NNN...NNN" as needed.
  */

  if ((requested_ptr = strstr(requested_uri, "?interface=")) == NULL)
    requested_ptr = strstr(requested_uri, "&interface=");
  if ((detected_ptr = strstr(detected_uri, "?interface=")) == NULL)
    detected_ptr = strstr(detected_uri, "&interface=");

  if (!requested_ptr && detected_ptr)
  {
   /*
    * Strip "[?&]interface=nnn" from the detected printer.
    */

    *detected_ptr = '\0';
  }
  else if (requested_ptr && !detected_ptr)
  {
   /*
    * Strip "[?&]interface=nnn" from the requested printer.
    */

    *requested_ptr = '\0';
  }

  if ((requested_ptr = strstr(requested_uri, "?serial=?")) != NULL)
  {
   /*
    * Strip "?serial=?" from the requested printer.  This is a special
    * case, as "?serial=?" means no serial number and not the serial
    * number '?'.  This is not covered by the checks below...
    */

    *requested_ptr = '\0';
  }

  if ((requested_ptr = strstr(requested_uri, "?serial=")) == NULL &&
      (detected_ptr = strstr(detected_uri, "?serial=")) != NULL)
  {
   /*
    * Strip "?serial=nnn" from the detected printer.
    */

    *detected_ptr = '\0';
  }
  else if (requested_ptr && !detected_ptr)
  {
   /*
    * Strip "?serial=nnn" from the requested printer.
    */

    *requested_ptr = '\0';
  }

  return (!strcmp(requested_uri, detected_uri));
}


/*
 * 'read_thread()' - Thread to read the backchannel data on.
 */

static void *read_thread(void *reference)
{
  unsigned char		readbuffer[512];
  int			rbytes;
  int			readstatus;


  (void)reference;

  do
  {
   /*
    * Try reading from the OUT (to host) endpoint...
    */

    rbytes     = sizeof(readbuffer);
    readstatus = libusb_bulk_transfer(g.printer->handle,
				      g.printer->read_endp,
				      readbuffer, rbytes,
				      &rbytes, 60000);
    if (readstatus == LIBUSB_SUCCESS && rbytes > 0)
    {
      fprintf(stderr, "DEBUG: Read %d bytes of back-channel data...\n", (int)rbytes);
      cupsBackChannelWrite((const char *)readbuffer, (size_t)rbytes, 1.0);
    }
    else if (readstatus == LIBUSB_ERROR_TIMEOUT)
      fputs("DEBUG: Got USB transaction timeout during read.\n", stderr);
    else if (readstatus == LIBUSB_ERROR_PIPE)
      fputs("DEBUG: Got USB pipe stalled during read.\n", stderr);
    else if (readstatus == LIBUSB_ERROR_INTERRUPTED)
      fputs("DEBUG: Got USB return aborted during read.\n", stderr);

   /*
    * Make sure this loop executes no more than once every 250 miliseconds...
    */

    if ((readstatus != LIBUSB_SUCCESS || rbytes == 0) &&
	 (g.wait_eof || !g.read_thread_stop))
      usleep(250000);
  }
  while (g.wait_eof || !g.read_thread_stop);

 /*
  * Let the main thread know that we have completed the read thread...
  */

  pthread_mutex_lock(&g.read_thread_mutex);
  g.read_thread_done = 1;
  pthread_cond_signal(&g.read_thread_cond);
  pthread_mutex_unlock(&g.read_thread_mutex);

  return (NULL);
}


/*
 * 'sidechannel_thread()' - Handle side-channel requests.
 */

static void*
sidechannel_thread(void *reference)
{
  cups_sc_command_t	command;	/* Request command */
  cups_sc_status_t	status;		/* Request/response status */
  char			data[2048];	/* Request/response data */
  int			datalen;	/* Request/response data size */


  (void)reference;

  do
  {
    datalen = sizeof(data);

    if (cupsSideChannelRead(&command, &status, data, &datalen, 1.0))
    {
      if (status == CUPS_SC_STATUS_TIMEOUT)
	continue;
      else
	break;
    }

    switch (command)
    {
      case CUPS_SC_CMD_SOFT_RESET:	/* Do a soft reset */
	  fputs("DEBUG: CUPS_SC_CMD_SOFT_RESET received from driver...\n",
		stderr);

	  soft_reset();
	  cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, NULL, 0, 1.0);
	  fputs("DEBUG: Returning status CUPS_STATUS_OK with no bytes...\n",
		stderr);
	  break;

      case CUPS_SC_CMD_DRAIN_OUTPUT:	/* Drain all pending output */
	  fputs("DEBUG: CUPS_SC_CMD_DRAIN_OUTPUT received from driver...\n",
		stderr);

	  g.drain_output = 1;
	  break;

      case CUPS_SC_CMD_GET_BIDI:	/* Is the connection bidirectional? */
	  fputs("DEBUG: CUPS_SC_CMD_GET_BIDI received from driver...\n",
		stderr);

	  data[0] = (g.printer->protocol >= 2 ? 1 : 0);
	  cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, 1, 1.0);

	  fprintf(stderr,
	          "DEBUG: Returned CUPS_SC_STATUS_OK with 1 byte (%02X)...\n",
		  data[0]);
	  break;

      case CUPS_SC_CMD_GET_DEVICE_ID:	/* Return IEEE-1284 device ID */
	  fputs("DEBUG: CUPS_SC_CMD_GET_DEVICE_ID received from driver...\n",
		stderr);

	  datalen = sizeof(data);
	  if (get_device_id(g.printer, data, sizeof(data)))
	  {
	    status  = CUPS_SC_STATUS_IO_ERROR;
	    datalen = 0;
	  }
	  else
	  {
	    status  = CUPS_SC_STATUS_OK;
	    datalen = strlen(data);
	  }
	  cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, datalen, 1.0);

          if (datalen < sizeof(data))
	    data[datalen] = '\0';
	  else
	    data[sizeof(data) - 1] = '\0';

	  fprintf(stderr,
	          "DEBUG: Returning CUPS_SC_STATUS_OK with %d bytes (%s)...\n",
		  datalen, data);
	  break;

      case CUPS_SC_CMD_GET_STATE:	/* Return device state */
	  fputs("DEBUG: CUPS_SC_CMD_GET_STATE received from driver...\n",
		stderr);

	  data[0] = CUPS_SC_STATE_ONLINE;
	  cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, 1, 1.0);

	  fprintf(stderr,
	          "DEBUG: Returned CUPS_SC_STATUS_OK with 1 byte (%02X)...\n",
		  data[0]);
	  break;

      case CUPS_SC_CMD_GET_CONNECTED:	/* Return whether device is
					   connected */
	  fputs("DEBUG: CUPS_SC_CMD_GET_CONNECTED received from driver...\n",
		stderr);

	  data[0] = (g.printer->handle ? 1 : 0);
	  cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, 1, 1.0);

	  fprintf(stderr,
	          "DEBUG: Returned CUPS_SC_STATUS_OK with 1 byte (%02X)...\n",
		  data[0]);
	  break;

      default:
	  fprintf(stderr, "DEBUG: Unknown side-channel command (%d) received "
			  "from driver...\n", command);

	  cupsSideChannelWrite(command, CUPS_SC_STATUS_NOT_IMPLEMENTED,
			       NULL, 0, 1.0);

	  fputs("DEBUG: Returned CUPS_SC_STATUS_NOT_IMPLEMENTED with no bytes...\n",
		stderr);
	  break;
    }
  }
  while (!g.sidechannel_thread_stop);

  pthread_mutex_lock(&g.sidechannel_thread_mutex);
  g.sidechannel_thread_done = 1;
  pthread_cond_signal(&g.sidechannel_thread_cond);
  pthread_mutex_unlock(&g.sidechannel_thread_mutex);

  return (NULL);
}


/*
 * 'soft_reset()' - Send a soft reset to the device.
 */

static void
soft_reset(void)
{
  fd_set	  input_set;		/* Input set for select() */
  struct timeval  tv;			/* Time value */
  char		  buffer[2048];		/* Buffer */
  struct timespec cond_timeout;		/* pthread condition timeout */


 /*
  * Send an abort once a second until the I/O lock is released by the main
  * thread...
  */

  pthread_mutex_lock(&g.readwrite_lock_mutex);
  while (g.readwrite_lock)
  {
    gettimeofday(&tv, NULL);
    cond_timeout.tv_sec  = tv.tv_sec + 1;
    cond_timeout.tv_nsec = tv.tv_usec * 1000;

    while (g.readwrite_lock)
    {
      if (pthread_cond_timedwait(&g.readwrite_lock_cond,
				 &g.readwrite_lock_mutex,
				 &cond_timeout) != 0)
	break;
    }
  }

  g.readwrite_lock = 1;
  pthread_mutex_unlock(&g.readwrite_lock_mutex);

 /*
  * Flush bytes waiting on print_fd...
  */

  g.print_bytes = 0;

  FD_ZERO(&input_set);
  FD_SET(g.print_fd, &input_set);

  tv.tv_sec  = 0;
  tv.tv_usec = 0;

  while (select(g.print_fd+1, &input_set, NULL, NULL, &tv) > 0)
    if (read(g.print_fd, buffer, sizeof(buffer)) <= 0)
      break;

 /*
  * Send the reset...
  */

  soft_reset_printer(g.printer);

 /*
  * Release the I/O lock...
  */

  pthread_mutex_lock(&g.readwrite_lock_mutex);
  g.readwrite_lock = 0;
  pthread_cond_signal(&g.readwrite_lock_cond);
  pthread_mutex_unlock(&g.readwrite_lock_mutex);
}


/*
 * 'soft_reset_printer()' - Do the soft reset request specific to printers
 *
 * This soft reset is specific to the printer device class and is much less
 * invasive than the general USB reset libusb_reset_device(). Especially it
 * does never happen that the USB addressing and configuration changes. What
 * is actually done is that all buffers get flushed and the bulk IN and OUT
 * pipes get reset to their default states. This clears all stall conditions.
 * See http://cholla.mmto.org/computers/linux/usb/usbprint11.pdf
 */

static int				/* O - 0 on success, < 0 on error */
soft_reset_printer(
    usb_printer_t *printer)		/* I - Printer */
{
  struct libusb_config_descriptor *confptr = NULL;
                                        /* Pointer to current configuration */
  int interface,			/* Interface to reset */
      errcode;				/* Error code */


  if (libusb_get_config_descriptor(printer->device, printer->conf,
                                   &confptr) < 0)
    interface = printer->iface;
  else
    interface = confptr->interface[printer->iface].
                         altsetting[printer->altset].bInterfaceNumber;

  libusb_free_config_descriptor(confptr);

  if ((errcode = libusb_control_transfer(printer->handle,
					 LIBUSB_REQUEST_TYPE_CLASS |
					 LIBUSB_ENDPOINT_OUT |
					 LIBUSB_RECIPIENT_OTHER,
					 2, 0, interface, NULL, 0, 5000)) < 0)
    errcode = libusb_control_transfer(printer->handle,
				      LIBUSB_REQUEST_TYPE_CLASS |
				      LIBUSB_ENDPOINT_OUT |
				      LIBUSB_RECIPIENT_INTERFACE,
				      2, 0, interface, NULL, 0, 5000);

  return (errcode);
}
