#include <libusb.h>
#include <cups/cups-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>
#define WAIT_EOF 0
#define WAIT_EOF_DELAY 7
#define WAIT_SIDE_DELAY 3
#define DEFAULT_TIMEOUT 5000L
typedef struct usb_printer_s
{
struct libusb_device *device;
int conf,
origconf,
iface,
altset,
write_endp,
read_endp,
protocol,
usblp_attached,
reset_after_job;
unsigned quirks;
struct libusb_device_handle *handle;
} usb_printer_t;
typedef int (*usb_cb_t)(usb_printer_t *, const char *, const char *,
const void *);
typedef struct usb_globals_s
{
usb_printer_t *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;
ssize_t print_bytes;
int wait_eof;
int drain_output;
int bidi_flag;
pthread_mutex_t sidechannel_thread_mutex;
pthread_cond_t sidechannel_thread_cond;
int sidechannel_thread_stop;
int sidechannel_thread_done;
} usb_globals_t;
#define USB_QUIRK_BLACKLIST 0x0001
#define USB_QUIRK_NO_REATTACH 0x0002
#define USB_QUIRK_SOFT_RESET 0x0004
#define USB_QUIRK_UNIDIR 0x0008
#define USB_QUIRK_USB_INIT 0x0010
#define USB_QUIRK_VENDOR_CLASS 0x0020
#define USB_QUIRK_WHITELIST 0x0000
typedef struct usb_quirk_s
{
int vendor_id,
product_id;
unsigned quirks;
} usb_quirk_t;
cups_array_t *all_quirks;
usb_globals_t g = { 0 };
libusb_device **all_list;
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);
void
list_devices(void)
{
load_quirks();
fputs("DEBUG: list_devices\n", stderr);
find_device(list_cb, NULL);
}
int
print_device(const char *uri,
const char *hostname,
const char *resource,
char *options,
int print_fd,
int copies,
int argc,
char *argv[])
{
int bytes;
ssize_t total_bytes;
struct sigaction action;
int status = CUPS_BACKEND_OK,
iostatus;
pthread_t read_thread_id,
sidechannel_thread_id;
int have_sidechannel = 0,
have_backchannel = 0;
struct stat sidechannel_info;
unsigned char print_buffer[8192],
*print_ptr;
fd_set input_set;
int nfds;
struct timeval *timeout,
tv;
struct timespec cond_timeout;
int num_opts;
cups_option_t *opts;
const char *val;
load_quirks();
have_sidechannel = !fstat(CUPS_SC_FD, &sidechannel_info) &&
S_ISSOCK(sidechannel_info.st_mode);
g.wait_eof = WAIT_EOF;
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;
g.printer->reset_after_job = (g.printer->quirks & USB_QUIRK_SOFT_RESET ? 1 : 0);
if (!print_fd)
{
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
action.sa_handler = SIG_IGN;
sigaction(SIGTERM, &action, NULL);
}
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);
}
}
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");
}
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");
}
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");
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);
if (g.print_bytes)
{
tv.tv_sec = 0;
tv.tv_usec = 100000;
timeout = &tv;
}
else if (g.drain_output)
{
tv.tv_sec = 0;
tv.tv_usec = 0;
timeout = &tv;
}
else
timeout = NULL;
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);
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 (g.drain_output && !nfds && !g.print_bytes)
{
cupsSideChannelWrite(CUPS_SC_CMD_DRAIN_OUTPUT, CUPS_SC_STATUS_OK, NULL, 0, 1.0);
g.drain_output = 0;
}
if (FD_ISSET(print_fd, &input_set))
{
g.print_bytes = read(print_fd, print_buffer, sizeof(print_buffer));
if (g.print_bytes < 0)
{
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)
{
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, 60000);
if (iostatus == LIBUSB_ERROR_TIMEOUT)
{
fputs("DEBUG: Got USB transaction timeout during write.\n", stderr);
iostatus = 0;
}
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, 60000);
}
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, 60000);
}
if (iostatus)
{
_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);
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);
}
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 (!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_device(g.printer);
libusb_free_device_list(all_list, 1);
libusb_exit(NULL);
return (status);
}
static int
close_device(usb_printer_t *printer)
{
struct libusb_device_descriptor devdesc;
struct libusb_config_descriptor *confptr;
if (printer->handle)
{
int errcode;
int number1,
number2;
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 (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);
}
}
}
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);
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");
}
libusb_close(printer->handle);
printer->handle = NULL;
}
return (0);
}
static int
compare_quirks(usb_quirk_t *a,
usb_quirk_t *b)
{
int result;
if ((result = b->vendor_id - a->vendor_id) == 0)
result = b->product_id - a->product_id;
return (result);
}
static usb_printer_t *
find_device(usb_cb_t cb,
const void *data)
{
libusb_device **list;
libusb_device *device = NULL;
struct libusb_device_descriptor devdesc;
struct libusb_config_descriptor *confptr = NULL;
const struct libusb_interface *ifaceptr = NULL;
const struct libusb_interface_descriptor *altptr = NULL;
const struct libusb_endpoint_descriptor *endpptr = NULL;
ssize_t err = 0,
numdevs,
i = 0;
uint8_t conf,
iface,
altset,
protocol,
endp,
read_endp,
write_endp;
char device_id[1024],
device_uri[1024];
static usb_printer_t printer;
err = libusb_init(NULL);
if (err)
{
fprintf(stderr, "DEBUG: Unable to initialize USB access via libusb, "
"libusb error %i\n", (int)err);
return (NULL);
}
numdevs = libusb_get_device_list(NULL, &list);
fprintf(stderr, "DEBUG: libusb_get_device_list=%d\n", (int)numdevs);
if (numdevs > 0)
for (i = 0; i < numdevs; i++)
{
device = list[i];
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);
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 ++)
{
protocol = 0;
for (altset = 0, altptr = ifaceptr->altsetting;
altset < ifaceptr->num_altsetting;
altset ++, altptr ++)
{
if (((altptr->bInterfaceClass != LIBUSB_CLASS_PRINTER ||
altptr->bInterfaceSubClass != 1) &&
((printer.quirks & USB_QUIRK_VENDOR_CLASS) == 0)) ||
(altptr->bInterfaceProtocol != 1 &&
altptr->bInterfaceProtocol != 2) ||
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 = -1;
write_endp = -1;
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 >= 0)
{
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 (numdevs >= 0)
libusb_free_device_list(list, 1);
libusb_exit(NULL);
return (NULL);
}
static unsigned
find_quirks(int vendor_id,
int product_id)
{
usb_quirk_t key,
*match;
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);
}
static int
get_device_id(usb_printer_t *printer,
char *buffer,
size_t bufsize)
{
int length;
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);
}
length = (((unsigned)buffer[0] & 255) << 8) |
((unsigned)buffer[1] & 255);
if (length > bufsize || length < 14)
length = (((unsigned)buffer[1] & 255) << 8) |
((unsigned)buffer[0] & 255);
if (length > bufsize)
length = bufsize;
if (length < 14)
{
*buffer = '\0';
return (-1);
}
length -= 2;
memmove(buffer, buffer + 2, length);
buffer[length] = '\0';
return (0);
}
static int
list_cb(usb_printer_t *printer,
const char *device_uri,
const char *device_id,
const void *data)
{
char make_model[1024];
if (backendGetMakeModel(device_id, make_model, sizeof(make_model)))
strlcpy(make_model, "Unknown", sizeof(make_model));
cupsBackendReport("direct", device_uri, make_model, make_model, device_id,
NULL);
return (0);
}
static void
load_quirks(void)
{
const char *datadir;
char filename[1024],
line[1024];
cups_dir_t *dir;
cups_dentry_t *dent;
cups_file_t *fp;
usb_quirk_t *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)))
{
if (line[0] == '#' || !line[0])
continue;
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, " 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 char *
make_device_uri(
usb_printer_t *printer,
const char *device_id,
char *uri,
size_t uri_size)
{
struct libusb_device_descriptor devdesc;
char options[1024];
int num_values;
cups_option_t *values;
const char *mfg,
*mdl,
*des = NULL,
*sern;
size_t mfglen;
char tempmfg[256],
tempsern[256],
*tempptr;
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))
{
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);
if (mfg)
{
if (!_cups_strcasecmp(mfg, "Hewlett-Packard"))
mfg = "HP";
else if (!_cups_strcasecmp(mfg, "Lexmark International"))
mfg = "Lexmark";
}
else
{
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)
{
if (des)
mdl = des;
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 ++;
}
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);
}
static int
open_device(usb_printer_t *printer,
int verbose)
{
struct libusb_device_descriptor devdesc;
struct libusb_config_descriptor *confptr = NULL;
int number1 = -1,
number2 = -1,
errcode = 0;
char current;
if (printer->handle)
return (0);
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;
}
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;
fprintf(stderr, "DEBUG: Failed to check whether %04x:%04x has the \"usblp\" kernel module attached\n",
devdesc.idVendor, devdesc.idProduct);
goto error;
}
if (libusb_control_transfer(printer->handle,
LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_ENDPOINT_IN |
LIBUSB_RECIPIENT_DEVICE,
8,
0, 0, (unsigned char *)¤t, 1, 5000) < 0)
current = 0;
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 (errcode != LIBUSB_ERROR_BUSY)
fprintf(stderr, "DEBUG: Failed to set configuration %d for %04x:%04x\n",
number1, devdesc.idVendor, devdesc.idProduct);
}
}
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;
}
}
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);
error:
if (verbose)
fputs("STATE: -connecting-to-device\n", stderr);
libusb_close(printer->handle);
printer->handle = NULL;
return (-1);
}
static int
print_cb(usb_printer_t *printer,
const char *device_uri,
const char *device_id,
const void *data)
{
char requested_uri[1024],
*requested_ptr,
detected_uri[1024],
*detected_ptr;
if (!strcmp((char *)data, device_uri))
return (1);
strlcpy(requested_uri, (char *)data, sizeof(requested_uri));
strlcpy(detected_uri, device_uri, sizeof(detected_uri));
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)
{
*detected_ptr = '\0';
}
else if (requested_ptr && !detected_ptr)
{
*requested_ptr = '\0';
}
if ((requested_ptr = strstr(requested_uri, "?serial=?")) != NULL)
{
*requested_ptr = '\0';
}
if ((requested_ptr = strstr(requested_uri, "?serial=")) == NULL &&
(detected_ptr = strstr(detected_uri, "?serial=")) != NULL)
{
*detected_ptr = '\0';
}
else if (requested_ptr && !detected_ptr)
{
*requested_ptr = '\0';
}
return (!strcmp(requested_uri, detected_uri));
}
static void *read_thread(void *reference)
{
unsigned char readbuffer[512];
int rbytes;
int readstatus;
struct timeval now,
delay,
end,
timeleft;
(void)reference;
delay.tv_sec = 0;
delay.tv_usec = 250000;
do
{
gettimeofday(&now, NULL);
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, 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);
if ((readstatus != LIBUSB_SUCCESS || rbytes == 0) &&
(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);
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);
}
static void*
sidechannel_thread(void *reference)
{
cups_sc_command_t command;
cups_sc_status_t status;
char data[2048];
int datalen;
(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:
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:
fputs("DEBUG: CUPS_SC_CMD_DRAIN_OUTPUT received from driver...\n",
stderr);
g.drain_output = 1;
break;
case CUPS_SC_CMD_GET_BIDI:
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:
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:
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:
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);
}
static void
soft_reset(void)
{
fd_set input_set;
struct timeval tv;
char buffer[2048];
struct timespec cond_timeout;
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);
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;
soft_reset_printer(g.printer);
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);
}
static int
soft_reset_printer(
usb_printer_t *printer)
{
struct libusb_config_descriptor *confptr = NULL;
int interface,
errcode;
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);
}