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