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