Home | History | Annotate | Download | only in toolbox
      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <string.h>
      4 #include <stdint.h>
      5 #include <dirent.h>
      6 #include <fcntl.h>
      7 #include <sys/ioctl.h>
      8 #include <sys/inotify.h>
      9 #include <sys/limits.h>
     10 #include <sys/poll.h>
     11 #include <linux/input.h>
     12 #include <errno.h>
     13 #include <unistd.h>
     14 
     15 struct label {
     16     const char *name;
     17     int value;
     18 };
     19 
     20 #define LABEL(constant) { #constant, constant }
     21 #define LABEL_END { NULL, -1 }
     22 
     23 static struct label key_value_labels[] = {
     24         { "UP", 0 },
     25         { "DOWN", 1 },
     26         { "REPEAT", 2 },
     27         LABEL_END,
     28 };
     29 
     30 #include "input.h-labels.h"
     31 
     32 #undef LABEL
     33 #undef LABEL_END
     34 
     35 static struct pollfd *ufds;
     36 static char **device_names;
     37 static int nfds;
     38 
     39 enum {
     40     PRINT_DEVICE_ERRORS     = 1U << 0,
     41     PRINT_DEVICE            = 1U << 1,
     42     PRINT_DEVICE_NAME       = 1U << 2,
     43     PRINT_DEVICE_INFO       = 1U << 3,
     44     PRINT_VERSION           = 1U << 4,
     45     PRINT_POSSIBLE_EVENTS   = 1U << 5,
     46     PRINT_INPUT_PROPS       = 1U << 6,
     47     PRINT_HID_DESCRIPTOR    = 1U << 7,
     48 
     49     PRINT_ALL_INFO          = (1U << 8) - 1,
     50 
     51     PRINT_LABELS            = 1U << 16,
     52 };
     53 
     54 static const char *get_label(const struct label *labels, int value)
     55 {
     56     while(labels->name && value != labels->value) {
     57         labels++;
     58     }
     59     return labels->name;
     60 }
     61 
     62 static int print_input_props(int fd)
     63 {
     64     uint8_t bits[INPUT_PROP_CNT / 8];
     65     int i, j;
     66     int res;
     67     int count;
     68     const char *bit_label;
     69 
     70     printf("  input props:\n");
     71     res = ioctl(fd, EVIOCGPROP(sizeof(bits)), bits);
     72     if(res < 0) {
     73         printf("    <not available\n");
     74         return 1;
     75     }
     76     count = 0;
     77     for(i = 0; i < res; i++) {
     78         for(j = 0; j < 8; j++) {
     79             if (bits[i] & 1 << j) {
     80                 bit_label = get_label(input_prop_labels, i * 8 + j);
     81                 if(bit_label)
     82                     printf("    %s\n", bit_label);
     83                 else
     84                     printf("    %04x\n", i * 8 + j);
     85                 count++;
     86             }
     87         }
     88     }
     89     if (!count)
     90         printf("    <none>\n");
     91     return 0;
     92 }
     93 
     94 static int print_possible_events(int fd, int print_flags)
     95 {
     96     uint8_t *bits = NULL;
     97     ssize_t bits_size = 0;
     98     const char* label;
     99     int i, j, k;
    100     int res, res2;
    101     struct label* bit_labels;
    102     const char *bit_label;
    103 
    104     printf("  events:\n");
    105     for(i = EV_KEY; i <= EV_MAX; i++) { // skip EV_SYN since we cannot query its available codes
    106         int count = 0;
    107         while(1) {
    108             res = ioctl(fd, EVIOCGBIT(i, bits_size), bits);
    109             if(res < bits_size)
    110                 break;
    111             bits_size = res + 16;
    112             bits = realloc(bits, bits_size * 2);
    113             if(bits == NULL) {
    114                 fprintf(stderr, "failed to allocate buffer of size %d\n", (int)bits_size);
    115                 return 1;
    116             }
    117         }
    118         res2 = 0;
    119         switch(i) {
    120             case EV_KEY:
    121                 res2 = ioctl(fd, EVIOCGKEY(res), bits + bits_size);
    122                 label = "KEY";
    123                 bit_labels = key_labels;
    124                 break;
    125             case EV_REL:
    126                 label = "REL";
    127                 bit_labels = rel_labels;
    128                 break;
    129             case EV_ABS:
    130                 label = "ABS";
    131                 bit_labels = abs_labels;
    132                 break;
    133             case EV_MSC:
    134                 label = "MSC";
    135                 bit_labels = msc_labels;
    136                 break;
    137             case EV_LED:
    138                 res2 = ioctl(fd, EVIOCGLED(res), bits + bits_size);
    139                 label = "LED";
    140                 bit_labels = led_labels;
    141                 break;
    142             case EV_SND:
    143                 res2 = ioctl(fd, EVIOCGSND(res), bits + bits_size);
    144                 label = "SND";
    145                 bit_labels = snd_labels;
    146                 break;
    147             case EV_SW:
    148                 res2 = ioctl(fd, EVIOCGSW(bits_size), bits + bits_size);
    149                 label = "SW ";
    150                 bit_labels = sw_labels;
    151                 break;
    152             case EV_REP:
    153                 label = "REP";
    154                 bit_labels = rep_labels;
    155                 break;
    156             case EV_FF:
    157                 label = "FF ";
    158                 bit_labels = ff_labels;
    159                 break;
    160             case EV_PWR:
    161                 label = "PWR";
    162                 bit_labels = NULL;
    163                 break;
    164             case EV_FF_STATUS:
    165                 label = "FFS";
    166                 bit_labels = ff_status_labels;
    167                 break;
    168             default:
    169                 res2 = 0;
    170                 label = "???";
    171                 bit_labels = NULL;
    172         }
    173         for(j = 0; j < res; j++) {
    174             for(k = 0; k < 8; k++)
    175                 if(bits[j] & 1 << k) {
    176                     char down;
    177                     if(j < res2 && (bits[j + bits_size] & 1 << k))
    178                         down = '*';
    179                     else
    180                         down = ' ';
    181                     if(count == 0)
    182                         printf("    %s (%04x):", label, i);
    183                     else if((count & (print_flags & PRINT_LABELS ? 0x3 : 0x7)) == 0 || i == EV_ABS)
    184                         printf("\n               ");
    185                     if(bit_labels && (print_flags & PRINT_LABELS)) {
    186                         bit_label = get_label(bit_labels, j * 8 + k);
    187                         if(bit_label)
    188                             printf(" %.20s%c%*s", bit_label, down, (int) (20 - strlen(bit_label)), "");
    189                         else
    190                             printf(" %04x%c                ", j * 8 + k, down);
    191                     } else {
    192                         printf(" %04x%c", j * 8 + k, down);
    193                     }
    194                     if(i == EV_ABS) {
    195                         struct input_absinfo abs;
    196                         if(ioctl(fd, EVIOCGABS(j * 8 + k), &abs) == 0) {
    197                             printf(" : value %d, min %d, max %d, fuzz %d, flat %d, resolution %d",
    198                                 abs.value, abs.minimum, abs.maximum, abs.fuzz, abs.flat,
    199                                 abs.resolution);
    200                         }
    201                     }
    202                     count++;
    203                 }
    204         }
    205         if(count)
    206             printf("\n");
    207     }
    208     free(bits);
    209     return 0;
    210 }
    211 
    212 static void print_event(int type, int code, int value, int print_flags)
    213 {
    214     const char *type_label, *code_label, *value_label;
    215 
    216     if (print_flags & PRINT_LABELS) {
    217         type_label = get_label(ev_labels, type);
    218         code_label = NULL;
    219         value_label = NULL;
    220 
    221         switch(type) {
    222             case EV_SYN:
    223                 code_label = get_label(syn_labels, code);
    224                 break;
    225             case EV_KEY:
    226                 code_label = get_label(key_labels, code);
    227                 value_label = get_label(key_value_labels, value);
    228                 break;
    229             case EV_REL:
    230                 code_label = get_label(rel_labels, code);
    231                 break;
    232             case EV_ABS:
    233                 code_label = get_label(abs_labels, code);
    234                 switch(code) {
    235                     case ABS_MT_TOOL_TYPE:
    236                         value_label = get_label(mt_tool_labels, value);
    237                 }
    238                 break;
    239             case EV_MSC:
    240                 code_label = get_label(msc_labels, code);
    241                 break;
    242             case EV_LED:
    243                 code_label = get_label(led_labels, code);
    244                 break;
    245             case EV_SND:
    246                 code_label = get_label(snd_labels, code);
    247                 break;
    248             case EV_SW:
    249                 code_label = get_label(sw_labels, code);
    250                 break;
    251             case EV_REP:
    252                 code_label = get_label(rep_labels, code);
    253                 break;
    254             case EV_FF:
    255                 code_label = get_label(ff_labels, code);
    256                 break;
    257             case EV_FF_STATUS:
    258                 code_label = get_label(ff_status_labels, code);
    259                 break;
    260         }
    261 
    262         if (type_label)
    263             printf("%-12.12s", type_label);
    264         else
    265             printf("%04x        ", type);
    266         if (code_label)
    267             printf(" %-20.20s", code_label);
    268         else
    269             printf(" %04x                ", code);
    270         if (value_label)
    271             printf(" %-20.20s", value_label);
    272         else
    273             printf(" %08x            ", value);
    274     } else {
    275         printf("%04x %04x %08x", type, code, value);
    276     }
    277 }
    278 
    279 static void print_hid_descriptor(int bus, int vendor, int product)
    280 {
    281     const char *dirname = "/sys/kernel/debug/hid";
    282     char prefix[16];
    283     DIR *dir;
    284     struct dirent *de;
    285     char filename[PATH_MAX];
    286     FILE *file;
    287     char line[2048];
    288 
    289     snprintf(prefix, sizeof(prefix), "%04X:%04X:%04X.", bus, vendor, product);
    290 
    291     dir = opendir(dirname);
    292     if(dir == NULL)
    293         return;
    294     while((de = readdir(dir))) {
    295         if (strstr(de->d_name, prefix) == de->d_name) {
    296             snprintf(filename, sizeof(filename), "%s/%s/rdesc", dirname, de->d_name);
    297 
    298             file = fopen(filename, "r");
    299             if (file) {
    300                 printf("  HID descriptor: %s\n\n", de->d_name);
    301                 while (fgets(line, sizeof(line), file)) {
    302                     fputs("    ", stdout);
    303                     fputs(line, stdout);
    304                 }
    305                 fclose(file);
    306                 puts("");
    307             }
    308         }
    309     }
    310     closedir(dir);
    311 }
    312 
    313 static int open_device(const char *device, int print_flags)
    314 {
    315     int version;
    316     int fd;
    317     int clkid = CLOCK_MONOTONIC;
    318     struct pollfd *new_ufds;
    319     char **new_device_names;
    320     char name[80];
    321     char location[80];
    322     char idstr[80];
    323     struct input_id id;
    324 
    325     fd = open(device, O_RDWR);
    326     if(fd < 0) {
    327         if(print_flags & PRINT_DEVICE_ERRORS)
    328             fprintf(stderr, "could not open %s, %s\n", device, strerror(errno));
    329         return -1;
    330     }
    331 
    332     if(ioctl(fd, EVIOCGVERSION, &version)) {
    333         if(print_flags & PRINT_DEVICE_ERRORS)
    334             fprintf(stderr, "could not get driver version for %s, %s\n", device, strerror(errno));
    335         return -1;
    336     }
    337     if(ioctl(fd, EVIOCGID, &id)) {
    338         if(print_flags & PRINT_DEVICE_ERRORS)
    339             fprintf(stderr, "could not get driver id for %s, %s\n", device, strerror(errno));
    340         return -1;
    341     }
    342     name[sizeof(name) - 1] = '\0';
    343     location[sizeof(location) - 1] = '\0';
    344     idstr[sizeof(idstr) - 1] = '\0';
    345     if(ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) {
    346         //fprintf(stderr, "could not get device name for %s, %s\n", device, strerror(errno));
    347         name[0] = '\0';
    348     }
    349     if(ioctl(fd, EVIOCGPHYS(sizeof(location) - 1), &location) < 1) {
    350         //fprintf(stderr, "could not get location for %s, %s\n", device, strerror(errno));
    351         location[0] = '\0';
    352     }
    353     if(ioctl(fd, EVIOCGUNIQ(sizeof(idstr) - 1), &idstr) < 1) {
    354         //fprintf(stderr, "could not get idstring for %s, %s\n", device, strerror(errno));
    355         idstr[0] = '\0';
    356     }
    357 
    358     if (ioctl(fd, EVIOCSCLOCKID, &clkid) != 0) {
    359         fprintf(stderr, "Can't enable monotonic clock reporting: %s\n", strerror(errno));
    360         // a non-fatal error
    361     }
    362 
    363     new_ufds = realloc(ufds, sizeof(ufds[0]) * (nfds + 1));
    364     if(new_ufds == NULL) {
    365         fprintf(stderr, "out of memory\n");
    366         return -1;
    367     }
    368     ufds = new_ufds;
    369     new_device_names = realloc(device_names, sizeof(device_names[0]) * (nfds + 1));
    370     if(new_device_names == NULL) {
    371         fprintf(stderr, "out of memory\n");
    372         return -1;
    373     }
    374     device_names = new_device_names;
    375 
    376     if(print_flags & PRINT_DEVICE)
    377         printf("add device %d: %s\n", nfds, device);
    378     if(print_flags & PRINT_DEVICE_INFO)
    379         printf("  bus:      %04x\n"
    380                "  vendor    %04x\n"
    381                "  product   %04x\n"
    382                "  version   %04x\n",
    383                id.bustype, id.vendor, id.product, id.version);
    384     if(print_flags & PRINT_DEVICE_NAME)
    385         printf("  name:     \"%s\"\n", name);
    386     if(print_flags & PRINT_DEVICE_INFO)
    387         printf("  location: \"%s\"\n"
    388                "  id:       \"%s\"\n", location, idstr);
    389     if(print_flags & PRINT_VERSION)
    390         printf("  version:  %d.%d.%d\n",
    391                version >> 16, (version >> 8) & 0xff, version & 0xff);
    392 
    393     if(print_flags & PRINT_POSSIBLE_EVENTS) {
    394         print_possible_events(fd, print_flags);
    395     }
    396 
    397     if(print_flags & PRINT_INPUT_PROPS) {
    398         print_input_props(fd);
    399     }
    400     if(print_flags & PRINT_HID_DESCRIPTOR) {
    401         print_hid_descriptor(id.bustype, id.vendor, id.product);
    402     }
    403 
    404     ufds[nfds].fd = fd;
    405     ufds[nfds].events = POLLIN;
    406     device_names[nfds] = strdup(device);
    407     nfds++;
    408 
    409     return 0;
    410 }
    411 
    412 int close_device(const char *device, int print_flags)
    413 {
    414     int i;
    415     for(i = 1; i < nfds; i++) {
    416         if(strcmp(device_names[i], device) == 0) {
    417             int count = nfds - i - 1;
    418             if(print_flags & PRINT_DEVICE)
    419                 printf("remove device %d: %s\n", i, device);
    420             free(device_names[i]);
    421             memmove(device_names + i, device_names + i + 1, sizeof(device_names[0]) * count);
    422             memmove(ufds + i, ufds + i + 1, sizeof(ufds[0]) * count);
    423             nfds--;
    424             return 0;
    425         }
    426     }
    427     if(print_flags & PRINT_DEVICE_ERRORS)
    428         fprintf(stderr, "remote device: %s not found\n", device);
    429     return -1;
    430 }
    431 
    432 static int read_notify(const char *dirname, int nfd, int print_flags)
    433 {
    434     int res;
    435     char devname[PATH_MAX];
    436     char *filename;
    437     char event_buf[512];
    438     int event_size;
    439     int event_pos = 0;
    440     struct inotify_event *event;
    441 
    442     res = read(nfd, event_buf, sizeof(event_buf));
    443     if(res < (int)sizeof(*event)) {
    444         if(errno == EINTR)
    445             return 0;
    446         fprintf(stderr, "could not get event, %s\n", strerror(errno));
    447         return 1;
    448     }
    449     //printf("got %d bytes of event information\n", res);
    450 
    451     strcpy(devname, dirname);
    452     filename = devname + strlen(devname);
    453     *filename++ = '/';
    454 
    455     while(res >= (int)sizeof(*event)) {
    456         event = (struct inotify_event *)(event_buf + event_pos);
    457         //printf("%d: %08x \"%s\"\n", event->wd, event->mask, event->len ? event->name : "");
    458         if(event->len) {
    459             strcpy(filename, event->name);
    460             if(event->mask & IN_CREATE) {
    461                 open_device(devname, print_flags);
    462             }
    463             else {
    464                 close_device(devname, print_flags);
    465             }
    466         }
    467         event_size = sizeof(*event) + event->len;
    468         res -= event_size;
    469         event_pos += event_size;
    470     }
    471     return 0;
    472 }
    473 
    474 static int scan_dir(const char *dirname, int print_flags)
    475 {
    476     char devname[PATH_MAX];
    477     char *filename;
    478     DIR *dir;
    479     struct dirent *de;
    480     dir = opendir(dirname);
    481     if(dir == NULL)
    482         return -1;
    483     strcpy(devname, dirname);
    484     filename = devname + strlen(devname);
    485     *filename++ = '/';
    486     while((de = readdir(dir))) {
    487         if(de->d_name[0] == '.' &&
    488            (de->d_name[1] == '\0' ||
    489             (de->d_name[1] == '.' && de->d_name[2] == '\0')))
    490             continue;
    491         strcpy(filename, de->d_name);
    492         open_device(devname, print_flags);
    493     }
    494     closedir(dir);
    495     return 0;
    496 }
    497 
    498 static void usage(char *name)
    499 {
    500     fprintf(stderr, "Usage: %s [-t] [-n] [-s switchmask] [-S] [-v [mask]] [-d] [-p] [-i] [-l] [-q] [-c count] [-r] [device]\n", name);
    501     fprintf(stderr, "    -t: show time stamps\n");
    502     fprintf(stderr, "    -n: don't print newlines\n");
    503     fprintf(stderr, "    -s: print switch states for given bits\n");
    504     fprintf(stderr, "    -S: print all switch states\n");
    505     fprintf(stderr, "    -v: verbosity mask (errs=1, dev=2, name=4, info=8, vers=16, pos. events=32, props=64)\n");
    506     fprintf(stderr, "    -d: show HID descriptor, if available\n");
    507     fprintf(stderr, "    -p: show possible events (errs, dev, name, pos. events)\n");
    508     fprintf(stderr, "    -i: show all device info and possible events\n");
    509     fprintf(stderr, "    -l: label event types and names in plain text\n");
    510     fprintf(stderr, "    -q: quiet (clear verbosity mask)\n");
    511     fprintf(stderr, "    -c: print given number of events then exit\n");
    512     fprintf(stderr, "    -r: print rate events are received\n");
    513 }
    514 
    515 int getevent_main(int argc, char *argv[])
    516 {
    517     int c;
    518     int i;
    519     int res;
    520     int get_time = 0;
    521     int print_device = 0;
    522     char *newline = "\n";
    523     uint16_t get_switch = 0;
    524     struct input_event event;
    525     int print_flags = 0;
    526     int print_flags_set = 0;
    527     int dont_block = -1;
    528     int event_count = 0;
    529     int sync_rate = 0;
    530     int64_t last_sync_time = 0;
    531     const char *device = NULL;
    532     const char *device_path = "/dev/input";
    533 
    534     opterr = 0;
    535     do {
    536         c = getopt(argc, argv, "tns:Sv::dpilqc:rh");
    537         if (c == EOF)
    538             break;
    539         switch (c) {
    540         case 't':
    541             get_time = 1;
    542             break;
    543         case 'n':
    544             newline = "";
    545             break;
    546         case 's':
    547             get_switch = strtoul(optarg, NULL, 0);
    548             if(dont_block == -1)
    549                 dont_block = 1;
    550             break;
    551         case 'S':
    552             get_switch = ~0;
    553             if(dont_block == -1)
    554                 dont_block = 1;
    555             break;
    556         case 'v':
    557             if(optarg)
    558                 print_flags |= strtoul(optarg, NULL, 0);
    559             else
    560                 print_flags |= PRINT_DEVICE | PRINT_DEVICE_NAME | PRINT_DEVICE_INFO | PRINT_VERSION;
    561             print_flags_set = 1;
    562             break;
    563         case 'd':
    564             print_flags |= PRINT_HID_DESCRIPTOR;
    565             break;
    566         case 'p':
    567             print_flags |= PRINT_DEVICE_ERRORS | PRINT_DEVICE
    568                     | PRINT_DEVICE_NAME | PRINT_POSSIBLE_EVENTS | PRINT_INPUT_PROPS;
    569             print_flags_set = 1;
    570             if(dont_block == -1)
    571                 dont_block = 1;
    572             break;
    573         case 'i':
    574             print_flags |= PRINT_ALL_INFO;
    575             print_flags_set = 1;
    576             if(dont_block == -1)
    577                 dont_block = 1;
    578             break;
    579         case 'l':
    580             print_flags |= PRINT_LABELS;
    581             break;
    582         case 'q':
    583             print_flags_set = 1;
    584             break;
    585         case 'c':
    586             event_count = atoi(optarg);
    587             dont_block = 0;
    588             break;
    589         case 'r':
    590             sync_rate = 1;
    591             break;
    592         case '?':
    593             fprintf(stderr, "%s: invalid option -%c\n",
    594                 argv[0], optopt);
    595         case 'h':
    596             usage(argv[0]);
    597             exit(1);
    598         }
    599     } while (1);
    600     if(dont_block == -1)
    601         dont_block = 0;
    602 
    603     if (optind + 1 == argc) {
    604         device = argv[optind];
    605         optind++;
    606     }
    607     if (optind != argc) {
    608         usage(argv[0]);
    609         exit(1);
    610     }
    611     nfds = 1;
    612     ufds = calloc(1, sizeof(ufds[0]));
    613     ufds[0].fd = inotify_init();
    614     ufds[0].events = POLLIN;
    615     if(device) {
    616         if(!print_flags_set)
    617             print_flags |= PRINT_DEVICE_ERRORS;
    618         res = open_device(device, print_flags);
    619         if(res < 0) {
    620             return 1;
    621         }
    622     } else {
    623         if(!print_flags_set)
    624             print_flags |= PRINT_DEVICE_ERRORS | PRINT_DEVICE | PRINT_DEVICE_NAME;
    625         print_device = 1;
    626 		res = inotify_add_watch(ufds[0].fd, device_path, IN_DELETE | IN_CREATE);
    627         if(res < 0) {
    628             fprintf(stderr, "could not add watch for %s, %s\n", device_path, strerror(errno));
    629             return 1;
    630         }
    631         res = scan_dir(device_path, print_flags);
    632         if(res < 0) {
    633             fprintf(stderr, "scan dir failed for %s\n", device_path);
    634             return 1;
    635         }
    636     }
    637 
    638     if(get_switch) {
    639         for(i = 1; i < nfds; i++) {
    640             uint16_t sw;
    641             res = ioctl(ufds[i].fd, EVIOCGSW(1), &sw);
    642             if(res < 0) {
    643                 fprintf(stderr, "could not get switch state, %s\n", strerror(errno));
    644                 return 1;
    645             }
    646             sw &= get_switch;
    647             printf("%04x%s", sw, newline);
    648         }
    649     }
    650 
    651     if(dont_block)
    652         return 0;
    653 
    654     while(1) {
    655         //int pollres =
    656         poll(ufds, nfds, -1);
    657         //printf("poll %d, returned %d\n", nfds, pollres);
    658         if(ufds[0].revents & POLLIN) {
    659             read_notify(device_path, ufds[0].fd, print_flags);
    660         }
    661         for(i = 1; i < nfds; i++) {
    662             if(ufds[i].revents) {
    663                 if(ufds[i].revents & POLLIN) {
    664                     res = read(ufds[i].fd, &event, sizeof(event));
    665                     if(res < (int)sizeof(event)) {
    666                         fprintf(stderr, "could not get event\n");
    667                         return 1;
    668                     }
    669                     if(get_time) {
    670                         printf("[%8ld.%06ld] ", event.time.tv_sec, event.time.tv_usec);
    671                     }
    672                     if(print_device)
    673                         printf("%s: ", device_names[i]);
    674                     print_event(event.type, event.code, event.value, print_flags);
    675                     if(sync_rate && event.type == 0 && event.code == 0) {
    676                         int64_t now = event.time.tv_sec * 1000000LL + event.time.tv_usec;
    677                         if(last_sync_time)
    678                             printf(" rate %lld", 1000000LL / (now - last_sync_time));
    679                         last_sync_time = now;
    680                     }
    681                     printf("%s", newline);
    682                     if(event_count && --event_count == 0)
    683                         return 0;
    684                 }
    685             }
    686         }
    687     }
    688 
    689     return 0;
    690 }
    691