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