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