Make use of the card's extended capabilities.
[gnupg.git] / tools / ccidmon.c
blobb8546407eabe01cb96cf1f5b445d2e0bf73fe939
1 /* ccidmon.c - CCID monitor for use with the Linux usbmon facility.
2 * Copyright (C) 2009 Free Software Foundation, Inc.
4 * This file is part of GnuPG.
6 * GnuPG is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * GnuPG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
21 /* This utility takes the output of usbmon, filters out the bulk data
22 and prints the CCID messages in a human friendly way.
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <stddef.h>
33 #include <string.h>
34 #include <errno.h>
35 #include <stdarg.h>
36 #include <assert.h>
37 #include <unistd.h>
38 #include <signal.h>
41 #ifndef PACKAGE_VERSION
42 # define PACKAGE_VERSION "[build on " __DATE__ " " __TIME__ "]"
43 #endif
44 #ifndef PACKAGE_BUGREPORT
45 # define PACKAGE_BUGREPORT "devnull@example.org"
46 #endif
47 #define PGM "ccidmon"
49 /* Option flags. */
50 static int verbose;
51 static int debug;
52 static int skip_escape;
53 static int usb_bus, usb_dev;
54 static int sniffusb;
57 /* Error counter. */
58 static int any_error;
60 /* Data storage. */
61 struct
63 int is_bi;
64 char address[50];
65 int count;
66 char data[2000];
67 } databuffer;
70 enum {
71 RDR_to_PC_NotifySlotChange= 0x50,
72 RDR_to_PC_HardwareError = 0x51,
74 PC_to_RDR_SetParameters = 0x61,
75 PC_to_RDR_IccPowerOn = 0x62,
76 PC_to_RDR_IccPowerOff = 0x63,
77 PC_to_RDR_GetSlotStatus = 0x65,
78 PC_to_RDR_Secure = 0x69,
79 PC_to_RDR_T0APDU = 0x6a,
80 PC_to_RDR_Escape = 0x6b,
81 PC_to_RDR_GetParameters = 0x6c,
82 PC_to_RDR_ResetParameters = 0x6d,
83 PC_to_RDR_IccClock = 0x6e,
84 PC_to_RDR_XfrBlock = 0x6f,
85 PC_to_RDR_Mechanical = 0x71,
86 PC_to_RDR_Abort = 0x72,
87 PC_to_RDR_SetDataRate = 0x73,
89 RDR_to_PC_DataBlock = 0x80,
90 RDR_to_PC_SlotStatus = 0x81,
91 RDR_to_PC_Parameters = 0x82,
92 RDR_to_PC_Escape = 0x83,
93 RDR_to_PC_DataRate = 0x84
97 #define digitp(p) ((p) >= '0' && (p) <= '9')
98 #define hexdigitp(a) (digitp (a) \
99 || ((a) >= 'A' && (a) <= 'F') \
100 || ((a) >= 'a' && (a) <= 'f'))
101 #define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t')
102 #define xtoi_1(p) ((p) <= '9'? ((p)- '0'): \
103 (p) <= 'F'? ((p)-'A'+10):((p)-'a'+10))
107 /* Print diagnostic message and exit with failure. */
108 static void
109 die (const char *format, ...)
111 va_list arg_ptr;
113 fflush (stdout);
114 fprintf (stderr, "%s: ", PGM);
116 va_start (arg_ptr, format);
117 vfprintf (stderr, format, arg_ptr);
118 va_end (arg_ptr);
119 putc ('\n', stderr);
121 exit (1);
125 /* Print diagnostic message. */
126 static void
127 err (const char *format, ...)
129 va_list arg_ptr;
131 any_error = 1;
133 fflush (stdout);
134 fprintf (stderr, "%s: ", PGM);
136 va_start (arg_ptr, format);
137 vfprintf (stderr, format, arg_ptr);
138 va_end (arg_ptr);
139 putc ('\n', stderr);
143 /* Convert a little endian stored 4 byte value into an unsigned
144 integer. */
145 static unsigned int
146 convert_le_u32 (const unsigned char *buf)
148 return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
152 /* Convert a little endian stored 2 byte value into an unsigned
153 integer. */
154 static unsigned int
155 convert_le_u16 (const unsigned char *buf)
157 return buf[0] | (buf[1] << 8);
163 static void
164 print_pr_data (const unsigned char *data, size_t datalen, size_t off)
166 int needlf = 0;
167 int first = 1;
169 for (; off < datalen; off++)
171 if (!(off % 16) || first)
173 if (needlf)
174 putchar ('\n');
175 printf (" [%04d] ", off);
177 printf (" %02X", data[off]);
178 needlf = 1;
179 first = 0;
181 if (needlf)
182 putchar ('\n');
186 static void
187 print_p2r_header (const char *name, const unsigned char *msg, size_t msglen)
189 printf ("%s:\n", name);
190 if (msglen < 7)
191 return;
192 printf (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
193 printf (" bSlot .............: %u\n", msg[5]);
194 printf (" bSeq ..............: %u\n", msg[6]);
198 static void
199 print_p2r_iccpoweron (const unsigned char *msg, size_t msglen)
201 print_p2r_header ("PC_to_RDR_IccPowerOn", msg, msglen);
202 if (msglen < 10)
203 return;
204 printf (" bPowerSelect ......: 0x%02x (%s)\n", msg[7],
205 msg[7] == 0? "auto":
206 msg[7] == 1? "5.0 V":
207 msg[7] == 2? "3.0 V":
208 msg[7] == 3? "1.8 V":"");
209 print_pr_data (msg, msglen, 8);
213 static void
214 print_p2r_iccpoweroff (const unsigned char *msg, size_t msglen)
216 print_p2r_header ("PC_to_RDR_IccPowerOff", msg, msglen);
217 print_pr_data (msg, msglen, 7);
221 static void
222 print_p2r_getslotstatus (const unsigned char *msg, size_t msglen)
224 print_p2r_header ("PC_to_RDR_GetSlotStatus", msg, msglen);
225 print_pr_data (msg, msglen, 7);
229 static void
230 print_p2r_xfrblock (const unsigned char *msg, size_t msglen)
232 unsigned int val;
234 print_p2r_header ("PC_to_RDR_XfrBlock", msg, msglen);
235 if (msglen < 10)
236 return;
237 printf (" bBWI ..............: 0x%02x\n", msg[7]);
238 val = convert_le_u16 (msg+8);
239 printf (" wLevelParameter ...: 0x%04x%s\n", val,
240 val == 1? " (continued)":
241 val == 2? " (continues+ends)":
242 val == 3? " (continues+continued)":
243 val == 16? " (DataBlock-expected)":"");
244 print_pr_data (msg, msglen, 10);
248 static void
249 print_p2r_getparameters (const unsigned char *msg, size_t msglen)
251 print_p2r_header ("PC_to_RDR_GetParameters", msg, msglen);
252 print_pr_data (msg, msglen, 7);
256 static void
257 print_p2r_resetparameters (const unsigned char *msg, size_t msglen)
259 print_p2r_header ("PC_to_RDR_ResetParameters", msg, msglen);
260 print_pr_data (msg, msglen, 7);
264 static void
265 print_p2r_setparameters (const unsigned char *msg, size_t msglen)
267 print_p2r_header ("PC_to_RDR_SetParameters", msg, msglen);
268 if (msglen < 10)
269 return;
270 printf (" bProtocolNum ......: 0x%02x\n", msg[7]);
271 print_pr_data (msg, msglen, 8);
275 static void
276 print_p2r_escape (const unsigned char *msg, size_t msglen)
278 if (skip_escape)
279 return;
280 print_p2r_header ("PC_to_RDR_Escape", msg, msglen);
281 print_pr_data (msg, msglen, 7);
285 static void
286 print_p2r_iccclock (const unsigned char *msg, size_t msglen)
288 print_p2r_header ("PC_to_RDR_IccClock", msg, msglen);
289 if (msglen < 10)
290 return;
291 printf (" bClockCommand .....: 0x%02x\n", msg[7]);
292 print_pr_data (msg, msglen, 8);
296 static void
297 print_p2r_to0apdu (const unsigned char *msg, size_t msglen)
299 print_p2r_header ("PC_to_RDR_T0APDU", msg, msglen);
300 if (msglen < 10)
301 return;
302 printf (" bmChanges .........: 0x%02x\n", msg[7]);
303 printf (" bClassGetResponse .: 0x%02x\n", msg[8]);
304 printf (" bClassEnvelope ....: 0x%02x\n", msg[9]);
305 print_pr_data (msg, msglen, 10);
309 static void
310 print_p2r_secure (const unsigned char *msg, size_t msglen)
312 unsigned int val;
314 print_p2r_header ("PC_to_RDR_Secure", msg, msglen);
315 if (msglen < 10)
316 return;
317 printf (" bBMI ..............: 0x%02x\n", msg[7]);
318 val = convert_le_u16 (msg+8);
319 printf (" wLevelParameter ...: 0x%04x%s\n", val,
320 val == 1? " (continued)":
321 val == 2? " (continues+ends)":
322 val == 3? " (continues+continued)":
323 val == 16? " (DataBlock-expected)":"");
324 print_pr_data (msg, msglen, 10);
328 static void
329 print_p2r_mechanical (const unsigned char *msg, size_t msglen)
331 print_p2r_header ("PC_to_RDR_Mechanical", msg, msglen);
332 if (msglen < 10)
333 return;
334 printf (" bFunction .........: 0x%02x\n", msg[7]);
335 print_pr_data (msg, msglen, 8);
339 static void
340 print_p2r_abort (const unsigned char *msg, size_t msglen)
342 print_p2r_header ("PC_to_RDR_Abort", msg, msglen);
343 print_pr_data (msg, msglen, 7);
347 static void
348 print_p2r_setdatarate (const unsigned char *msg, size_t msglen)
350 print_p2r_header ("PC_to_RDR_SetDataRate", msg, msglen);
351 if (msglen < 10)
352 return;
353 print_pr_data (msg, msglen, 7);
357 static void
358 print_p2r_unknown (const unsigned char *msg, size_t msglen)
360 char buf[100];
362 snprintf (buf, sizeof buf, "Unknown PC_to_RDR command 0x%02X",
363 msglen? msg[0]:0);
364 print_p2r_header (buf, msg, msglen);
365 if (msglen < 10)
366 return;
367 print_pr_data (msg, msglen, 0);
371 static void
372 print_p2r (const unsigned char *msg, size_t msglen)
374 switch (msglen? msg[0]:0)
376 case PC_to_RDR_IccPowerOn:
377 print_p2r_iccpoweron (msg, msglen);
378 break;
379 case PC_to_RDR_IccPowerOff:
380 print_p2r_iccpoweroff (msg, msglen);
381 break;
382 case PC_to_RDR_GetSlotStatus:
383 print_p2r_getslotstatus (msg, msglen);
384 break;
385 case PC_to_RDR_XfrBlock:
386 print_p2r_xfrblock (msg, msglen);
387 break;
388 case PC_to_RDR_GetParameters:
389 print_p2r_getparameters (msg, msglen);
390 break;
391 case PC_to_RDR_ResetParameters:
392 print_p2r_resetparameters (msg, msglen);
393 break;
394 case PC_to_RDR_SetParameters:
395 print_p2r_setparameters (msg, msglen);
396 break;
397 case PC_to_RDR_Escape:
398 print_p2r_escape (msg, msglen);
399 break;
400 case PC_to_RDR_IccClock:
401 print_p2r_iccclock (msg, msglen);
402 break;
403 case PC_to_RDR_T0APDU:
404 print_p2r_to0apdu (msg, msglen);
405 break;
406 case PC_to_RDR_Secure:
407 print_p2r_secure (msg, msglen);
408 break;
409 case PC_to_RDR_Mechanical:
410 print_p2r_mechanical (msg, msglen);
411 break;
412 case PC_to_RDR_Abort:
413 print_p2r_abort (msg, msglen);
414 break;
415 case PC_to_RDR_SetDataRate:
416 print_p2r_setdatarate (msg, msglen);
417 break;
418 default:
419 print_p2r_unknown (msg, msglen);
420 break;
425 static void
426 print_r2p_header (const char *name, const unsigned char *msg, size_t msglen)
428 printf ("%s:\n", name);
429 if (msglen < 9)
430 return;
431 printf (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
432 printf (" bSlot .............: %u\n", msg[5]);
433 printf (" bSeq ..............: %u\n", msg[6]);
434 printf (" bStatus ...........: %u\n", msg[7]);
435 if (msg[8])
436 printf (" bError ............: %u\n", msg[8]);
440 static void
441 print_r2p_datablock (const unsigned char *msg, size_t msglen)
443 print_r2p_header ("RDR_to_PC_DataBlock", msg, msglen);
444 if (msglen < 10)
445 return;
446 if (msg[9])
447 printf (" bChainParameter ...: 0x%02x%s\n", msg[9],
448 msg[9] == 1? " (continued)":
449 msg[9] == 2? " (continues+ends)":
450 msg[9] == 3? " (continues+continued)":
451 msg[9] == 16? " (XferBlock-expected)":"");
452 print_pr_data (msg, msglen, 10);
456 static void
457 print_r2p_slotstatus (const unsigned char *msg, size_t msglen)
459 print_r2p_header ("RDR_to_PC_SlotStatus", msg, msglen);
460 if (msglen < 10)
461 return;
462 printf (" bClockStatus ......: 0x%02x%s\n", msg[9],
463 msg[9] == 0? " (running)":
464 msg[9] == 1? " (stopped-L)":
465 msg[9] == 2? " (stopped-H)":
466 msg[9] == 3? " (stopped)":"");
467 print_pr_data (msg, msglen, 10);
471 static void
472 print_r2p_parameters (const unsigned char *msg, size_t msglen)
474 print_r2p_header ("RDR_to_PC_Parameters", msg, msglen);
475 if (msglen < 10)
476 return;
478 printf (" protocol ..........: T=%d\n", msg[9]);
479 if (msglen == 17 && msg[9] == 1)
481 /* Protocol T=1. */
482 printf (" bmFindexDindex ....: %02X\n", msg[10]);
483 printf (" bmTCCKST1 .........: %02X\n", msg[11]);
484 printf (" bGuardTimeT1 ......: %02X\n", msg[12]);
485 printf (" bmWaitingIntegersT1: %02X\n", msg[13]);
486 printf (" bClockStop ........: %02X\n", msg[14]);
487 printf (" bIFSC .............: %d\n", msg[15]);
488 printf (" bNadValue .........: %d\n", msg[16]);
490 else
491 print_pr_data (msg, msglen, 10);
495 static void
496 print_r2p_escape (const unsigned char *msg, size_t msglen)
498 if (skip_escape)
499 return;
500 print_r2p_header ("RDR_to_PC_Escape", msg, msglen);
501 if (msglen < 10)
502 return;
503 printf (" buffer[9] .........: %02X\n", msg[9]);
504 print_pr_data (msg, msglen, 10);
508 static void
509 print_r2p_datarate (const unsigned char *msg, size_t msglen)
511 print_r2p_header ("RDR_to_PC_DataRate", msg, msglen);
512 if (msglen < 10)
513 return;
514 if (msglen >= 18)
516 printf (" dwClockFrequency ..: %u\n", convert_le_u32 (msg+10));
517 printf (" dwDataRate ..... ..: %u\n", convert_le_u32 (msg+14));
518 print_pr_data (msg, msglen, 18);
520 else
521 print_pr_data (msg, msglen, 10);
525 static void
526 print_r2p_unknown (const unsigned char *msg, size_t msglen)
528 char buf[100];
530 snprintf (buf, sizeof buf, "Unknown RDR_to_PC command 0x%02X",
531 msglen? msg[0]:0);
532 print_r2p_header (buf, msg, msglen);
533 if (msglen < 10)
534 return;
535 printf (" bMessageType ......: %02X\n", msg[0]);
536 printf (" buffer[9] .........: %02X\n", msg[9]);
537 print_pr_data (msg, msglen, 10);
541 static void
542 print_r2p (const unsigned char *msg, size_t msglen)
544 switch (msglen? msg[0]:0)
546 case RDR_to_PC_DataBlock:
547 print_r2p_datablock (msg, msglen);
548 break;
549 case RDR_to_PC_SlotStatus:
550 print_r2p_slotstatus (msg, msglen);
551 break;
552 case RDR_to_PC_Parameters:
553 print_r2p_parameters (msg, msglen);
554 break;
555 case RDR_to_PC_Escape:
556 print_r2p_escape (msg, msglen);
557 break;
558 case RDR_to_PC_DataRate:
559 print_r2p_datarate (msg, msglen);
560 break;
561 default:
562 print_r2p_unknown (msg, msglen);
563 break;
569 static void
570 flush_data (void)
572 if (!databuffer.count)
573 return;
575 if (verbose)
576 printf ("Address: %s\n", databuffer.address);
577 if (databuffer.is_bi)
579 print_r2p (databuffer.data, databuffer.count);
580 if (verbose)
581 putchar ('\n');
583 else
584 print_p2r (databuffer.data, databuffer.count);
586 databuffer.count = 0;
589 static void
590 collect_data (char *hexdata, const char *address, unsigned int lineno)
592 size_t length;
593 int is_bi;
594 char *s;
595 unsigned int value;
597 is_bi = (*address && address[1] == 'i');
599 if (databuffer.is_bi != is_bi || strcmp (databuffer.address, address))
600 flush_data ();
601 databuffer.is_bi = is_bi;
602 if (strlen (address) >= sizeof databuffer.address)
603 die ("address field too long");
604 strcpy (databuffer.address, address);
606 length = databuffer.count;
607 for (s=hexdata; *s; s++ )
609 if (ascii_isspace (*s))
610 continue;
611 if (!hexdigitp (*s))
613 err ("invalid hex digit in line %u - line skipped", lineno);
614 break;
616 value = xtoi_1 (*s) * 16;
617 s++;
618 if (!hexdigitp (*s))
620 err ("invalid hex digit in line %u - line skipped", lineno);
621 break;
623 value += xtoi_1 (*s);
625 if (length >= sizeof (databuffer.data))
627 err ("too much data at line %u - can handle only up to % bytes",
628 lineno, sizeof (databuffer.data));
629 break;
631 databuffer.data[length++] = value;
633 databuffer.count = length;
637 static void
638 parse_line (char *line, unsigned int lineno)
640 char *p;
641 char *event_type, *address, *data, *status, *datatag;
643 if (debug)
644 printf ("line[%u] =`%s'\n", lineno, line);
646 p = strtok (line, " ");
647 if (!p)
648 die ("invalid line %d (no URB)");
649 p = strtok (NULL, " ");
650 if (!p)
651 die ("invalid line %d (no timestamp)");
652 event_type = strtok (NULL, " ");
653 if (!event_type)
654 die ("invalid line %d (no event type)");
655 address = strtok (NULL, " ");
656 if (!address)
657 die ("invalid line %d (no address");
658 if (usb_bus || usb_dev)
660 int bus, dev;
662 p = strchr (address, ':');
663 if (!p)
664 die ("invalid line %d (invalid address");
665 p++;
666 bus = atoi (p);
667 p = strchr (p, ':');
668 if (!p)
669 die ("invalid line %d (invalid address");
670 p++;
671 dev = atoi (p);
673 if ((usb_bus && usb_bus != bus) || (usb_dev && usb_dev != dev))
674 return; /* We don't want that one. */
676 if (*address != 'B' || (address[1] != 'o' && address[1] != 'i'))
677 return; /* We only want block in and block out. */
678 status = strtok (NULL, " ");
679 if (!status)
680 return;
681 if (!strchr ("-0123456789", *status))
682 return; /* Setup packet. */
683 /* We don't support "Z[io]" types thus we don't need to check here. */
684 p = strtok (NULL, " ");
685 if (!p)
686 return; /* No data length. */
688 datatag = strtok (NULL, " ");
689 if (datatag && *datatag == '=')
691 data = strtok (NULL, "");
692 collect_data (data?data:"", address, lineno);
697 static void
698 parse_line_sniffusb (char *line, unsigned int lineno)
700 char *p;
702 if (debug)
703 printf ("line[%u] =`%s'\n", lineno, line);
705 p = strtok (line, " \t");
706 if (!p)
707 return;
708 p = strtok (NULL, " \t");
709 if (!p)
710 return;
711 p = strtok (NULL, " \t");
712 if (!p)
713 return;
715 if (hexdigitp (p[0]) && hexdigitp (p[1])
716 && hexdigitp (p[2]) && hexdigitp (p[3])
717 && p[4] == ':' && !p[5])
719 size_t length;
720 unsigned int value;
722 length = databuffer.count;
723 while ((p=strtok (NULL, " \t")))
725 if (!hexdigitp (p[0]) || !hexdigitp (p[1]))
727 err ("invalid hex digit in line %u (%s)", lineno,p);
728 break;
730 value = xtoi_1 (p[0]) * 16 + xtoi_1 (p[1]);
732 if (length >= sizeof (databuffer.data))
734 err ("too much data at line %u - can handle only up to % bytes",
735 lineno, sizeof (databuffer.data));
736 break;
738 databuffer.data[length++] = value;
740 databuffer.count = length;
743 else if (!strcmp (p, "TransferFlags"))
745 flush_data ();
747 *databuffer.address = 0;
748 while ((p=strtok (NULL, " \t(,)")))
750 if (!strcmp (p, "USBD_TRANSFER_DIRECTION_IN"))
752 databuffer.is_bi = 1;
753 break;
755 else if (!strcmp (p, "USBD_TRANSFER_DIRECTION_OUT"))
757 databuffer.is_bi = 0;
758 break;
766 static void
767 parse_input (FILE *fp)
769 char line[2000];
770 size_t length;
771 unsigned int lineno = 0;
773 while (fgets (line, sizeof (line), fp))
775 lineno++;
776 length = strlen (line);
777 if (length && line[length - 1] == '\n')
778 line[--length] = 0;
779 else
780 err ("line number %u too long or last line not terminated", lineno);
781 if (length && line[length - 1] == '\r')
782 line[--length] = 0;
783 if (sniffusb)
784 parse_line_sniffusb (line, lineno);
785 else
786 parse_line (line, lineno);
788 flush_data ();
789 if (ferror (fp))
790 err ("error reading input at line %u: %s", lineno, strerror (errno));
794 int
795 main (int argc, char **argv)
797 int last_argc = -1;
799 if (argc)
801 argc--; argv++;
803 while (argc && last_argc != argc )
805 last_argc = argc;
806 if (!strcmp (*argv, "--"))
808 argc--; argv++;
809 break;
811 else if (!strcmp (*argv, "--version"))
813 fputs (PGM " (GnuPG) " PACKAGE_VERSION "\n", stdout);
814 exit (0);
816 else if (!strcmp (*argv, "--help"))
818 puts ("Usage: " PGM " [BUS:DEV]\n"
819 "Parse the output of usbmod assuming it is CCID compliant.\n\n"
820 " --skip-escape do not show escape packets\n"
821 " --sniffusb Assume output from Sniffusb.exe\n"
822 " --verbose enable extra informational output\n"
823 " --debug enable additional debug output\n"
824 " --help display this help and exit\n\n"
825 "Report bugs to " PACKAGE_BUGREPORT ".");
826 exit (0);
828 else if (!strcmp (*argv, "--verbose"))
830 verbose = 1;
831 argc--; argv++;
833 else if (!strcmp (*argv, "--debug"))
835 verbose = debug = 1;
836 argc--; argv++;
838 else if (!strcmp (*argv, "--skip-escape"))
840 skip_escape = 1;
841 argc--; argv++;
843 else if (!strcmp (*argv, "--sniffusb"))
845 sniffusb = 1;
846 argc--; argv++;
850 if (argc && sniffusb)
851 die ("no arguments expected when using --sniffusb\n");
852 else if (argc > 1)
853 die ("usage: " PGM " [BUS:DEV] (try --help for more information)\n");
855 if (argc == 1)
857 const char *s = strchr (argv[0], ':');
859 usb_bus = atoi (argv[0]);
860 if (s)
861 usb_dev = atoi (s+1);
862 if (usb_bus < 1 || usb_bus > 999 || usb_dev < 1 || usb_dev > 999)
863 die ("invalid bus:dev specified");
867 signal (SIGPIPE, SIG_IGN);
869 parse_input (stdin);
871 return any_error? 1:0;
876 Local Variables:
877 compile-command: "gcc -Wall -Wno-pointer-sign -g -o ccidmon ccidmon.c"
878 End: