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 <dev/usb/usbhid.h> 50 #include <usbhid.h> 51 #include <syslog.h> 52 #include <signal.h> 53 #include <errno.h> 54 #include <sys/stat.h> 55 56 static int verbose = 0; 57 static int isdemon = 0; 58 static int reparse = 1; 59 static const char * pidfile = "/var/run/usbaction.pid"; 60 61 struct command { 62 struct command *next; 63 int line; 64 65 struct hid_item item; 66 int value; 67 int lastseen; 68 int lastused; 69 int debounce; 70 char anyvalue; 71 char *name; 72 char *action; 73 }; 74 struct command *commands; 75 76 #define SIZE 4000 77 78 void usage(void); 79 struct command *parse_conf(const char *, report_desc_t, int, int); 80 void docmd(struct command *, int, const char *, int, char **); 81 void freecommands(struct command *); 82 83 static void 84 sighup(int sig __unused) 85 { 86 reparse = 1; 87 } 88 89 int 90 main(int argc, char **argv) 91 { 92 const char *conf = NULL; 93 const char *dev = NULL; 94 const char *table = NULL; 95 int fd, fp, ch, n, val, i; 96 size_t sz, sz1; 97 int demon, ignore, dieearly; 98 report_desc_t repd; 99 char buf[100]; 100 char devnamebuf[PATH_MAX]; 101 struct command *cmd; 102 int reportid; 103 104 demon = 1; 105 ignore = 0; 106 dieearly = 0; 107 while ((ch = getopt(argc, argv, "c:def:ip:t:v")) != -1) { 108 switch(ch) { 109 case 'c': 110 conf = optarg; 111 break; 112 case 'd': 113 demon ^= 1; 114 break; 115 case 'e': 116 dieearly = 1; 117 break; 118 case 'i': 119 ignore++; 120 break; 121 case 'f': 122 dev = optarg; 123 break; 124 case 'p': 125 pidfile = optarg; 126 break; 127 case 't': 128 table = optarg; 129 break; 130 case 'v': 131 demon = 0; 132 verbose++; 133 break; 134 case '?': 135 default: 136 usage(); 137 } 138 } 139 argc -= optind; 140 argv += optind; 141 142 if (conf == NULL || dev == NULL) 143 usage(); 144 145 hid_init(table); 146 147 if (dev[0] != '/') { 148 snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s", 149 isdigit(dev[0]) ? "uhid" : "", dev); 150 dev = devnamebuf; 151 } 152 153 fd = open(dev, O_RDWR); 154 if (fd < 0) 155 err(1, "%s", dev); 156 reportid = hid_get_report_id(fd); 157 repd = hid_get_report_desc(fd); 158 if (repd == NULL) 159 err(1, "hid_get_report_desc() failed"); 160 161 commands = parse_conf(conf, repd, reportid, ignore); 162 163 sz = (size_t)hid_report_size(repd, hid_input, reportid); 164 165 if (verbose) 166 printf("report size %zu\n", sz); 167 if (sz > sizeof buf) 168 errx(1, "report too large"); 169 170 (void)signal(SIGHUP, sighup); 171 172 if (demon) { 173 fp = open(pidfile, O_WRONLY|O_CREAT, S_IRUSR|S_IRGRP|S_IROTH); 174 if (fp >= 0) { 175 sz1 = snprintf(buf, sizeof buf, "%d\n", getpid()); 176 if (sz1 > sizeof buf) 177 sz1 = sizeof buf; 178 write(fp, buf, sz1); 179 close(fp); 180 } else 181 err(1, "%s", pidfile); 182 if (daemon(0, 0) < 0) 183 err(1, "daemon()"); 184 isdemon = 1; 185 } 186 187 for(;;) { 188 n = read(fd, buf, sz); 189 if (verbose > 2) { 190 printf("read %d bytes:", n); 191 for (i = 0; i < n; i++) 192 printf(" %02x", buf[i]); 193 printf("\n"); 194 } 195 if (n < 0) { 196 if (verbose) 197 err(1, "read"); 198 else 199 exit(1); 200 } 201 #if 0 202 if (n != sz) { 203 err(2, "read size"); 204 } 205 #endif 206 for (cmd = commands; cmd; cmd = cmd->next) { 207 val = hid_get_data(buf, &cmd->item); 208 if (cmd->value != val && cmd->anyvalue == 0) 209 goto next; 210 if ((cmd->debounce == 0) || 211 ((cmd->debounce == 1) && ((cmd->lastseen == -1) || 212 (cmd->lastseen != val)))) { 213 docmd(cmd, val, dev, argc, argv); 214 goto next; 215 } 216 if ((cmd->debounce > 1) && 217 ((cmd->lastused == -1) || 218 (abs(cmd->lastused - val) >= cmd->debounce))) { 219 docmd(cmd, val, dev, argc, argv); 220 cmd->lastused = val; 221 goto next; 222 } 223 next: 224 cmd->lastseen = val; 225 } 226 227 if (dieearly) 228 exit(0); 229 230 if (reparse) { 231 struct command *cmds = 232 parse_conf(conf, repd, reportid, ignore); 233 if (cmds) { 234 freecommands(commands); 235 commands = cmds; 236 } 237 reparse = 0; 238 } 239 } 240 241 exit(0); 242 } 243 244 void 245 usage(void) 246 { 247 248 fprintf(stderr, "Usage: %s [-deiv] -c config_file -f hid_dev " 249 "[-p pidfile] [-t tablefile]\n", getprogname()); 250 exit(1); 251 } 252 253 static int 254 peek(FILE *f) 255 { 256 int c; 257 258 c = getc(f); 259 if (c != EOF) 260 ungetc(c, f); 261 return c; 262 } 263 264 struct command * 265 parse_conf(const char *conf, report_desc_t repd, int reportid, int ignore) 266 { 267 FILE *f; 268 char *p; 269 int line; 270 char buf[SIZE], name[SIZE], value[SIZE], debounce[SIZE], action[SIZE]; 271 char usbuf[SIZE], coll[SIZE]; 272 struct command *cmd, *cmds; 273 struct hid_data *d; 274 struct hid_item h; 275 int u, lo, hi, range; 276 277 278 f = fopen(conf, "r"); 279 if (f == NULL) 280 err(1, "%s", conf); 281 282 cmds = NULL; 283 for (line = 1; ; line++) { 284 if (fgets(buf, sizeof buf, f) == NULL) 285 break; 286 if (buf[0] == '#' || buf[0] == '\n') 287 continue; 288 p = strchr(buf, '\n'); 289 while (p && isspace(peek(f))) { 290 if (fgets(p, sizeof buf - strlen(buf), f) == NULL) 291 break; 292 p = strchr(buf, '\n'); 293 } 294 if (p) 295 *p = 0; 296 if (sscanf(buf, "%s %s %s %[^\n]", 297 name, value, debounce, action) != 4) { 298 if (isdemon) { 299 syslog(LOG_WARNING, "config file `%s', line %d" 300 ", syntax error: %s", conf, line, buf); 301 freecommands(cmds); 302 return (NULL); 303 } else { 304 errx(1, "config file `%s', line %d," 305 ", syntax error: %s", conf, line, buf); 306 } 307 } 308 309 cmd = malloc(sizeof *cmd); 310 if (cmd == NULL) 311 err(1, "malloc failed"); 312 cmd->next = cmds; 313 cmds = cmd; 314 cmd->line = line; 315 316 if (strcmp(value, "*") == 0) { 317 cmd->anyvalue = 1; 318 } else { 319 cmd->anyvalue = 0; 320 if (sscanf(value, "%d", &cmd->value) != 1) { 321 if (isdemon) { 322 syslog(LOG_WARNING, 323 "config file `%s', line %d, " 324 "bad value: %s (should be * or a number)\n", 325 conf, line, value); 326 freecommands(cmds); 327 return (NULL); 328 } else { 329 errx(1, "config file `%s', line %d, " 330 "bad value: %s (should be * or a number)\n", 331 conf, line, value); 332 } 333 } 334 } 335 336 if (sscanf(debounce, "%d", &cmd->debounce) != 1) { 337 if (isdemon) { 338 syslog(LOG_WARNING, 339 "config file `%s', line %d, " 340 "bad value: %s (should be a number >= 0)\n", 341 conf, line, debounce); 342 freecommands(cmds); 343 return (NULL); 344 } else { 345 errx(1, "config file `%s', line %d, " 346 "bad value: %s (should be a number >= 0)\n", 347 conf, line, debounce); 348 } 349 } 350 351 coll[0] = 0; 352 for (d = hid_start_parse(repd, 1 << hid_input, reportid); 353 hid_get_item(d, &h); ) { 354 if (verbose > 2) 355 printf("kind=%d usage=%x\n", h.kind, h.usage); 356 if (h.flags & HIO_CONST) 357 continue; 358 switch (h.kind) { 359 case hid_input: 360 if (h.usage_minimum != 0 || 361 h.usage_maximum != 0) { 362 lo = h.usage_minimum; 363 hi = h.usage_maximum; 364 range = 1; 365 } else { 366 lo = h.usage; 367 hi = h.usage; 368 range = 0; 369 } 370 for (u = lo; u <= hi; u++) { 371 snprintf(usbuf, sizeof usbuf, "%s:%s", 372 hid_usage_page(HID_PAGE(u)), 373 hid_usage_in_page(u)); 374 if (verbose > 2) 375 printf("usage %s\n", usbuf); 376 if (!strcasecmp(usbuf, name)) 377 goto foundhid; 378 if (coll[0]) { 379 snprintf(usbuf, sizeof usbuf, 380 "%s.%s:%s", coll+1, 381 hid_usage_page(HID_PAGE(u)), 382 hid_usage_in_page(u)); 383 if (verbose > 2) 384 printf("usage %s\n", 385 usbuf); 386 if (!strcasecmp(usbuf, name)) 387 goto foundhid; 388 } 389 } 390 break; 391 case hid_collection: 392 snprintf(coll + strlen(coll), 393 sizeof coll - strlen(coll), ".%s:%s", 394 hid_usage_page(HID_PAGE(h.usage)), 395 hid_usage_in_page(h.usage)); 396 break; 397 case hid_endcollection: 398 if (coll[0]) 399 *strrchr(coll, '.') = 0; 400 break; 401 default: 402 break; 403 } 404 } 405 if (ignore) { 406 if (verbose) 407 warnx("ignore item '%s'", name); 408 continue; 409 } 410 if (isdemon) { 411 syslog(LOG_WARNING, "config file `%s', line %d, HID " 412 "item not found: `%s'\n", conf, line, name); 413 freecommands(cmds); 414 return (NULL); 415 } else { 416 errx(1, "config file `%s', line %d, HID item " 417 "not found: `%s'\n", conf, line, name); 418 } 419 420 foundhid: 421 hid_end_parse(d); 422 cmd->lastseen = -1; 423 cmd->lastused = -1; 424 cmd->item = h; 425 cmd->name = strdup(name); 426 cmd->action = strdup(action); 427 if (range) { 428 if (cmd->value == 1) 429 cmd->value = u - lo; 430 else 431 cmd->value = -1; 432 } 433 434 if (verbose) 435 printf("PARSE:%d %s, %d, '%s'\n", cmd->line, name, 436 cmd->value, cmd->action); 437 } 438 fclose(f); 439 return (cmds); 440 } 441 442 void 443 docmd(struct command *cmd, int value, const char *hid, int argc, char **argv) 444 { 445 char cmdbuf[SIZE], *p, *q; 446 size_t len; 447 int n, r; 448 449 for (p = cmd->action, q = cmdbuf; *p && q < &cmdbuf[SIZE-1]; ) { 450 if (*p == '$') { 451 p++; 452 len = &cmdbuf[SIZE-1] - q; 453 if (isdigit(*p)) { 454 n = strtol(p, &p, 10) - 1; 455 if (n >= 0 && n < argc) { 456 strncpy(q, argv[n], len); 457 q += strlen(q); 458 } 459 } else if (*p == 'V') { 460 p++; 461 snprintf(q, len, "%d", value); 462 q += strlen(q); 463 } else if (*p == 'N') { 464 p++; 465 strncpy(q, cmd->name, len); 466 q += strlen(q); 467 } else if (*p == 'H') { 468 p++; 469 strncpy(q, hid, len); 470 q += strlen(q); 471 } else if (*p) { 472 *q++ = *p++; 473 } 474 } else { 475 *q++ = *p++; 476 } 477 } 478 *q = 0; 479 480 if (verbose) 481 printf("system '%s'\n", cmdbuf); 482 r = system(cmdbuf); 483 if (verbose > 1 && r) 484 printf("return code = 0x%x\n", r); 485 } 486 487 void 488 freecommands(struct command *cmd) 489 { 490 struct command *next; 491 492 while (cmd) { 493 next = cmd->next; 494 free(cmd); 495 cmd = next; 496 } 497 } 498