1*53408464Skrw /* $OpenBSD: usbhidaction.c,v 1.22 2016/03/17 19:40:43 krw Exp $ */ 27517eab2Snate /* $NetBSD: usbhidaction.c,v 1.7 2002/01/18 14:38:59 augustss Exp $ */ 37517eab2Snate 47517eab2Snate /* 57517eab2Snate * Copyright (c) 2000, 2002 The NetBSD Foundation, Inc. 67517eab2Snate * All rights reserved. 77517eab2Snate * 87517eab2Snate * This code is derived from software contributed to The NetBSD Foundation 97517eab2Snate * by Lennart Augustsson <lennart@augustsson.net>. 107517eab2Snate * 117517eab2Snate * Redistribution and use in source and binary forms, with or without 127517eab2Snate * modification, are permitted provided that the following conditions 137517eab2Snate * are met: 147517eab2Snate * 1. Redistributions of source code must retain the above copyright 157517eab2Snate * notice, this list of conditions and the following disclaimer. 167517eab2Snate * 2. Redistributions in binary form must reproduce the above copyright 177517eab2Snate * notice, this list of conditions and the following disclaimer in the 187517eab2Snate * documentation and/or other materials provided with the distribution. 197517eab2Snate * 207517eab2Snate * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 217517eab2Snate * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 227517eab2Snate * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 237517eab2Snate * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 247517eab2Snate * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 257517eab2Snate * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 267517eab2Snate * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 277517eab2Snate * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 287517eab2Snate * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 297517eab2Snate * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 307517eab2Snate * POSSIBILITY OF SUCH DAMAGE. 317517eab2Snate */ 327517eab2Snate 337517eab2Snate #include <stdio.h> 347517eab2Snate #include <stdlib.h> 357517eab2Snate #include <string.h> 367517eab2Snate #include <ctype.h> 377517eab2Snate #include <err.h> 387517eab2Snate #include <fcntl.h> 397517eab2Snate #include <limits.h> 407517eab2Snate #include <unistd.h> 417517eab2Snate #include <sys/types.h> 427517eab2Snate #include <sys/ioctl.h> 437517eab2Snate #include <dev/usb/usb.h> 447517eab2Snate #include <dev/usb/usbhid.h> 457517eab2Snate #include <usbhid.h> 467517eab2Snate #include <syslog.h> 477517eab2Snate #include <signal.h> 48a368f40fSjasper #include <paths.h> 497517eab2Snate 507517eab2Snate int verbose = 0; 517517eab2Snate int isdemon = 0; 52cf57c52cSderaadt 53cf57c52cSderaadt volatile sig_atomic_t reparse = 0; 547517eab2Snate 557517eab2Snate struct command { 567517eab2Snate struct command *next; 577517eab2Snate int line; 587517eab2Snate 597517eab2Snate struct hid_item item; 607517eab2Snate int value; 617517eab2Snate char anyvalue; 627517eab2Snate char *name; 637517eab2Snate char *action; 647517eab2Snate }; 657517eab2Snate struct command *commands; 667517eab2Snate 677517eab2Snate #define SIZE 4000 687517eab2Snate 697517eab2Snate void usage(void); 707517eab2Snate struct command *parse_conf(const char *, report_desc_t, int, int); 717517eab2Snate void docmd(struct command *, int, const char *, int, char **); 727517eab2Snate void freecommands(struct command *); 737517eab2Snate 741853465dSderaadt /* ARGSUSED */ 757517eab2Snate static void 761853465dSderaadt sighup(int signo) 777517eab2Snate { 787517eab2Snate reparse = 1; 797517eab2Snate } 807517eab2Snate 817517eab2Snate int 827517eab2Snate main(int argc, char **argv) 837517eab2Snate { 847517eab2Snate const char *conf = NULL; 857517eab2Snate const char *dev = NULL; 867517eab2Snate int fd, ch, sz, n, val, i; 877517eab2Snate int demon, ignore; 887517eab2Snate report_desc_t repd; 897517eab2Snate char buf[100]; 907517eab2Snate char devnamebuf[PATH_MAX]; 917517eab2Snate struct command *cmd; 927517eab2Snate int reportid; 937517eab2Snate 947517eab2Snate demon = 1; 957517eab2Snate ignore = 0; 967517eab2Snate while ((ch = getopt(argc, argv, "c:df:iv")) != -1) { 977517eab2Snate switch(ch) { 987517eab2Snate case 'c': 997517eab2Snate conf = optarg; 1007517eab2Snate break; 1017517eab2Snate case 'd': 1027517eab2Snate demon ^= 1; 1037517eab2Snate break; 1047517eab2Snate case 'i': 1057517eab2Snate ignore++; 1067517eab2Snate break; 1077517eab2Snate case 'f': 1087517eab2Snate dev = optarg; 1097517eab2Snate break; 1107517eab2Snate case 'v': 1117517eab2Snate demon = 0; 1127517eab2Snate verbose++; 1137517eab2Snate break; 1147517eab2Snate case '?': 1157517eab2Snate default: 1167517eab2Snate usage(); 1177517eab2Snate } 1187517eab2Snate } 1197517eab2Snate argc -= optind; 1207517eab2Snate argv += optind; 1217517eab2Snate 1227517eab2Snate if (conf == NULL || dev == NULL) 1237517eab2Snate usage(); 1247517eab2Snate 12578e5c336Sderaadt if (hid_start(NULL) == -1) 12678e5c336Sderaadt errx(1, "hid_init"); 1277517eab2Snate 1287517eab2Snate if (dev[0] != '/') { 1297517eab2Snate snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s", 1300e71acc1Sderaadt isdigit((unsigned char)dev[0]) ? "uhid" : "", dev); 1317517eab2Snate dev = devnamebuf; 1327517eab2Snate } 1337517eab2Snate 134baecae6aSmk if (demon && conf[0] != '/') 135baecae6aSmk errx(1, "config file must have an absolute path, %s", conf); 136baecae6aSmk 1370205f8e6Sguenther fd = open(dev, O_RDWR | O_CLOEXEC); 1387517eab2Snate if (fd < 0) 1397517eab2Snate err(1, "%s", dev); 140240ba166Sfgsch 1417517eab2Snate if (ioctl(fd, USB_GET_REPORT_ID, &reportid) < 0) 1427517eab2Snate reportid = -1; 1437517eab2Snate repd = hid_get_report_desc(fd); 1447517eab2Snate if (repd == NULL) 145c20d9ad4Sjsyn err(1, "hid_get_report_desc() failed"); 1467517eab2Snate 1477517eab2Snate commands = parse_conf(conf, repd, reportid, ignore); 1487517eab2Snate 1497517eab2Snate sz = hid_report_size(repd, hid_input, reportid); 1507517eab2Snate 1517517eab2Snate if (verbose) 1527517eab2Snate printf("report size %d\n", sz); 1537517eab2Snate if (sz > sizeof buf) 1547517eab2Snate errx(1, "report too large"); 1557517eab2Snate 1567517eab2Snate (void)signal(SIGHUP, sighup); 1577517eab2Snate 15812e427a9Srobert /* we do not care about the children, so ignore them */ 15912e427a9Srobert (void)signal(SIGCHLD, SIG_IGN); 16012e427a9Srobert 1617517eab2Snate if (demon) { 1627517eab2Snate if (daemon(0, 0) < 0) 1637517eab2Snate err(1, "daemon()"); 1647517eab2Snate isdemon = 1; 1657517eab2Snate } 1667517eab2Snate 1677517eab2Snate for(;;) { 1687517eab2Snate n = read(fd, buf, sz); 1697517eab2Snate if (verbose > 2) { 1707517eab2Snate printf("read %d bytes:", n); 1717517eab2Snate for (i = 0; i < n; i++) 1727517eab2Snate printf(" %02x", buf[i]); 1737517eab2Snate printf("\n"); 1747517eab2Snate } 1757517eab2Snate if (n < 0) { 1767517eab2Snate if (verbose) 1777517eab2Snate err(1, "read"); 1787517eab2Snate else 1797517eab2Snate exit(1); 1807517eab2Snate } 1817517eab2Snate if (n != sz) { 1827517eab2Snate err(2, "read size"); 1837517eab2Snate } 1847517eab2Snate for (cmd = commands; cmd; cmd = cmd->next) { 1857517eab2Snate val = hid_get_data(buf, &cmd->item); 1867517eab2Snate if (cmd->value == val || cmd->anyvalue) 1877517eab2Snate docmd(cmd, val, dev, argc, argv); 1887517eab2Snate } 1897517eab2Snate if (reparse) { 1907517eab2Snate struct command *cmds = 1917517eab2Snate parse_conf(conf, repd, reportid, ignore); 1927517eab2Snate if (cmds) { 1937517eab2Snate freecommands(commands); 1947517eab2Snate commands = cmds; 1957517eab2Snate } 1967517eab2Snate reparse = 0; 1977517eab2Snate } 1987517eab2Snate } 1997517eab2Snate 2007517eab2Snate exit(0); 2017517eab2Snate } 2027517eab2Snate 2037517eab2Snate void 2047517eab2Snate usage(void) 2057517eab2Snate { 2067517eab2Snate extern char *__progname; 2077517eab2Snate 208a7876005Ssobrado fprintf(stderr, "usage: %s [-div] -c config-file -f device arg ...\n", 209a7876005Ssobrado __progname); 2107517eab2Snate exit(1); 2117517eab2Snate } 2127517eab2Snate 2137517eab2Snate static int 2147517eab2Snate peek(FILE *f) 2157517eab2Snate { 2167517eab2Snate int c; 2177517eab2Snate 2187517eab2Snate c = getc(f); 2197517eab2Snate if (c != EOF) 2207517eab2Snate ungetc(c, f); 2217517eab2Snate return c; 2227517eab2Snate } 2237517eab2Snate 2247517eab2Snate struct command * 2257517eab2Snate parse_conf(const char *conf, report_desc_t repd, int reportid, int ignore) 2267517eab2Snate { 2277517eab2Snate FILE *f; 2287517eab2Snate char *p; 2297517eab2Snate int line; 2307517eab2Snate char buf[SIZE], name[SIZE], value[SIZE], action[SIZE]; 2317517eab2Snate char usage[SIZE], coll[SIZE]; 2327517eab2Snate struct command *cmd, *cmds; 2337517eab2Snate struct hid_data *d; 2347517eab2Snate struct hid_item h; 2357517eab2Snate int u, lo, hi, range; 2367517eab2Snate 2377517eab2Snate f = fopen(conf, "r"); 2387517eab2Snate if (f == NULL) 2397517eab2Snate err(1, "%s", conf); 2407517eab2Snate 2417517eab2Snate cmds = NULL; 2427517eab2Snate for (line = 1; ; line++) { 2437517eab2Snate if (fgets(buf, sizeof buf, f) == NULL) 2447517eab2Snate break; 2457517eab2Snate if (buf[0] == '#' || buf[0] == '\n') 2467517eab2Snate continue; 2477517eab2Snate p = strchr(buf, '\n'); 2487517eab2Snate while (p && isspace(peek(f))) { 2497517eab2Snate if (fgets(p, sizeof buf - strlen(buf), f) == NULL) 2507517eab2Snate break; 2517517eab2Snate p = strchr(buf, '\n'); 2527517eab2Snate } 2537517eab2Snate if (p) 2547517eab2Snate *p = 0; 2557517eab2Snate if (sscanf(buf, "%s %s %[^\n]", name, value, action) != 3) { 2567517eab2Snate if (isdemon) { 2577517eab2Snate syslog(LOG_WARNING, "config file `%s', line %d" 2587517eab2Snate ", syntax error: %s", conf, line, buf); 2597517eab2Snate freecommands(cmds); 2605698d383Sguenther fclose(f); 2617517eab2Snate return (NULL); 2627517eab2Snate } else { 263ca8e7c81Sjaredy errx(1, "config file `%s', line %d" 2647517eab2Snate ", syntax error: %s", conf, line, buf); 2657517eab2Snate } 2667517eab2Snate } 2677517eab2Snate 2687517eab2Snate cmd = malloc(sizeof *cmd); 2697517eab2Snate if (cmd == NULL) 2707517eab2Snate err(1, "malloc failed"); 2717517eab2Snate cmd->next = cmds; 2727517eab2Snate cmds = cmd; 2737517eab2Snate cmd->line = line; 2747517eab2Snate 2757517eab2Snate if (strcmp(value, "*") == 0) { 2767517eab2Snate cmd->anyvalue = 1; 2777517eab2Snate } else { 2787517eab2Snate cmd->anyvalue = 0; 2797517eab2Snate if (sscanf(value, "%d", &cmd->value) != 1) { 2807517eab2Snate if (isdemon) { 2817517eab2Snate syslog(LOG_WARNING, 2827517eab2Snate "config file `%s', line %d, " 283ca8e7c81Sjaredy "bad value: %s", 2847517eab2Snate conf, line, value); 2857517eab2Snate freecommands(cmds); 2865698d383Sguenther fclose(f); 2877517eab2Snate return (NULL); 2887517eab2Snate } else { 2897517eab2Snate errx(1, "config file `%s', line %d, " 290ca8e7c81Sjaredy "bad value: %s", 2917517eab2Snate conf, line, value); 2927517eab2Snate } 2937517eab2Snate } 2947517eab2Snate } 2957517eab2Snate 2967517eab2Snate coll[0] = 0; 2975698d383Sguenther d = hid_start_parse(repd, 1 << hid_input, reportid); 2985698d383Sguenther if (d == NULL) 2995698d383Sguenther err(1, "hid_start_parse failed"); 3005698d383Sguenther while (hid_get_item(d, &h)) { 3017517eab2Snate if (verbose > 2) 3027517eab2Snate printf("kind=%d usage=%x\n", h.kind, h.usage); 3037517eab2Snate if (h.flags & HIO_CONST) 3047517eab2Snate continue; 3057517eab2Snate switch (h.kind) { 3067517eab2Snate case hid_input: 3077517eab2Snate if (h.usage_minimum != 0 || 3087517eab2Snate h.usage_maximum != 0) { 3097517eab2Snate lo = h.usage_minimum; 3107517eab2Snate hi = h.usage_maximum; 3117517eab2Snate range = 1; 3127517eab2Snate } else { 3137517eab2Snate lo = h.usage; 3147517eab2Snate hi = h.usage; 3157517eab2Snate range = 0; 3167517eab2Snate } 3177517eab2Snate for (u = lo; u <= hi; u++) { 3187517eab2Snate snprintf(usage, sizeof usage, "%s:%s", 3197517eab2Snate hid_usage_page(HID_PAGE(u)), 3207517eab2Snate hid_usage_in_page(u)); 3217517eab2Snate if (verbose > 2) 3227517eab2Snate printf("usage %s\n", usage); 3237517eab2Snate if (!strcasecmp(usage, name)) 3247517eab2Snate goto foundhid; 3257517eab2Snate if (coll[0]) { 3267517eab2Snate snprintf(usage, sizeof usage, 3277517eab2Snate "%s.%s:%s", coll+1, 3287517eab2Snate hid_usage_page(HID_PAGE(u)), 3297517eab2Snate hid_usage_in_page(u)); 3307517eab2Snate if (verbose > 2) 3317517eab2Snate printf("usage %s\n", 3327517eab2Snate usage); 3337517eab2Snate if (!strcasecmp(usage, name)) 3347517eab2Snate goto foundhid; 3357517eab2Snate } 3367517eab2Snate } 3377517eab2Snate break; 3387517eab2Snate case hid_collection: 3397517eab2Snate snprintf(coll + strlen(coll), 3407517eab2Snate sizeof coll - strlen(coll), ".%s:%s", 3417517eab2Snate hid_usage_page(HID_PAGE(h.usage)), 3427517eab2Snate hid_usage_in_page(h.usage)); 3437517eab2Snate break; 3447517eab2Snate case hid_endcollection: 3457517eab2Snate if (coll[0]) 3467517eab2Snate *strrchr(coll, '.') = 0; 3477517eab2Snate break; 3487517eab2Snate default: 3497517eab2Snate break; 3507517eab2Snate } 3517517eab2Snate } 3525698d383Sguenther hid_end_parse(d); 3537517eab2Snate if (ignore) { 3547517eab2Snate if (verbose) 355c20d9ad4Sjsyn warnx("ignore item '%s'", name); 3565698d383Sguenther /* pop and free this ignored item */ 3575698d383Sguenther cmds = cmd->next; 3585698d383Sguenther free(cmd); 3597517eab2Snate continue; 3607517eab2Snate } 3617517eab2Snate if (isdemon) { 3627517eab2Snate syslog(LOG_WARNING, "config file `%s', line %d, HID " 363ca8e7c81Sjaredy "item not found: `%s'", conf, line, name); 3647517eab2Snate freecommands(cmds); 3655698d383Sguenther fclose(f); 3667517eab2Snate return (NULL); 3677517eab2Snate } else { 3687517eab2Snate errx(1, "config file `%s', line %d, HID item " 369ca8e7c81Sjaredy "not found: `%s'", conf, line, name); 3707517eab2Snate } 3717517eab2Snate 3727517eab2Snate foundhid: 3737517eab2Snate hid_end_parse(d); 3747517eab2Snate cmd->item = h; 3757517eab2Snate cmd->name = strdup(name); 3767517eab2Snate cmd->action = strdup(action); 3777517eab2Snate if (range) { 3787517eab2Snate if (cmd->value == 1) 3797517eab2Snate cmd->value = u - lo; 3807517eab2Snate else 3817517eab2Snate cmd->value = -1; 3827517eab2Snate } 3837517eab2Snate 3847517eab2Snate if (verbose) 3857517eab2Snate printf("PARSE:%d %s, %d, '%s'\n", cmd->line, name, 3867517eab2Snate cmd->value, cmd->action); 3877517eab2Snate } 3887517eab2Snate fclose(f); 3897517eab2Snate return (cmds); 3907517eab2Snate } 3917517eab2Snate 3927517eab2Snate void 3937517eab2Snate docmd(struct command *cmd, int value, const char *hid, int argc, char **argv) 3947517eab2Snate { 3957517eab2Snate char cmdbuf[SIZE], *p, *q; 3967517eab2Snate size_t len; 3977517eab2Snate int n, r; 398a368f40fSjasper pid_t pid; 3997517eab2Snate 400940c232bSckuethe if (cmd->action == NULL) { 401940c232bSckuethe if (verbose) 402940c232bSckuethe printf("no action for device %s value %d\n", 403940c232bSckuethe hid, value); 404940c232bSckuethe return; 405940c232bSckuethe } 4067517eab2Snate for (p = cmd->action, q = cmdbuf; *p && q < &cmdbuf[SIZE-1]; ) { 4077517eab2Snate if (*p == '$') { 4087517eab2Snate p++; 4097517eab2Snate len = &cmdbuf[SIZE-1] - q; 4100e71acc1Sderaadt if (isdigit((unsigned char)*p)) { 4117517eab2Snate n = strtol(p, &p, 10) - 1; 4127517eab2Snate if (n >= 0 && n < argc) { 4137517eab2Snate strncpy(q, argv[n], len); 4147517eab2Snate q += strlen(q); 4157517eab2Snate } 4167517eab2Snate } else if (*p == 'V') { 4177517eab2Snate p++; 4187517eab2Snate snprintf(q, len, "%d", value); 4197517eab2Snate q += strlen(q); 4207517eab2Snate } else if (*p == 'N') { 4217517eab2Snate p++; 4227517eab2Snate strncpy(q, cmd->name, len); 4237517eab2Snate q += strlen(q); 4247517eab2Snate } else if (*p == 'H') { 4257517eab2Snate p++; 4267517eab2Snate strncpy(q, hid, len); 4277517eab2Snate q += strlen(q); 4287517eab2Snate } else if (*p) { 4297517eab2Snate *q++ = *p++; 4307517eab2Snate } 4317517eab2Snate } else { 4327517eab2Snate *q++ = *p++; 4337517eab2Snate } 4347517eab2Snate } 4357517eab2Snate *q = 0; 4367517eab2Snate 437a368f40fSjasper pid = fork(); 438a368f40fSjasper if (pid == -1) 439a368f40fSjasper warn("fork failed"); 440a368f40fSjasper else if (pid == 0) { 441a368f40fSjasper setpgid(0, 0); 4427517eab2Snate if (verbose) 443a368f40fSjasper printf("executing '%s'\n", cmdbuf); 444*53408464Skrw r = execl(_PATH_BSHELL, "sh", "-c", cmdbuf, (char *)NULL); 445a368f40fSjasper err(1, "execl"); 446a368f40fSjasper } 4477517eab2Snate } 4487517eab2Snate 4497517eab2Snate void 4507517eab2Snate freecommands(struct command *cmd) 4517517eab2Snate { 4527517eab2Snate struct command *next; 4537517eab2Snate 4547517eab2Snate while (cmd) { 4557517eab2Snate next = cmd->next; 4567517eab2Snate free(cmd); 4577517eab2Snate cmd = next; 4587517eab2Snate } 4597517eab2Snate } 460