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