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