]> git.ipfire.org Git - thirdparty/cups.git/blob - backend/usb-libusb.c
Merge changes from CUPS 1.5svn-r9000.
[thirdparty/cups.git] / backend / usb-libusb.c
1 /*
2 * "$Id$"
3 *
4 * Libusb interface code for the Common UNIX Printing System (CUPS).
5 *
6 * Copyright 2007-2009 by Apple Inc.
7 *
8 * These coded instructions, statements, and computer programs are the
9 * property of Apple Inc. and are protected by Federal copyright
10 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
11 * which should have been included with this file. If this file is
12 * file is missing or damaged, see the license at "http://www.cups.org/".
13 *
14 * Contents:
15 *
16 * list_devices() - List the available printers.
17 * print_device() - Print a file to a USB device.
18 * close_device() - Close the connection to the USB printer.
19 * find_device() - Find or enumerate USB printers.
20 * get_device_id() - Get the IEEE-1284 device ID for the printer.
21 * list_cb() - List USB printers for discovery.
22 * make_device_uri() - Create a device URI for a USB printer.
23 * open_device() - Open a connection to the USB printer.
24 * print_cb() - Find a USB printer for printing.
25 * side_cb() - Handle side-channel requests.
26 */
27
28 /*
29 * Include necessary headers...
30 */
31
32 #include <usb.h>
33 #include <poll.h>
34 #include <cups/globals.h>
35
36
37 /*
38 * Local types...
39 */
40
41 typedef struct usb_printer_s /**** USB Printer Data ****/
42 {
43 struct usb_device *device; /* Device info */
44 int conf, /* Configuration */
45 iface, /* Interface */
46 altset, /* Alternate setting */
47 write_endp, /* Write endpoint */
48 read_endp; /* Read endpoint */
49 struct usb_dev_handle *handle; /* Open handle to device */
50 } usb_printer_t;
51
52 typedef int (*usb_cb_t)(usb_printer_t *, const char *, const char *,
53 const void *);
54
55
56 /*
57 * Local functions...
58 */
59
60 static int close_device(usb_printer_t *printer);
61 static usb_printer_t *find_device(usb_cb_t cb, const void *data);
62 static int get_device_id(usb_printer_t *printer, char *buffer,
63 size_t bufsize);
64 static int list_cb(usb_printer_t *printer, const char *device_uri,
65 const char *device_id, const void *data);
66 static char *make_device_uri(usb_printer_t *printer,
67 const char *device_id,
68 char *uri, size_t uri_size);
69 static int open_device(usb_printer_t *printer, int verbose);
70 static int print_cb(usb_printer_t *printer, const char *device_uri,
71 const char *device_id, const void *data);
72 static ssize_t side_cb(usb_printer_t *printer, int print_fd);
73
74
75 /*
76 * 'list_devices()' - List the available printers.
77 */
78
79 void
80 list_devices(void)
81 {
82 fputs("DEBUG: list_devices\n", stderr);
83 find_device(list_cb, NULL);
84 }
85
86
87 /*
88 * 'print_device()' - Print a file to a USB device.
89 */
90
91 int /* O - Exit status */
92 print_device(const char *uri, /* I - Device URI */
93 const char *hostname, /* I - Hostname/manufacturer */
94 const char *resource, /* I - Resource/modelname */
95 char *options, /* I - Device options/serial number */
96 int print_fd, /* I - File descriptor to print */
97 int copies, /* I - Copies to print */
98 int argc, /* I - Number of command-line arguments (6 or 7) */
99 char *argv[]) /* I - Command-line arguments */
100 {
101 usb_printer_t *printer; /* Printer */
102 ssize_t bytes, /* Bytes read/written */
103 tbytes; /* Total bytes written */
104 char buffer[8192]; /* Print data buffer */
105 struct sigaction action; /* Actions for POSIX signals */
106 struct pollfd pfds[2]; /* Poll descriptors */
107
108
109 fputs("DEBUG: print_device\n", stderr);
110
111 /*
112 * Connect to the printer...
113 */
114
115 while ((printer = find_device(print_cb, uri)) == NULL)
116 {
117 _cupsLangPuts(stderr,
118 _("INFO: Waiting for printer to become available...\n"));
119 sleep(5);
120 }
121
122
123 /*
124 * If we are printing data from a print driver on stdin, ignore SIGTERM
125 * so that the driver can finish out any page data, e.g. to eject the
126 * current page. We only do this for stdin printing as otherwise there
127 * is no way to cancel a raw print job...
128 */
129
130 if (!print_fd)
131 {
132 memset(&action, 0, sizeof(action));
133
134 sigemptyset(&action.sa_mask);
135 action.sa_handler = SIG_IGN;
136 sigaction(SIGTERM, &action, NULL);
137 }
138
139 tbytes = 0;
140
141 pfds[0].fd = print_fd;
142 pfds[0].events = POLLIN;
143 pfds[1].fd = CUPS_SC_FD;
144 pfds[1].events = POLLIN;
145
146 while (copies > 0 && tbytes >= 0)
147 {
148 copies --;
149
150 if (print_fd != 0)
151 {
152 fputs("PAGE: 1 1\n", stderr);
153 lseek(print_fd, 0, SEEK_SET);
154 }
155
156 /*
157 * TODO: Add back-channel support, along with better write error handling.
158 */
159
160 while (poll(pfds, 2, -1) > 0)
161 {
162 /*
163 * CUPS STR #3318: USB process hangs on end-of-file, making further
164 * printing impossible
165 *
166 * From a strict interpretation of POSIX poll(), POLLHUP should never be
167 * set without POLLIN, since POLLIN is the event you request. That said,
168 * it appears that some versions of Linux break this.
169 */
170
171 if (pfds[0].revents & (POLLIN | POLLHUP))
172 {
173 if ((bytes = read(print_fd, buffer, sizeof(buffer))) > 0)
174 {
175 if (usb_bulk_write(printer->handle, printer->write_endp, buffer,
176 bytes, 45000) < 0)
177 {
178 _cupsLangPrintf(stderr,
179 _("ERROR: Unable to write %d bytes to printer\n"),
180 (int)bytes);
181 tbytes = -1;
182 break;
183 }
184
185 tbytes += bytes;
186 }
187 else if (bytes == 0 || (bytes < 0 && errno != EAGAIN && errno != EINTR))
188 break;
189 }
190
191 if (pfds[1].revents & (POLLIN | POLLHUP))
192 {
193 if ((bytes = side_cb(printer, print_fd)) < 0)
194 pfds[1].events = 0; /* Filter has gone away... */
195 else
196 tbytes += bytes;
197 }
198 }
199 }
200
201 /*
202 * Close our connection and return...
203 */
204
205 close_device(printer);
206
207 return (CUPS_BACKEND_OK);
208 }
209
210
211 /*
212 * 'close_device()' - Close the connection to the USB printer.
213 */
214
215 static int /* I - 0 on success, -1 on failure */
216 close_device(usb_printer_t *printer) /* I - Printer */
217 {
218 if (printer->handle)
219 {
220 /*
221 * Release interfaces before closing so that we know all data is written
222 * to the device...
223 */
224
225 int number = printer->device->config[printer->conf].
226 interface[printer->iface].
227 altsetting[printer->altset].bInterfaceNumber;
228 usb_release_interface(printer->handle, number);
229
230 if (number != 0)
231 usb_release_interface(printer->handle, 0);
232
233 /*
234 * Close the interface and return...
235 */
236
237 usb_close(printer->handle);
238 printer->handle = NULL;
239 }
240
241 return (0);
242 }
243
244
245 /*
246 * 'find_device()' - Find or enumerate USB printers.
247 */
248
249 static usb_printer_t * /* O - Found printer */
250 find_device(usb_cb_t cb, /* I - Callback function */
251 const void *data) /* I - User data for callback */
252 {
253 struct usb_bus *bus; /* Current bus */
254 struct usb_device *device; /* Current device */
255 struct usb_config_descriptor *confptr;/* Pointer to current configuration */
256 struct usb_interface *ifaceptr; /* Pointer to current interface */
257 struct usb_interface_descriptor *altptr;
258 /* Pointer to current alternate setting */
259 struct usb_endpoint_descriptor *endpptr;
260 /* Pointer to current endpoint */
261 int conf, /* Current configuration */
262 iface, /* Current interface */
263 altset, /* Current alternate setting */
264 protocol, /* Current protocol */
265 endp, /* Current endpoint */
266 read_endp, /* Current read endpoint */
267 write_endp; /* Current write endpoint */
268 char device_id[1024],/* IEEE-1284 device ID */
269 device_uri[1024];
270 /* Device URI */
271 static usb_printer_t printer; /* Current printer */
272
273
274 /*
275 * Initialize libusb...
276 */
277
278 usb_init();
279 fprintf(stderr, "DEBUG: usb_find_busses=%d\n", usb_find_busses());
280 fprintf(stderr, "DEBUG: usb_find_devices=%d\n", usb_find_devices());
281
282 /*
283 * Then loop through the devices it found...
284 */
285
286 for (bus = usb_get_busses(); bus; bus = bus->next)
287 for (device = bus->devices; device; device = device->next)
288 {
289 /*
290 * Ignore devices with no configuration data and anything that is not
291 * a printer...
292 */
293
294 if (!device->config || !device->descriptor.idVendor ||
295 !device->descriptor.idProduct)
296 continue;
297
298 for (conf = 0, confptr = device->config;
299 conf < device->descriptor.bNumConfigurations;
300 conf ++, confptr ++)
301 for (iface = 0, ifaceptr = confptr->interface;
302 iface < confptr->bNumInterfaces;
303 iface ++, ifaceptr ++)
304 {
305 /*
306 * Some printers offer multiple interfaces...
307 */
308
309 protocol = 0;
310
311 for (altset = 0, altptr = ifaceptr->altsetting;
312 altset < ifaceptr->num_altsetting;
313 altset ++, altptr ++)
314 {
315 /*
316 * Currently we only support unidirectional and bidirectional
317 * printers. Future versions of this code will support the
318 * 1284.4 (packet mode) protocol as well.
319 */
320
321 if (altptr->bInterfaceClass != USB_CLASS_PRINTER ||
322 altptr->bInterfaceSubClass != 1 ||
323 (altptr->bInterfaceProtocol != 1 && /* Unidirectional */
324 altptr->bInterfaceProtocol != 2) || /* Bidirectional */
325 altptr->bInterfaceProtocol < protocol)
326 continue;
327
328 read_endp = -1;
329 write_endp = -1;
330
331 for (endp = 0, endpptr = altptr->endpoint;
332 endp < altptr->bNumEndpoints;
333 endp ++, endpptr ++)
334 if ((endpptr->bmAttributes & USB_ENDPOINT_TYPE_MASK) ==
335 USB_ENDPOINT_TYPE_BULK)
336 {
337 if (endpptr->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
338 read_endp = endp;
339 else
340 write_endp = endp;
341 }
342
343 if (write_endp >= 0)
344 {
345 /*
346 * Save the best match so far...
347 */
348
349 protocol = altptr->bInterfaceProtocol;
350 printer.altset = altset;
351 printer.write_endp = write_endp;
352 printer.read_endp = read_endp;
353 }
354 }
355
356 if (protocol > 0)
357 {
358 printer.device = device;
359 printer.conf = conf;
360 printer.iface = iface;
361 printer.handle = NULL;
362
363 if (!open_device(&printer, data != NULL))
364 {
365 if (!get_device_id(&printer, device_id, sizeof(device_id)))
366 {
367 make_device_uri(&printer, device_id, device_uri,
368 sizeof(device_uri));
369
370 if ((*cb)(&printer, device_uri, device_id, data))
371 {
372 printer.read_endp = printer.device->config[printer.conf].
373 interface[printer.iface].
374 altsetting[printer.altset].
375 endpoint[printer.read_endp].
376 bEndpointAddress;
377 printer.write_endp = printer.device->config[printer.conf].
378 interface[printer.iface].
379 altsetting[printer.altset].
380 endpoint[printer.write_endp].
381 bEndpointAddress;
382 return (&printer);
383 }
384 }
385
386 close_device(&printer);
387 }
388 }
389 }
390 }
391
392 /*
393 * If we get this far without returning, then we haven't found a printer
394 * to print to...
395 */
396
397 return (NULL);
398 }
399
400
401 /*
402 * 'get_device_id()' - Get the IEEE-1284 device ID for the printer.
403 */
404
405 static int /* O - 0 on success, -1 on error */
406 get_device_id(usb_printer_t *printer, /* I - Printer */
407 char *buffer, /* I - String buffer */
408 size_t bufsize) /* I - Number of bytes in buffer */
409 {
410 int length; /* Length of device ID */
411
412
413 if (usb_control_msg(printer->handle,
414 USB_TYPE_CLASS | USB_ENDPOINT_IN | USB_RECIP_INTERFACE,
415 0, printer->conf, printer->iface,
416 buffer, bufsize, 5000) < 0)
417 {
418 *buffer = '\0';
419 return (-1);
420 }
421
422 /*
423 * Extract the length of the device ID string from the first two
424 * bytes. The 1284 spec says the length is stored MSB first...
425 */
426
427 length = (((unsigned)buffer[0] & 255) << 8) +
428 ((unsigned)buffer[1] & 255);
429
430 /*
431 * Check to see if the length is larger than our buffer; first
432 * assume that the vendor incorrectly implemented the 1284 spec,
433 * and then limit the length to the size of our buffer...
434 */
435
436 if (length > bufsize)
437 length = (((unsigned)buffer[1] & 255) << 8) +
438 ((unsigned)buffer[0] & 255);
439
440 if (length > bufsize)
441 length = bufsize;
442
443 length -= 2;
444
445 /*
446 * Copy the device ID text to the beginning of the buffer and
447 * nul-terminate.
448 */
449
450 memmove(buffer, buffer + 2, length);
451 buffer[length] = '\0';
452
453 return (0);
454 }
455
456
457 /*
458 * 'list_cb()' - List USB printers for discovery.
459 */
460
461 static int /* O - 0 to continue, 1 to stop */
462 list_cb(usb_printer_t *printer, /* I - Printer */
463 const char *device_uri, /* I - Device URI */
464 const char *device_id, /* I - IEEE-1284 device ID */
465 const void *data) /* I - User data (not used) */
466 {
467 char make_model[1024]; /* Make and model */
468
469
470 /*
471 * Get the device URI and make/model strings...
472 */
473
474 backendGetMakeModel(device_id, make_model, sizeof(make_model));
475
476 /*
477 * Report the printer...
478 */
479
480 cupsBackendReport("direct", device_uri, make_model, make_model, device_id,
481 NULL);
482
483 /*
484 * Keep going...
485 */
486
487 return (0);
488 }
489
490
491 /*
492 * 'make_device_uri()' - Create a device URI for a USB printer.
493 */
494
495 static char * /* O - Device URI */
496 make_device_uri(
497 usb_printer_t *printer, /* I - Printer */
498 const char *device_id, /* I - IEEE-1284 device ID */
499 char *uri, /* I - Device URI buffer */
500 size_t uri_size) /* I - Size of device URI buffer */
501 {
502 char options[1024]; /* Device URI options */
503 int num_values; /* Number of 1284 parameters */
504 cups_option_t *values; /* 1284 parameters */
505 const char *mfg, /* Manufacturer */
506 *mdl, /* Model */
507 *des, /* Description */
508 *sern; /* Serial number */
509 char tempmfg[256], /* Temporary manufacturer string */
510 tempsern[256], /* Temporary serial number string */
511 *tempptr; /* Pointer into temp string */
512
513
514 /*
515 * Get the make, model, and serial numbers...
516 */
517
518 num_values = _cupsGet1284Values(device_id, &values);
519
520 if ((sern = cupsGetOption("SERIALNUMBER", num_values, values)) == NULL)
521 if ((sern = cupsGetOption("SERN", num_values, values)) == NULL)
522 if ((sern = cupsGetOption("SN", num_values, values)) == NULL)
523 {
524 /*
525 * Try getting the serial number from the device itself...
526 */
527
528 int length = usb_get_string_simple(printer->handle,
529 printer->device->descriptor.
530 iSerialNumber,
531 tempsern, sizeof(tempsern) - 1);
532 if (length > 0)
533 {
534 tempsern[length] = '\0';
535 sern = tempsern;
536 }
537 }
538
539 if ((mfg = cupsGetOption("MANUFACTURER", num_values, values)) == NULL)
540 mfg = cupsGetOption("MFG", num_values, values);
541
542 if ((mdl = cupsGetOption("MODEL", num_values, values)) == NULL)
543 mdl = cupsGetOption("MDL", num_values, values);
544
545 #ifdef __APPLE__
546 /*
547 * To maintain compatibility with the original IOKit-based backend on Mac OS X,
548 * don't map manufacturer names...
549 */
550
551 if (!mfg)
552
553 #else
554 /*
555 * To maintain compatibility with the original character device backend on
556 * Linux and *BSD, map manufacturer names...
557 */
558
559 if (mfg)
560 {
561 if (!strcasecmp(mfg, "Hewlett-Packard"))
562 mfg = "HP";
563 else if (!strcasecmp(mfg, "Lexmark International"))
564 mfg = "Lexmark";
565 }
566 else
567 #endif /* __APPLE__ */
568 {
569 /*
570 * No manufacturer? Use the model string or description...
571 */
572
573 if (mdl)
574 _ppdNormalizeMakeAndModel(mdl, tempmfg, sizeof(tempmfg));
575 else if ((des = cupsGetOption("DESCRIPTION", num_values, values)) != NULL ||
576 (des = cupsGetOption("DES", num_values, values)) != NULL)
577 _ppdNormalizeMakeAndModel(des, tempmfg, sizeof(tempmfg));
578 else
579 strlcpy(tempmfg, "Unknown", sizeof(tempmfg));
580
581 if ((tempptr = strchr(tempmfg, ' ')) != NULL)
582 *tempptr = '\0';
583
584 mfg = tempmfg;
585 }
586
587 /*
588 * Generate the device URI from the manufacturer, model, serial number,
589 * and interface number...
590 */
591
592 if (sern)
593 {
594 if (printer->iface > 0)
595 snprintf(options, sizeof(options), "?serial=%s&interface=%d", sern,
596 printer->iface);
597 else
598 snprintf(options, sizeof(options), "?serial=%s", sern);
599 }
600 else if (printer->iface > 0)
601 snprintf(options, sizeof(options), "?interface=%d", printer->iface);
602 else
603 options[0] = '\0';
604
605 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, uri_size, "usb", NULL, mfg, 0,
606 "/%s%s", mdl, options);
607
608 cupsFreeOptions(num_values, values);
609
610 return (uri);
611 }
612
613
614 /*
615 * 'open_device()' - Open a connection to the USB printer.
616 */
617
618 static int /* O - 0 on success, -1 on error */
619 open_device(usb_printer_t *printer, /* I - Printer */
620 int verbose) /* I - Update connecting-to-device state? */
621 {
622 int number; /* Configuration/interface/altset numbers */
623
624
625 /*
626 * Return immediately if we are already connected...
627 */
628
629 if (printer->handle)
630 return (0);
631
632 /*
633 * Try opening the printer...
634 */
635
636 if ((printer->handle = usb_open(printer->device)) == NULL)
637 return (-1);
638
639 /*
640 * Then set the desired configuration...
641 */
642
643 if (verbose)
644 fputs("STATE: +connecting-to-device\n", stderr);
645
646 number = printer->device->config[printer->conf].bConfigurationValue;
647
648 if (usb_set_configuration(printer->handle, number) < 0)
649 {
650 /*
651 * If the set fails, chances are that the printer only supports a
652 * single configuration. Technically these printers don't conform to
653 * the USB printer specification, but otherwise they'll work...
654 */
655
656 if (errno != EBUSY)
657 fprintf(stderr, "DEBUG: Failed to set configuration %d for %04x:%04x\n",
658 number, printer->device->descriptor.idVendor,
659 printer->device->descriptor.idProduct);
660 }
661
662 /*
663 * Claim interfaces as needed...
664 */
665
666 number = printer->device->config[printer->conf].interface[printer->iface].
667 altsetting[printer->altset].bInterfaceNumber;
668 while (usb_claim_interface(printer->handle, number) < 0)
669 {
670 if (errno != EBUSY)
671 fprintf(stderr, "DEBUG: Failed to claim interface %d for %04x:%04x: %s\n",
672 number, printer->device->descriptor.idVendor,
673 printer->device->descriptor.idProduct, strerror(errno));
674
675 goto error;
676 }
677
678 if (number != 0)
679 while (usb_claim_interface(printer->handle, 0) < 0)
680 {
681 if (errno != EBUSY)
682 fprintf(stderr, "DEBUG: Failed to claim interface 0 for %04x:%04x: %s\n",
683 printer->device->descriptor.idVendor,
684 printer->device->descriptor.idProduct, strerror(errno));
685
686 goto error;
687 }
688
689 /*
690 * Set alternate setting...
691 */
692
693 number = printer->device->config[printer->conf].interface[printer->iface].
694 altsetting[printer->altset].bAlternateSetting;
695 while (usb_set_altinterface(printer->handle, number) < 0)
696 {
697 if (errno != EBUSY)
698 fprintf(stderr,
699 "DEBUG: Failed to set alternate interface %d for %04x:%04x: %s\n",
700 number, printer->device->descriptor.idVendor,
701 printer->device->descriptor.idProduct, strerror(errno));
702
703 goto error;
704 }
705
706 if (verbose)
707 fputs("STATE: -connecting-to-device\n", stderr);
708
709 return (0);
710
711 /*
712 * If we get here, there was a hard error...
713 */
714
715 error:
716
717 if (verbose)
718 fputs("STATE: -connecting-to-device\n", stderr);
719
720 usb_close(printer->handle);
721 printer->handle = NULL;
722
723 return (-1);
724 }
725
726
727 /*
728 * 'print_cb()' - Find a USB printer for printing.
729 */
730
731 static int /* O - 0 to continue, 1 to stop (found) */
732 print_cb(usb_printer_t *printer, /* I - Printer */
733 const char *device_uri, /* I - Device URI */
734 const char *device_id, /* I - IEEE-1284 device ID */
735 const void *data) /* I - User data (make, model, S/N) */
736 {
737 return (!strcmp((char *)data, device_uri));
738 }
739
740
741 /*
742 * 'side_cb()' - Handle side-channel requests.
743 */
744
745 static ssize_t /* O - Number of bytes written */
746 side_cb(usb_printer_t *printer, /* I - Printer */
747 int print_fd) /* I - File to print */
748 {
749 ssize_t bytes, /* Bytes read/written */
750 tbytes; /* Total bytes written */
751 char buffer[8192]; /* Print data buffer */
752 struct pollfd pfd; /* Poll descriptor */
753 cups_sc_command_t command; /* Request command */
754 cups_sc_status_t status; /* Request/response status */
755 char data[2048]; /* Request/response data */
756 int datalen; /* Request/response data size */
757
758
759 tbytes = 0;
760 datalen = sizeof(data);
761
762 if (cupsSideChannelRead(&command, &status, data, &datalen, 1.0))
763 return (-1);
764
765 switch (command)
766 {
767 case CUPS_SC_CMD_DRAIN_OUTPUT :
768 pfd.fd = print_fd;
769 pfd.events = POLLIN;
770
771 while (poll(&pfd, 1, 1000) > 0)
772 {
773 if ((bytes = read(print_fd, buffer, sizeof(buffer))) > 0)
774 {
775 while (usb_bulk_write(printer->handle, printer->write_endp, buffer,
776 bytes, 5000) < 0)
777 {
778 _cupsLangPrintf(stderr,
779 _("ERROR: Unable to write %d bytes to printer\n"),
780 (int)bytes);
781 tbytes = -1;
782 break;
783 }
784
785 tbytes += bytes;
786 }
787 else if (bytes < 0 && errno != EAGAIN && errno != EINTR)
788 break;
789 }
790
791 if (tbytes < 0)
792 status = CUPS_SC_STATUS_IO_ERROR;
793 else
794 status = CUPS_SC_STATUS_OK;
795
796 datalen = 0;
797 break;
798
799 case CUPS_SC_CMD_GET_BIDI :
800 status = CUPS_SC_STATUS_OK;
801 data[0] = 0; /* TODO: Change to 1 when read supported */
802 datalen = 1;
803 break;
804
805 case CUPS_SC_CMD_GET_DEVICE_ID :
806 if (get_device_id(printer, data, sizeof(data)))
807 {
808 status = CUPS_SC_STATUS_IO_ERROR;
809 datalen = 0;
810 }
811 else
812 {
813 status = CUPS_SC_STATUS_OK;
814 datalen = strlen(data);
815 }
816 break;
817
818 default :
819 status = CUPS_SC_STATUS_NOT_IMPLEMENTED;
820 datalen = 0;
821 break;
822 }
823
824 cupsSideChannelWrite(command, status, data, datalen, 1.0);
825
826 return (tbytes);
827 }
828
829
830 /*
831 * End of "$Id$".
832 */
833