usbhidaction.c revision 126774
1113288Smdodd/* $NetBSD: usbhidaction.c,v 1.8 2002/06/11 06:06:21 itojun Exp $ */ 2113288Smdodd/* $FreeBSD: head/usr.bin/usbhidaction/usbhidaction.c 126774 2004-03-09 11:35:43Z dwmalone $ */ 3113288Smdodd 4113288Smdodd/* 5113288Smdodd * Copyright (c) 2000, 2002 The NetBSD Foundation, Inc. 6113288Smdodd * All rights reserved. 7113288Smdodd * 8113288Smdodd * This code is derived from software contributed to The NetBSD Foundation 9113288Smdodd * by Lennart Augustsson <lennart@augustsson.net>. 10113288Smdodd * 11113288Smdodd * Redistribution and use in source and binary forms, with or without 12113288Smdodd * modification, are permitted provided that the following conditions 13113288Smdodd * are met: 14113288Smdodd * 1. Redistributions of source code must retain the above copyright 15113288Smdodd * notice, this list of conditions and the following disclaimer. 16113288Smdodd * 2. Redistributions in binary form must reproduce the above copyright 17113288Smdodd * notice, this list of conditions and the following disclaimer in the 18113288Smdodd * documentation and/or other materials provided with the distribution. 19113288Smdodd * 3. All advertising materials mentioning features or use of this software 20113288Smdodd * must display the following acknowledgement: 21113288Smdodd * This product includes software developed by the NetBSD 22113288Smdodd * Foundation, Inc. and its contributors. 23113288Smdodd * 4. Neither the name of The NetBSD Foundation nor the names of its 24113288Smdodd * contributors may be used to endorse or promote products derived 25113288Smdodd * from this software without specific prior written permission. 26113288Smdodd * 27113288Smdodd * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 28113288Smdodd * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 29113288Smdodd * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 30113288Smdodd * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 31113288Smdodd * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32113288Smdodd * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33113288Smdodd * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34113288Smdodd * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35113288Smdodd * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36113288Smdodd * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37113288Smdodd * POSSIBILITY OF SUCH DAMAGE. 38113288Smdodd */ 39113288Smdodd 40113288Smdodd#include <stdio.h> 41113288Smdodd#include <stdlib.h> 42113288Smdodd#include <string.h> 43113288Smdodd#include <ctype.h> 44113288Smdodd#include <err.h> 45113288Smdodd#include <fcntl.h> 46113288Smdodd#include <limits.h> 47113288Smdodd#include <unistd.h> 48113288Smdodd#include <sys/types.h> 49113288Smdodd#include <sys/ioctl.h> 50113288Smdodd#include <dev/usb/usb.h> 51113288Smdodd#include <dev/usb/usbhid.h> 52113288Smdodd#include <usbhid.h> 53113288Smdodd#include <syslog.h> 54113288Smdodd#include <signal.h> 55113288Smdodd#include <errno.h> 56113288Smdodd#include <sys/stat.h> 57113288Smdodd 58113288Smdoddstatic int verbose = 0; 59113288Smdoddstatic int isdemon = 0; 60113288Smdoddstatic int reparse = 1; 61126774Sdwmalonestatic const char * pidfile = "/var/run/usbaction.pid"; 62113288Smdodd 63113288Smdoddstruct command { 64113288Smdodd struct command *next; 65113288Smdodd int line; 66113288Smdodd 67113288Smdodd struct hid_item item; 68113288Smdodd int value; 69113309Smdodd int lastseen; 70113309Smdodd int lastused; 71113309Smdodd int debounce; 72113288Smdodd char anyvalue; 73113288Smdodd char *name; 74113288Smdodd char *action; 75113288Smdodd}; 76113288Smdoddstruct command *commands; 77113288Smdodd 78113288Smdodd#define SIZE 4000 79113288Smdodd 80113288Smdoddvoid usage(void); 81113288Smdoddstruct command *parse_conf(const char *, report_desc_t, int, int); 82113288Smdoddvoid docmd(struct command *, int, const char *, int, char **); 83113288Smdoddvoid freecommands(struct command *); 84113288Smdodd 85113288Smdoddstatic void 86126774Sdwmalonesighup(int sig __unused) 87113288Smdodd{ 88113288Smdodd reparse = 1; 89113288Smdodd} 90113288Smdodd 91113288Smdoddint 92113288Smdoddmain(int argc, char **argv) 93113288Smdodd{ 94113288Smdodd const char *conf = NULL; 95113288Smdodd const char *dev = NULL; 96126774Sdwmalone int fd, fp, ch, n, val, i; 97126774Sdwmalone size_t sz, sz1; 98113309Smdodd int demon, ignore, dieearly; 99113288Smdodd report_desc_t repd; 100113288Smdodd char buf[100]; 101113288Smdodd char devnamebuf[PATH_MAX]; 102113288Smdodd struct command *cmd; 103113288Smdodd int reportid; 104113288Smdodd 105113288Smdodd demon = 1; 106113288Smdodd ignore = 0; 107113309Smdodd dieearly = 0; 108113309Smdodd while ((ch = getopt(argc, argv, "c:def:ip:v")) != -1) { 109113288Smdodd switch(ch) { 110113288Smdodd case 'c': 111113288Smdodd conf = optarg; 112113288Smdodd break; 113113288Smdodd case 'd': 114113288Smdodd demon ^= 1; 115113288Smdodd break; 116113309Smdodd case 'e': 117113309Smdodd dieearly = 1; 118113309Smdodd break; 119113288Smdodd case 'i': 120113288Smdodd ignore++; 121113288Smdodd break; 122113288Smdodd case 'f': 123113288Smdodd dev = optarg; 124113288Smdodd break; 125113288Smdodd case 'p': 126113288Smdodd pidfile = optarg; 127113288Smdodd break; 128113288Smdodd case 'v': 129113288Smdodd demon = 0; 130113288Smdodd verbose++; 131113288Smdodd break; 132113288Smdodd case '?': 133113288Smdodd default: 134113288Smdodd usage(); 135113288Smdodd } 136113288Smdodd } 137113288Smdodd argc -= optind; 138113288Smdodd argv += optind; 139113288Smdodd 140113288Smdodd if (conf == NULL || dev == NULL) 141113288Smdodd usage(); 142113288Smdodd 143113288Smdodd hid_init(NULL); 144113288Smdodd 145113288Smdodd if (dev[0] != '/') { 146113288Smdodd snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s", 147113288Smdodd isdigit(dev[0]) ? "uhid" : "", dev); 148113288Smdodd dev = devnamebuf; 149113288Smdodd } 150113288Smdodd 151113288Smdodd fd = open(dev, O_RDWR); 152113288Smdodd if (fd < 0) 153113288Smdodd err(1, "%s", dev); 154113288Smdodd if (ioctl(fd, USB_GET_REPORT_ID, &reportid) < 0) 155113288Smdodd reportid = -1; 156113288Smdodd repd = hid_get_report_desc(fd); 157113288Smdodd if (repd == NULL) 158113288Smdodd err(1, "hid_get_report_desc() failed"); 159113288Smdodd 160113288Smdodd commands = parse_conf(conf, repd, reportid, ignore); 161113288Smdodd 162126774Sdwmalone sz = (size_t)hid_report_size(repd, hid_input, reportid); 163113288Smdodd 164113288Smdodd if (verbose) 165126774Sdwmalone printf("report size %zu\n", sz); 166113288Smdodd if (sz > sizeof buf) 167113288Smdodd errx(1, "report too large"); 168113288Smdodd 169113288Smdodd (void)signal(SIGHUP, sighup); 170113288Smdodd 171113288Smdodd if (demon) { 172113288Smdodd fp = open(pidfile, O_WRONLY|O_CREAT, S_IRUSR|S_IRGRP|S_IROTH); 173113288Smdodd if (fp >= 0) { 174126774Sdwmalone sz1 = snprintf(buf, sizeof buf, "%d\n", getpid()); 175126774Sdwmalone if (sz1 > sizeof buf) 176126774Sdwmalone sz1 = sizeof buf; 177126774Sdwmalone write(fp, buf, sz1); 178113288Smdodd close(fp); 179113288Smdodd } else 180113288Smdodd err(1, "%s", pidfile); 181113309Smdodd if (daemon(0, 0) < 0) 182113309Smdodd err(1, "daemon()"); 183113288Smdodd isdemon = 1; 184113288Smdodd } 185113288Smdodd 186113288Smdodd for(;;) { 187113288Smdodd n = read(fd, buf, sz); 188113288Smdodd if (verbose > 2) { 189113288Smdodd printf("read %d bytes:", n); 190113288Smdodd for (i = 0; i < n; i++) 191113288Smdodd printf(" %02x", buf[i]); 192113288Smdodd printf("\n"); 193113288Smdodd } 194113288Smdodd if (n < 0) { 195113288Smdodd if (verbose) 196113288Smdodd err(1, "read"); 197113288Smdodd else 198113288Smdodd exit(1); 199113288Smdodd } 200113288Smdodd#if 0 201113288Smdodd if (n != sz) { 202113288Smdodd err(2, "read size"); 203113288Smdodd } 204113288Smdodd#endif 205113288Smdodd for (cmd = commands; cmd; cmd = cmd->next) { 206113288Smdodd val = hid_get_data(buf, &cmd->item); 207113309Smdodd if (cmd->value != val && cmd->anyvalue == 0) 208113309Smdodd goto next; 209113309Smdodd if ((cmd->debounce == 0) || 210113328Smdodd ((cmd->debounce == 1) && ((cmd->lastseen == -1) || 211113309Smdodd (cmd->lastseen != val)))) { 212113288Smdodd docmd(cmd, val, dev, argc, argv); 213113309Smdodd goto next; 214113309Smdodd } 215113309Smdodd if ((cmd->debounce > 1) && 216113309Smdodd ((cmd->lastused == -1) || 217113309Smdodd (abs(cmd->lastused - val) >= cmd->debounce))) { 218113309Smdodd docmd(cmd, val, dev, argc, argv); 219113309Smdodd cmd->lastused = val; 220113309Smdodd goto next; 221113309Smdodd } 222113309Smdoddnext: 223113309Smdodd cmd->lastseen = val; 224113288Smdodd } 225113309Smdodd 226113309Smdodd if (dieearly) 227113309Smdodd exit(0); 228113309Smdodd 229113288Smdodd if (reparse) { 230113288Smdodd struct command *cmds = 231113288Smdodd parse_conf(conf, repd, reportid, ignore); 232113288Smdodd if (cmds) { 233113288Smdodd freecommands(commands); 234113288Smdodd commands = cmds; 235113288Smdodd } 236113288Smdodd reparse = 0; 237113288Smdodd } 238113288Smdodd } 239113288Smdodd 240113288Smdodd exit(0); 241113288Smdodd} 242113288Smdodd 243113288Smdoddvoid 244113288Smdoddusage(void) 245113288Smdodd{ 246113288Smdodd 247113309Smdodd fprintf(stderr, "Usage: %s [-deiv] -c config_file -f hid_dev " 248113309Smdodd "[-p pidfile]\n", getprogname()); 249113288Smdodd exit(1); 250113288Smdodd} 251113288Smdodd 252113288Smdoddstatic int 253113288Smdoddpeek(FILE *f) 254113288Smdodd{ 255113288Smdodd int c; 256113288Smdodd 257113288Smdodd c = getc(f); 258113288Smdodd if (c != EOF) 259113288Smdodd ungetc(c, f); 260113288Smdodd return c; 261113288Smdodd} 262113288Smdodd 263113288Smdoddstruct command * 264113288Smdoddparse_conf(const char *conf, report_desc_t repd, int reportid, int ignore) 265113288Smdodd{ 266113288Smdodd FILE *f; 267113288Smdodd char *p; 268113288Smdodd int line; 269113309Smdodd char buf[SIZE], name[SIZE], value[SIZE], debounce[SIZE], action[SIZE]; 270126774Sdwmalone char usbuf[SIZE], coll[SIZE]; 271113288Smdodd struct command *cmd, *cmds; 272113288Smdodd struct hid_data *d; 273113288Smdodd struct hid_item h; 274113288Smdodd int u, lo, hi, range; 275113288Smdodd 276113288Smdodd 277113288Smdodd f = fopen(conf, "r"); 278113288Smdodd if (f == NULL) 279113288Smdodd err(1, "%s", conf); 280113288Smdodd 281113288Smdodd cmds = NULL; 282113288Smdodd for (line = 1; ; line++) { 283113288Smdodd if (fgets(buf, sizeof buf, f) == NULL) 284113288Smdodd break; 285113288Smdodd if (buf[0] == '#' || buf[0] == '\n') 286113288Smdodd continue; 287113288Smdodd p = strchr(buf, '\n'); 288113288Smdodd while (p && isspace(peek(f))) { 289113288Smdodd if (fgets(p, sizeof buf - strlen(buf), f) == NULL) 290113288Smdodd break; 291113288Smdodd p = strchr(buf, '\n'); 292113288Smdodd } 293113288Smdodd if (p) 294113288Smdodd *p = 0; 295113309Smdodd if (sscanf(buf, "%s %s %s %[^\n]", 296113309Smdodd name, value, debounce, action) != 4) { 297113288Smdodd if (isdemon) { 298113288Smdodd syslog(LOG_WARNING, "config file `%s', line %d" 299113288Smdodd ", syntax error: %s", conf, line, buf); 300113288Smdodd freecommands(cmds); 301113288Smdodd return (NULL); 302113288Smdodd } else { 303113288Smdodd errx(1, "config file `%s', line %d," 304113288Smdodd ", syntax error: %s", conf, line, buf); 305113288Smdodd } 306113288Smdodd } 307113288Smdodd 308113288Smdodd cmd = malloc(sizeof *cmd); 309113288Smdodd if (cmd == NULL) 310113288Smdodd err(1, "malloc failed"); 311113288Smdodd cmd->next = cmds; 312113288Smdodd cmds = cmd; 313113288Smdodd cmd->line = line; 314113288Smdodd 315113288Smdodd if (strcmp(value, "*") == 0) { 316113288Smdodd cmd->anyvalue = 1; 317113288Smdodd } else { 318113288Smdodd cmd->anyvalue = 0; 319113288Smdodd if (sscanf(value, "%d", &cmd->value) != 1) { 320113288Smdodd if (isdemon) { 321113288Smdodd syslog(LOG_WARNING, 322113288Smdodd "config file `%s', line %d, " 323113309Smdodd "bad value: %s (should be * or a number)\n", 324113288Smdodd conf, line, value); 325113288Smdodd freecommands(cmds); 326113288Smdodd return (NULL); 327113288Smdodd } else { 328113288Smdodd errx(1, "config file `%s', line %d, " 329113309Smdodd "bad value: %s (should be * or a number)\n", 330113288Smdodd conf, line, value); 331113288Smdodd } 332113288Smdodd } 333113288Smdodd } 334113288Smdodd 335113309Smdodd if (sscanf(debounce, "%d", &cmd->debounce) != 1) { 336113309Smdodd if (isdemon) { 337113309Smdodd syslog(LOG_WARNING, 338113309Smdodd "config file `%s', line %d, " 339113309Smdodd "bad value: %s (should be a number >= 0)\n", 340113309Smdodd conf, line, debounce); 341113309Smdodd freecommands(cmds); 342113309Smdodd return (NULL); 343113309Smdodd } else { 344113309Smdodd errx(1, "config file `%s', line %d, " 345113309Smdodd "bad value: %s (should be a number >= 0)\n", 346113309Smdodd conf, line, debounce); 347113309Smdodd } 348113309Smdodd } 349113309Smdodd 350113288Smdodd coll[0] = 0; 351113288Smdodd for (d = hid_start_parse(repd, 1 << hid_input, reportid); 352113288Smdodd hid_get_item(d, &h); ) { 353113288Smdodd if (verbose > 2) 354113288Smdodd printf("kind=%d usage=%x\n", h.kind, h.usage); 355113288Smdodd if (h.flags & HIO_CONST) 356113288Smdodd continue; 357113288Smdodd switch (h.kind) { 358113288Smdodd case hid_input: 359113288Smdodd if (h.usage_minimum != 0 || 360113288Smdodd h.usage_maximum != 0) { 361113288Smdodd lo = h.usage_minimum; 362113288Smdodd hi = h.usage_maximum; 363113288Smdodd range = 1; 364113288Smdodd } else { 365113288Smdodd lo = h.usage; 366113288Smdodd hi = h.usage; 367113288Smdodd range = 0; 368113288Smdodd } 369113288Smdodd for (u = lo; u <= hi; u++) { 370126774Sdwmalone snprintf(usbuf, sizeof usbuf, "%s:%s", 371113288Smdodd hid_usage_page(HID_PAGE(u)), 372113288Smdodd hid_usage_in_page(u)); 373113288Smdodd if (verbose > 2) 374126774Sdwmalone printf("usage %s\n", usbuf); 375126774Sdwmalone if (!strcasecmp(usbuf, name)) 376113288Smdodd goto foundhid; 377113288Smdodd if (coll[0]) { 378126774Sdwmalone snprintf(usbuf, sizeof usbuf, 379113288Smdodd "%s.%s:%s", coll+1, 380113288Smdodd hid_usage_page(HID_PAGE(u)), 381113288Smdodd hid_usage_in_page(u)); 382113288Smdodd if (verbose > 2) 383113288Smdodd printf("usage %s\n", 384126774Sdwmalone usbuf); 385126774Sdwmalone if (!strcasecmp(usbuf, name)) 386113288Smdodd goto foundhid; 387113288Smdodd } 388113288Smdodd } 389113288Smdodd break; 390113288Smdodd case hid_collection: 391113288Smdodd snprintf(coll + strlen(coll), 392113288Smdodd sizeof coll - strlen(coll), ".%s:%s", 393113288Smdodd hid_usage_page(HID_PAGE(h.usage)), 394113288Smdodd hid_usage_in_page(h.usage)); 395113288Smdodd break; 396113288Smdodd case hid_endcollection: 397113288Smdodd if (coll[0]) 398113288Smdodd *strrchr(coll, '.') = 0; 399113288Smdodd break; 400113288Smdodd default: 401113288Smdodd break; 402113288Smdodd } 403113288Smdodd } 404113288Smdodd if (ignore) { 405113288Smdodd if (verbose) 406113288Smdodd warnx("ignore item '%s'", name); 407113288Smdodd continue; 408113288Smdodd } 409113288Smdodd if (isdemon) { 410113288Smdodd syslog(LOG_WARNING, "config file `%s', line %d, HID " 411113288Smdodd "item not found: `%s'\n", conf, line, name); 412113288Smdodd freecommands(cmds); 413113288Smdodd return (NULL); 414113288Smdodd } else { 415113288Smdodd errx(1, "config file `%s', line %d, HID item " 416113288Smdodd "not found: `%s'\n", conf, line, name); 417113288Smdodd } 418113288Smdodd 419113288Smdodd foundhid: 420113288Smdodd hid_end_parse(d); 421113328Smdodd cmd->lastseen = -1; 422113328Smdodd cmd->lastused = -1; 423113288Smdodd cmd->item = h; 424113288Smdodd cmd->name = strdup(name); 425113288Smdodd cmd->action = strdup(action); 426113288Smdodd if (range) { 427113288Smdodd if (cmd->value == 1) 428113288Smdodd cmd->value = u - lo; 429113288Smdodd else 430113288Smdodd cmd->value = -1; 431113288Smdodd } 432113288Smdodd 433113288Smdodd if (verbose) 434113288Smdodd printf("PARSE:%d %s, %d, '%s'\n", cmd->line, name, 435113288Smdodd cmd->value, cmd->action); 436113288Smdodd } 437113288Smdodd fclose(f); 438113288Smdodd return (cmds); 439113288Smdodd} 440113288Smdodd 441113288Smdoddvoid 442113288Smdodddocmd(struct command *cmd, int value, const char *hid, int argc, char **argv) 443113288Smdodd{ 444113288Smdodd char cmdbuf[SIZE], *p, *q; 445113288Smdodd size_t len; 446113288Smdodd int n, r; 447113288Smdodd 448113288Smdodd for (p = cmd->action, q = cmdbuf; *p && q < &cmdbuf[SIZE-1]; ) { 449113288Smdodd if (*p == '$') { 450113288Smdodd p++; 451113288Smdodd len = &cmdbuf[SIZE-1] - q; 452113288Smdodd if (isdigit(*p)) { 453113288Smdodd n = strtol(p, &p, 10) - 1; 454113288Smdodd if (n >= 0 && n < argc) { 455113288Smdodd strncpy(q, argv[n], len); 456113288Smdodd q += strlen(q); 457113288Smdodd } 458113288Smdodd } else if (*p == 'V') { 459113288Smdodd p++; 460113288Smdodd snprintf(q, len, "%d", value); 461113288Smdodd q += strlen(q); 462113288Smdodd } else if (*p == 'N') { 463113288Smdodd p++; 464113288Smdodd strncpy(q, cmd->name, len); 465113288Smdodd q += strlen(q); 466113288Smdodd } else if (*p == 'H') { 467113288Smdodd p++; 468113288Smdodd strncpy(q, hid, len); 469113288Smdodd q += strlen(q); 470113288Smdodd } else if (*p) { 471113288Smdodd *q++ = *p++; 472113288Smdodd } 473113288Smdodd } else { 474113288Smdodd *q++ = *p++; 475113288Smdodd } 476113288Smdodd } 477113288Smdodd *q = 0; 478113288Smdodd 479113288Smdodd if (verbose) 480113288Smdodd printf("system '%s'\n", cmdbuf); 481113288Smdodd r = system(cmdbuf); 482113288Smdodd if (verbose > 1 && r) 483113288Smdodd printf("return code = 0x%x\n", r); 484113288Smdodd} 485113288Smdodd 486113288Smdoddvoid 487113288Smdoddfreecommands(struct command *cmd) 488113288Smdodd{ 489113288Smdodd struct command *next; 490113288Smdodd 491113288Smdodd while (cmd) { 492113288Smdodd next = cmd->next; 493113288Smdodd free(cmd); 494113288Smdodd cmd = next; 495113288Smdodd } 496113288Smdodd} 497