1 /* $NetBSD: usbhidaction.c,v 1.27 2016/12/31 10:13:15 dholland Exp $ */ 2 3 /* 4 * Copyright (c) 2000, 2002 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Lennart Augustsson <lennart@augustsson.net>. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 #include <sys/cdefs.h> 32 33 #ifndef lint 34 __RCSID("$NetBSD: usbhidaction.c,v 1.27 2016/12/31 10:13:15 dholland Exp $"); 35 #endif 36 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <ctype.h> 41 #include <err.h> 42 #include <fcntl.h> 43 #include <limits.h> 44 #include <unistd.h> 45 #include <sys/types.h> 46 #include <sys/ioctl.h> 47 #include <dev/usb/usb.h> 48 #include <dev/usb/usbhid.h> 49 #include <usbhid.h> 50 #include <util.h> 51 #include <syslog.h> 52 #include <signal.h> 53 #include <util.h> 54 55 static int verbose = 0; 56 static int isdemon = 0; 57 static int reparse = 0; 58 59 struct command { 60 struct command *next; 61 int line; 62 63 struct hid_item item; 64 int value; 65 char anyvalue; 66 char *name; 67 char *action; 68 }; 69 static struct command *commands; 70 71 #define SIZE 4000 72 73 static void usage(void) __dead; 74 static struct command *parse_conf(const char *, report_desc_t, int, int); 75 static void docmd(struct command *, int, const char *, int, char **); 76 static void freecommands(struct command *); 77 78 static void 79 /*ARGSUSED*/ 80 sighup(int sig) 81 { 82 reparse = 1; 83 } 84 85 int 86 main(int argc, char **argv) 87 { 88 const char *conf = NULL; 89 const char *dev = NULL; 90 int fd, ch, sz, n, val, i; 91 int demon, ignore; 92 report_desc_t repd; 93 char buf[100]; 94 char devnamebuf[PATH_MAX]; 95 struct command *cmd; 96 int reportid; 97 const char *table = NULL; 98 99 setprogname(argv[0]); 100 (void)setlinebuf(stdout); 101 102 demon = 1; 103 ignore = 0; 104 while ((ch = getopt(argc, argv, "c:df:it:v")) != -1) { 105 switch(ch) { 106 case 'c': 107 conf = optarg; 108 break; 109 case 'd': 110 demon ^= 1; 111 break; 112 case 'i': 113 ignore++; 114 break; 115 case 'f': 116 dev = optarg; 117 break; 118 case 't': 119 table = optarg; 120 break; 121 case 'v': 122 demon = 0; 123 verbose++; 124 break; 125 case '?': 126 default: 127 usage(); 128 } 129 } 130 argc -= optind; 131 argv += optind; 132 133 if (conf == NULL || dev == NULL) 134 usage(); 135 136 hid_init(table); 137 138 if (dev[0] != '/') { 139 (void)snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s", 140 isdigit((unsigned char)dev[0]) ? "uhid" : "", dev); 141 dev = devnamebuf; 142 } 143 144 if (demon && conf[0] != '/') 145 errx(EXIT_FAILURE, 146 "config file must have an absolute path, %s", conf); 147 148 fd = open(dev, O_RDWR | O_CLOEXEC); 149 if (fd < 0) 150 err(EXIT_FAILURE, "%s", dev); 151 152 if (ioctl(fd, USB_GET_REPORT_ID, &reportid) < 0) 153 reportid = -1; 154 repd = hid_get_report_desc(fd); 155 if (repd == NULL) 156 err(EXIT_FAILURE, "hid_get_report_desc() failed"); 157 158 commands = parse_conf(conf, repd, reportid, ignore); 159 160 sz = hid_report_size(repd, hid_input, reportid); 161 162 if (verbose) 163 (void)printf("report size %d\n", sz); 164 if ((size_t)sz > sizeof(buf)) 165 errx(EXIT_FAILURE, "report too large"); 166 167 (void)signal(SIGHUP, sighup); 168 169 if (demon) { 170 if (daemon(0, 0) < 0) 171 err(EXIT_FAILURE, "daemon()"); 172 (void)pidfile(NULL); 173 isdemon = 1; 174 } 175 176 for(;;) { 177 n = read(fd, buf, (size_t)sz); 178 if (verbose > 2) { 179 (void)printf("read %d bytes:", n); 180 for (i = 0; i < n; i++) 181 (void)printf(" %02x", buf[i]); 182 (void)printf("\n"); 183 } 184 if (n < 0) { 185 if (verbose) 186 err(EXIT_FAILURE, "read"); 187 else 188 exit(EXIT_FAILURE); 189 } 190 #if 0 191 if (n != sz) { 192 err(EXIT_FAILURE, "read size"); 193 } 194 #endif 195 for (cmd = commands; cmd; cmd = cmd->next) { 196 val = hid_get_data(buf, &cmd->item); 197 if (cmd->value == val || cmd->anyvalue) 198 docmd(cmd, val, dev, argc, argv); 199 } 200 if (reparse) { 201 struct command *cmds = 202 parse_conf(conf, repd, reportid, ignore); 203 if (cmds) { 204 freecommands(commands); 205 commands = cmds; 206 } 207 reparse = 0; 208 } 209 } 210 } 211 212 static void 213 usage(void) 214 { 215 216 (void)fprintf(stderr, "usage: %s -c config_file [-d] -f hid_dev " 217 "[-i] [-t table] [-v]\n", getprogname()); 218 exit(EXIT_FAILURE); 219 } 220 221 static int 222 peek(FILE *f) 223 { 224 int c; 225 226 c = getc(f); 227 if (c != EOF) 228 (void)ungetc(c, f); 229 return c; 230 } 231 232 static struct command * 233 parse_conf(const char *conf, report_desc_t repd, int reportid, int ignore) 234 { 235 FILE *f; 236 char *p; 237 int line; 238 char buf[SIZE], name[SIZE], value[SIZE], action[SIZE]; 239 char usagestr[SIZE], coll[SIZE]; 240 struct command *cmd, *cmds; 241 struct hid_data *d; 242 struct hid_item h; 243 int u, lo, hi, range; 244 245 f = fopen(conf, "r"); 246 if (f == NULL) 247 err(EXIT_FAILURE, "%s", conf); 248 249 cmds = NULL; 250 for (line = 1; ; line++) { 251 if (fgets(buf, sizeof buf, f) == NULL) 252 break; 253 if (buf[0] == '#' || buf[0] == '\n') 254 continue; 255 p = strchr(buf, '\n'); 256 while (p && isspace(peek(f))) { 257 if (fgets(p, (int)(sizeof buf - strlen(buf)), f) 258 == NULL) 259 break; 260 p = strchr(buf, '\n'); 261 } 262 if (p) 263 *p = '\0'; 264 /* XXX SIZE == 4000 */ 265 if (sscanf(buf, "%3999s %3999s %[^\n]", name, value, action) != 3) { 266 if (isdemon) { 267 syslog(LOG_WARNING, "config file `%s', line %d" 268 ", syntax error: %s", conf, line, buf); 269 freecommands(cmds); 270 (void)fclose(f); 271 return (NULL); 272 } else { 273 errx(EXIT_FAILURE, "config file `%s', line %d," 274 ", syntax error: %s", conf, line, buf); 275 } 276 } 277 278 cmd = emalloc(sizeof *cmd); 279 cmd->next = cmds; 280 cmds = cmd; 281 cmd->line = line; 282 283 if (strcmp(value, "*") == 0) { 284 cmd->anyvalue = 1; 285 } else { 286 cmd->anyvalue = 0; 287 if (sscanf(value, "%d", &cmd->value) != 1) { 288 if (isdemon) { 289 syslog(LOG_WARNING, 290 "config file `%s', line %d, " 291 "bad value: %s\n", 292 conf, line, value); 293 freecommands(cmds); 294 (void)fclose(f); 295 return (NULL); 296 } else { 297 errx(EXIT_FAILURE, 298 "config file `%s', line %d, " 299 "bad value: %s\n", 300 conf, line, value); 301 } 302 } 303 } 304 305 coll[0] = 0; 306 for (d = hid_start_parse(repd, 1 << hid_input, reportid); 307 hid_get_item(d, &h); ) { 308 if (verbose > 2) 309 (void)printf("kind=%d usage=%x flags=%x\n", 310 h.kind, h.usage, h.flags); 311 switch (h.kind) { 312 case hid_input: 313 if (h.flags & HIO_CONST) 314 continue; 315 if (h.usage_minimum != 0 || 316 h.usage_maximum != 0) { 317 lo = h.usage_minimum; 318 hi = h.usage_maximum; 319 range = 1; 320 } else { 321 lo = h.usage; 322 hi = h.usage; 323 range = 0; 324 } 325 for (u = lo; u <= hi; u++) { 326 (void)snprintf(usagestr, 327 sizeof usagestr, 328 "%s:%s", 329 hid_usage_page((int)HID_PAGE(u)), 330 hid_usage_in_page((u_int)u)); 331 if (verbose > 2) 332 (void)printf("usage %s\n", 333 usagestr); 334 if (!strcasecmp(usagestr, name)) 335 goto foundhid; 336 if (coll[0]) { 337 (void)snprintf(usagestr, 338 sizeof usagestr, 339 "%s.%s:%s", coll + 1, 340 hid_usage_page((int)HID_PAGE(u)), 341 hid_usage_in_page((u_int)u)); 342 if (verbose > 2) 343 (void)printf( 344 "usage %s\n", 345 usagestr); 346 if (!strcasecmp(usagestr, name)) 347 goto foundhid; 348 } 349 } 350 break; 351 case hid_collection: 352 (void)snprintf(coll + strlen(coll), 353 sizeof coll - strlen(coll), ".%s:%s", 354 hid_usage_page((int)HID_PAGE(h.usage)), 355 hid_usage_in_page(h.usage)); 356 if (verbose > 2) 357 (void)printf("coll '%s'\n", coll); 358 break; 359 case hid_endcollection: 360 if (coll[0]) 361 *strrchr(coll, '.') = 0; 362 break; 363 default: 364 break; 365 } 366 } 367 if (ignore) { 368 if (verbose) 369 warnx("ignore item '%s'", name); 370 continue; 371 } 372 if (isdemon) { 373 syslog(LOG_WARNING, "config file `%s', line %d, HID " 374 "item not found: `%s'", conf, line, name); 375 freecommands(cmds); 376 (void)fclose(f); 377 return (NULL); 378 } else { 379 errx(EXIT_FAILURE, "config file `%s', line %d," 380 " HID item not found: `%s'", conf, line, name); 381 } 382 383 foundhid: 384 hid_end_parse(d); 385 cmd->item = h; 386 cmd->name = estrdup(name); 387 cmd->action = estrdup(action); 388 if (range) { 389 if (cmd->value == 1) 390 cmd->value = u - lo; 391 else 392 cmd->value = -1; 393 } 394 395 if (verbose) { 396 char valuebuf[16]; 397 398 if (cmd->anyvalue) 399 snprintf(valuebuf, sizeof(valuebuf), "%s", "*"); 400 else 401 snprintf(valuebuf, sizeof(valuebuf), "%d", 402 cmd->value); 403 (void)printf("PARSE:%d %s, %s, '%s'\n", cmd->line, name, 404 valuebuf, cmd->action); 405 } 406 } 407 (void)fclose(f); 408 return (cmds); 409 } 410 411 static void 412 docmd(struct command *cmd, int value, const char *hid, int argc, char **argv) 413 { 414 char cmdbuf[SIZE], *p, *q; 415 size_t len; 416 int n, r; 417 418 for (p = cmd->action, q = cmdbuf; *p && q < &cmdbuf[SIZE-1]; ) { 419 if (*p == '$') { 420 p++; 421 len = &cmdbuf[SIZE-1] - q; 422 if (isdigit((unsigned char)*p)) { 423 n = strtol(p, &p, 10) - 1; 424 if (n >= 0 && n < argc) { 425 (void)strlcpy(q, argv[n], len); 426 q += strlen(q); 427 } 428 } else if (*p == 'V') { 429 p++; 430 (void)snprintf(q, len, "%d", value); 431 q += strlen(q); 432 } else if (*p == 'N') { 433 p++; 434 (void)strlcpy(q, cmd->name, len); 435 q += strlen(q); 436 } else if (*p == 'H') { 437 p++; 438 (void)strlcpy(q, hid, len); 439 q += strlen(q); 440 } else if (*p) { 441 *q++ = *p++; 442 } 443 } else { 444 *q++ = *p++; 445 } 446 } 447 *q = 0; 448 449 if (verbose) 450 (void)printf("system '%s'\n", cmdbuf); 451 r = system(cmdbuf); 452 if (verbose > 1 && r) 453 (void)printf("return code = 0x%x\n", r); 454 } 455 456 static void 457 freecommand(struct command *cmd) 458 { 459 free(cmd->name); 460 free(cmd->action); 461 free(cmd); 462 } 463 464 static void 465 freecommands(struct command *cmd) 466 { 467 struct command *next; 468 469 while (cmd) { 470 next = cmd->next; 471 freecommand(cmd); 472 cmd = next; 473 } 474 } 475