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

/*
 * Include necessary headers...
 */

#include "usb_libusb.h"
#include <libusb-1.0/libusb.h>
#include <cups/backend.h>
#include <cups/array.h>
#include <cups/cups.h>
#include <cups/sidechannel.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>
#include <signal.h>
#include <stdio.h>
#include <bsd/string.h>
#include <errno.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

#define CUPS_DATADIR "/usr/share/cups"
#define CUPS_LLFMT  "%lld"
#define CUPS_LLCAST (long long)

/*
 * 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) {
        printf(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");
            printf(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");
            printf(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) {
        printf(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) {
                    printf(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) {
                        printf(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...
                     */

                    printf(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;
                        get_device_id(&printer, device_id, sizeof(device_id));

                        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);
}

static inline int           /* O - 1 on match, 0 otherwise */
_cups_isspace(int ch)           /* I - Character to test */
{
    return (ch == ' ' || ch == '\f' || ch == '\n' || ch == '\r' || ch == '\t' ||
            ch == '\v');
}

/*
 * '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;          /* Serial number */
    size_t    mfglen;         /* Length of manufacturer string */
    char      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);

    if ((sern = cupsGetOption("SERIALNUMBER", num_values, values)) == NULL)
        if ((sern = cupsGetOption("SERN", num_values, values)) == NULL)
            if ((sern = cupsGetOption("SN", num_values, values)) == NULL &&
                    ((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;
                }
            }

    if ((mfg = cupsGetOption("MANUFACTURER", num_values, values)) == NULL)
        mfg = cupsGetOption("MFG", num_values, values);

    if ((mdl = cupsGetOption("MODEL", num_values, values)) == NULL)
        mdl = cupsGetOption("MDL", num_values, values);

    /*
     * 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;
    struct timeval    now,
               delay,
               end,
               timeleft;


    (void)reference;

    /*
     * Read frequency: once every 250 milliseconds.
     */

    delay.tv_sec = 0;
    delay.tv_usec = 250000;

    do {
        /*
         * Remember when we started so we can throttle the loop after the read
         * call...
         */

        gettimeofday(&now, NULL);

        /*
         * Calculate what 250 milliSeconds are in absolute time...
         */

        timeradd(&now, &delay, &end);

        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 ((g.wait_eof || !g.read_thread_stop)) {
            gettimeofday(&now, NULL);
            if (timercmp(&now, &end, < )) {
                timersub(&end, &now, &timeleft);
                usleep(1000000 * timeleft.tv_sec + timeleft.tv_usec);
            }
        }
    } 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);
}
