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