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