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