1113288Smdodd/*      $NetBSD: usbhidaction.c,v 1.8 2002/06/11 06:06:21 itojun Exp $ */
2113288Smdodd/*	$FreeBSD$ */
3113288Smdodd
4113288Smdodd/*
5113288Smdodd * Copyright (c) 2000, 2002 The NetBSD Foundation, Inc.
6113288Smdodd * All rights reserved.
7113288Smdodd *
8113288Smdodd * This code is derived from software contributed to The NetBSD Foundation
9113288Smdodd * by Lennart Augustsson <lennart@augustsson.net>.
10113288Smdodd *
11113288Smdodd * Redistribution and use in source and binary forms, with or without
12113288Smdodd * modification, are permitted provided that the following conditions
13113288Smdodd * are met:
14113288Smdodd * 1. Redistributions of source code must retain the above copyright
15113288Smdodd *    notice, this list of conditions and the following disclaimer.
16113288Smdodd * 2. Redistributions in binary form must reproduce the above copyright
17113288Smdodd *    notice, this list of conditions and the following disclaimer in the
18113288Smdodd *    documentation and/or other materials provided with the distribution.
19113288Smdodd *
20113288Smdodd * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21113288Smdodd * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22113288Smdodd * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23113288Smdodd * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24113288Smdodd * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25113288Smdodd * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26113288Smdodd * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27113288Smdodd * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28113288Smdodd * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29113288Smdodd * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30113288Smdodd * POSSIBILITY OF SUCH DAMAGE.
31113288Smdodd */
32113288Smdodd
33113288Smdodd#include <stdio.h>
34113288Smdodd#include <stdlib.h>
35113288Smdodd#include <string.h>
36113288Smdodd#include <ctype.h>
37113288Smdodd#include <err.h>
38113288Smdodd#include <fcntl.h>
39113288Smdodd#include <limits.h>
40113288Smdodd#include <unistd.h>
41113288Smdodd#include <sys/types.h>
42188945Sthompsa#include <dev/usb/usbhid.h>
43113288Smdodd#include <usbhid.h>
44113288Smdodd#include <syslog.h>
45113288Smdodd#include <signal.h>
46200462Sdelphij#include <errno.h>
47113288Smdodd#include <sys/stat.h>
48113288Smdodd
49113288Smdoddstatic int	verbose = 0;
50113288Smdoddstatic int	isdemon = 0;
51113288Smdoddstatic int	reparse = 1;
52126774Sdwmalonestatic const char *	pidfile = "/var/run/usbaction.pid";
53113288Smdodd
54113288Smdoddstruct command {
55113288Smdodd	struct command *next;
56113288Smdodd	int line;
57113288Smdodd
58113288Smdodd	struct hid_item item;
59113288Smdodd	int value;
60113309Smdodd	int lastseen;
61113309Smdodd	int lastused;
62113309Smdodd	int debounce;
63113288Smdodd	char anyvalue;
64113288Smdodd	char *name;
65113288Smdodd	char *action;
66113288Smdodd};
67113288Smdoddstruct command *commands;
68113288Smdodd
69113288Smdodd#define SIZE 4000
70113288Smdodd
71113288Smdoddvoid usage(void);
72113288Smdoddstruct command *parse_conf(const char *, report_desc_t, int, int);
73113288Smdoddvoid docmd(struct command *, int, const char *, int, char **);
74113288Smdoddvoid freecommands(struct command *);
75113288Smdodd
76113288Smdoddstatic void
77126774Sdwmalonesighup(int sig __unused)
78113288Smdodd{
79113288Smdodd	reparse = 1;
80113288Smdodd}
81113288Smdodd
82113288Smdoddint
83113288Smdoddmain(int argc, char **argv)
84113288Smdodd{
85113288Smdodd	const char *conf = NULL;
86113288Smdodd	const char *dev = NULL;
87171101Simp	const char *table = NULL;
88126774Sdwmalone	int fd, fp, ch, n, val, i;
89126774Sdwmalone	size_t sz, sz1;
90113309Smdodd	int demon, ignore, dieearly;
91113288Smdodd	report_desc_t repd;
92113288Smdodd	char buf[100];
93113288Smdodd	char devnamebuf[PATH_MAX];
94113288Smdodd	struct command *cmd;
95224511Smav	int reportid = -1;
96113288Smdodd
97113288Smdodd	demon = 1;
98113288Smdodd	ignore = 0;
99113309Smdodd	dieearly = 0;
100224511Smav	while ((ch = getopt(argc, argv, "c:def:ip:r:t:v")) != -1) {
101113288Smdodd		switch(ch) {
102113288Smdodd		case 'c':
103113288Smdodd			conf = optarg;
104113288Smdodd			break;
105113288Smdodd		case 'd':
106113288Smdodd			demon ^= 1;
107113288Smdodd			break;
108113309Smdodd		case 'e':
109113309Smdodd			dieearly = 1;
110113309Smdodd			break;
111113288Smdodd		case 'i':
112113288Smdodd			ignore++;
113113288Smdodd			break;
114113288Smdodd		case 'f':
115113288Smdodd			dev = optarg;
116113288Smdodd			break;
117113288Smdodd		case 'p':
118113288Smdodd			pidfile = optarg;
119113288Smdodd			break;
120224511Smav		case 'r':
121224511Smav			reportid = atoi(optarg);
122224511Smav			break;
123171101Simp		case 't':
124171101Simp			table = optarg;
125171101Simp			break;
126113288Smdodd		case 'v':
127113288Smdodd			demon = 0;
128113288Smdodd			verbose++;
129113288Smdodd			break;
130113288Smdodd		case '?':
131113288Smdodd		default:
132113288Smdodd			usage();
133113288Smdodd		}
134113288Smdodd	}
135113288Smdodd	argc -= optind;
136113288Smdodd	argv += optind;
137113288Smdodd
138113288Smdodd	if (conf == NULL || dev == NULL)
139113288Smdodd		usage();
140113288Smdodd
141171101Simp	hid_init(table);
142113288Smdodd
143113288Smdodd	if (dev[0] != '/') {
144113288Smdodd		snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s",
145113288Smdodd			 isdigit(dev[0]) ? "uhid" : "", dev);
146113288Smdodd		dev = devnamebuf;
147113288Smdodd	}
148113288Smdodd
149113288Smdodd	fd = open(dev, O_RDWR);
150113288Smdodd	if (fd < 0)
151113288Smdodd		err(1, "%s", dev);
152113288Smdodd	repd = hid_get_report_desc(fd);
153113288Smdodd	if (repd == NULL)
154113288Smdodd		err(1, "hid_get_report_desc() failed");
155113288Smdodd
156113288Smdodd	commands = parse_conf(conf, repd, reportid, ignore);
157113288Smdodd
158224511Smav	sz = (size_t)hid_report_size(repd, hid_input, -1);
159113288Smdodd
160113288Smdodd	if (verbose)
161126774Sdwmalone		printf("report size %zu\n", sz);
162113288Smdodd	if (sz > sizeof buf)
163113288Smdodd		errx(1, "report too large");
164113288Smdodd
165113288Smdodd	(void)signal(SIGHUP, sighup);
166113288Smdodd
167113288Smdodd	if (demon) {
168113288Smdodd		fp = open(pidfile, O_WRONLY|O_CREAT, S_IRUSR|S_IRGRP|S_IROTH);
169113288Smdodd		if (fp >= 0) {
170212048Skevlo			sz1 = snprintf(buf, sizeof buf, "%ld\n",
171212048Skevlo			    (long)getpid());
172126774Sdwmalone			if (sz1 > sizeof buf)
173126774Sdwmalone				sz1 = sizeof buf;
174126774Sdwmalone			write(fp, buf, sz1);
175113288Smdodd			close(fp);
176113288Smdodd		} else
177113288Smdodd			err(1, "%s", pidfile);
178113309Smdodd		if (daemon(0, 0) < 0)
179113309Smdodd			err(1, "daemon()");
180113288Smdodd		isdemon = 1;
181113288Smdodd	}
182113288Smdodd
183113288Smdodd	for(;;) {
184113288Smdodd		n = read(fd, buf, sz);
185113288Smdodd		if (verbose > 2) {
186113288Smdodd			printf("read %d bytes:", n);
187113288Smdodd			for (i = 0; i < n; i++)
188113288Smdodd				printf(" %02x", buf[i]);
189113288Smdodd			printf("\n");
190113288Smdodd		}
191113288Smdodd		if (n < 0) {
192113288Smdodd			if (verbose)
193113288Smdodd				err(1, "read");
194113288Smdodd			else
195113288Smdodd				exit(1);
196113288Smdodd		}
197113288Smdodd#if 0
198113288Smdodd		if (n != sz) {
199113288Smdodd			err(2, "read size");
200113288Smdodd		}
201113288Smdodd#endif
202113288Smdodd		for (cmd = commands; cmd; cmd = cmd->next) {
203224511Smav			if (cmd->item.report_ID != 0 &&
204224511Smav			    buf[0] != cmd->item.report_ID)
205224511Smav				continue;
206224511Smav			if (cmd->item.flags & HIO_VARIABLE)
207224511Smav				val = hid_get_data(buf, &cmd->item);
208224511Smav			else {
209224511Smav				uint32_t pos = cmd->item.pos;
210224511Smav				for (i = 0; i < cmd->item.report_count; i++) {
211224511Smav					val = hid_get_data(buf, &cmd->item);
212224511Smav					if (val == cmd->value)
213224511Smav						break;
214224511Smav					cmd->item.pos += cmd->item.report_size;
215224511Smav				}
216224511Smav				cmd->item.pos = pos;
217224511Smav				val = (i < cmd->item.report_count) ?
218224511Smav				    cmd->value : -1;
219224511Smav			}
220113309Smdodd			if (cmd->value != val && cmd->anyvalue == 0)
221113309Smdodd				goto next;
222113309Smdodd			if ((cmd->debounce == 0) ||
223113328Smdodd			    ((cmd->debounce == 1) && ((cmd->lastseen == -1) ||
224113309Smdodd					       (cmd->lastseen != val)))) {
225113288Smdodd				docmd(cmd, val, dev, argc, argv);
226113309Smdodd				goto next;
227113309Smdodd			}
228113309Smdodd			if ((cmd->debounce > 1) &&
229113309Smdodd			    ((cmd->lastused == -1) ||
230113309Smdodd			     (abs(cmd->lastused - val) >= cmd->debounce))) {
231113309Smdodd				docmd(cmd, val, dev, argc, argv);
232113309Smdodd				cmd->lastused = val;
233113309Smdodd				goto next;
234113309Smdodd			}
235113309Smdoddnext:
236113309Smdodd			cmd->lastseen = val;
237113288Smdodd		}
238113309Smdodd
239113309Smdodd		if (dieearly)
240113309Smdodd			exit(0);
241113309Smdodd
242113288Smdodd		if (reparse) {
243113288Smdodd			struct command *cmds =
244113288Smdodd			    parse_conf(conf, repd, reportid, ignore);
245113288Smdodd			if (cmds) {
246113288Smdodd				freecommands(commands);
247113288Smdodd				commands = cmds;
248113288Smdodd			}
249113288Smdodd			reparse = 0;
250113288Smdodd		}
251113288Smdodd	}
252113288Smdodd
253113288Smdodd	exit(0);
254113288Smdodd}
255113288Smdodd
256113288Smdoddvoid
257113288Smdoddusage(void)
258113288Smdodd{
259113288Smdodd
260113309Smdodd	fprintf(stderr, "Usage: %s [-deiv] -c config_file -f hid_dev "
261171101Simp		"[-p pidfile] [-t tablefile]\n", getprogname());
262113288Smdodd	exit(1);
263113288Smdodd}
264113288Smdodd
265113288Smdoddstatic int
266113288Smdoddpeek(FILE *f)
267113288Smdodd{
268113288Smdodd	int c;
269113288Smdodd
270113288Smdodd	c = getc(f);
271113288Smdodd	if (c != EOF)
272113288Smdodd		ungetc(c, f);
273113288Smdodd	return c;
274113288Smdodd}
275113288Smdodd
276113288Smdoddstruct command *
277113288Smdoddparse_conf(const char *conf, report_desc_t repd, int reportid, int ignore)
278113288Smdodd{
279113288Smdodd	FILE *f;
280113288Smdodd	char *p;
281113288Smdodd	int line;
282113309Smdodd	char buf[SIZE], name[SIZE], value[SIZE], debounce[SIZE], action[SIZE];
283229387Smav	char usbuf[SIZE], coll[SIZE], *tmp;
284113288Smdodd	struct command *cmd, *cmds;
285113288Smdodd	struct hid_data *d;
286113288Smdodd	struct hid_item h;
287229387Smav	int inst, cinst, u, lo, hi, range, t;
288113288Smdodd
289113288Smdodd	f = fopen(conf, "r");
290113288Smdodd	if (f == NULL)
291113288Smdodd		err(1, "%s", conf);
292113288Smdodd
293113288Smdodd	cmds = NULL;
294113288Smdodd	for (line = 1; ; line++) {
295113288Smdodd		if (fgets(buf, sizeof buf, f) == NULL)
296113288Smdodd			break;
297113288Smdodd		if (buf[0] == '#' || buf[0] == '\n')
298113288Smdodd			continue;
299113288Smdodd		p = strchr(buf, '\n');
300113288Smdodd		while (p && isspace(peek(f))) {
301113288Smdodd			if (fgets(p, sizeof buf - strlen(buf), f) == NULL)
302113288Smdodd				break;
303113288Smdodd			p = strchr(buf, '\n');
304113288Smdodd		}
305113288Smdodd		if (p)
306113288Smdodd			*p = 0;
307113309Smdodd		if (sscanf(buf, "%s %s %s %[^\n]",
308113309Smdodd			   name, value, debounce, action) != 4) {
309113288Smdodd			if (isdemon) {
310113288Smdodd				syslog(LOG_WARNING, "config file `%s', line %d"
311113288Smdodd				       ", syntax error: %s", conf, line, buf);
312113288Smdodd				freecommands(cmds);
313113288Smdodd				return (NULL);
314113288Smdodd			} else {
315113288Smdodd				errx(1, "config file `%s', line %d,"
316113288Smdodd				     ", syntax error: %s", conf, line, buf);
317113288Smdodd			}
318113288Smdodd		}
319229387Smav		tmp = strchr(name, '#');
320229387Smav		if (tmp != NULL) {
321229387Smav			*tmp = 0;
322229387Smav			inst = atoi(tmp + 1);
323229387Smav		} else
324229387Smav			inst = 0;
325113288Smdodd
326113288Smdodd		cmd = malloc(sizeof *cmd);
327113288Smdodd		if (cmd == NULL)
328113288Smdodd			err(1, "malloc failed");
329113288Smdodd		cmd->next = cmds;
330113288Smdodd		cmds = cmd;
331113288Smdodd		cmd->line = line;
332113288Smdodd
333113288Smdodd		if (strcmp(value, "*") == 0) {
334113288Smdodd			cmd->anyvalue = 1;
335113288Smdodd		} else {
336113288Smdodd			cmd->anyvalue = 0;
337113288Smdodd			if (sscanf(value, "%d", &cmd->value) != 1) {
338113288Smdodd				if (isdemon) {
339113288Smdodd					syslog(LOG_WARNING,
340113288Smdodd					       "config file `%s', line %d, "
341113309Smdodd					       "bad value: %s (should be * or a number)\n",
342113288Smdodd					       conf, line, value);
343113288Smdodd					freecommands(cmds);
344113288Smdodd					return (NULL);
345113288Smdodd				} else {
346113288Smdodd					errx(1, "config file `%s', line %d, "
347113309Smdodd					     "bad value: %s (should be * or a number)\n",
348113288Smdodd					     conf, line, value);
349113288Smdodd				}
350113288Smdodd			}
351113288Smdodd		}
352113288Smdodd
353113309Smdodd		if (sscanf(debounce, "%d", &cmd->debounce) != 1) {
354113309Smdodd			if (isdemon) {
355113309Smdodd				syslog(LOG_WARNING,
356113309Smdodd				       "config file `%s', line %d, "
357113309Smdodd				       "bad value: %s (should be a number >= 0)\n",
358113309Smdodd				       conf, line, debounce);
359113309Smdodd				freecommands(cmds);
360113309Smdodd				return (NULL);
361113309Smdodd			} else {
362113309Smdodd				errx(1, "config file `%s', line %d, "
363113309Smdodd				     "bad value: %s (should be a number >= 0)\n",
364113309Smdodd				     conf, line, debounce);
365113309Smdodd			}
366113309Smdodd		}
367113309Smdodd
368113288Smdodd		coll[0] = 0;
369229387Smav		cinst = 0;
370113288Smdodd		for (d = hid_start_parse(repd, 1 << hid_input, reportid);
371113288Smdodd		     hid_get_item(d, &h); ) {
372113288Smdodd			if (verbose > 2)
373113288Smdodd				printf("kind=%d usage=%x\n", h.kind, h.usage);
374113288Smdodd			if (h.flags & HIO_CONST)
375113288Smdodd				continue;
376113288Smdodd			switch (h.kind) {
377113288Smdodd			case hid_input:
378113288Smdodd				if (h.usage_minimum != 0 ||
379113288Smdodd				    h.usage_maximum != 0) {
380113288Smdodd					lo = h.usage_minimum;
381113288Smdodd					hi = h.usage_maximum;
382113288Smdodd					range = 1;
383113288Smdodd				} else {
384113288Smdodd					lo = h.usage;
385113288Smdodd					hi = h.usage;
386113288Smdodd					range = 0;
387113288Smdodd				}
388113288Smdodd				for (u = lo; u <= hi; u++) {
389113288Smdodd					if (coll[0]) {
390126774Sdwmalone						snprintf(usbuf, sizeof usbuf,
391113288Smdodd						  "%s.%s:%s", coll+1,
392229387Smav						  hid_usage_page(HID_PAGE(u)),
393113288Smdodd						  hid_usage_in_page(u));
394229387Smav					} else {
395229387Smav						snprintf(usbuf, sizeof usbuf,
396229387Smav						  "%s:%s",
397229387Smav						  hid_usage_page(HID_PAGE(u)),
398229387Smav						  hid_usage_in_page(u));
399113288Smdodd					}
400229387Smav					if (verbose > 2)
401229387Smav						printf("usage %s\n", usbuf);
402229387Smav					t = strlen(usbuf) - strlen(name);
403229387Smav					if (t > 0) {
404229387Smav						if (strcmp(usbuf + t, name))
405229387Smav							continue;
406229387Smav						if (usbuf[t - 1] != '.')
407229387Smav							continue;
408229387Smav					} else if (strcmp(usbuf, name))
409229387Smav						continue;
410229387Smav					if (inst == cinst++)
411229387Smav						goto foundhid;
412113288Smdodd				}
413113288Smdodd				break;
414113288Smdodd			case hid_collection:
415113288Smdodd				snprintf(coll + strlen(coll),
416113288Smdodd				    sizeof coll - strlen(coll),  ".%s:%s",
417113288Smdodd				    hid_usage_page(HID_PAGE(h.usage)),
418113288Smdodd				    hid_usage_in_page(h.usage));
419113288Smdodd				break;
420113288Smdodd			case hid_endcollection:
421113288Smdodd				if (coll[0])
422113288Smdodd					*strrchr(coll, '.') = 0;
423113288Smdodd				break;
424113288Smdodd			default:
425113288Smdodd				break;
426113288Smdodd			}
427113288Smdodd		}
428113288Smdodd		if (ignore) {
429113288Smdodd			if (verbose)
430113288Smdodd				warnx("ignore item '%s'", name);
431113288Smdodd			continue;
432113288Smdodd		}
433113288Smdodd		if (isdemon) {
434113288Smdodd			syslog(LOG_WARNING, "config file `%s', line %d, HID "
435113288Smdodd			       "item not found: `%s'\n", conf, line, name);
436113288Smdodd			freecommands(cmds);
437113288Smdodd			return (NULL);
438113288Smdodd		} else {
439113288Smdodd			errx(1, "config file `%s', line %d, HID item "
440113288Smdodd			     "not found: `%s'\n", conf, line, name);
441113288Smdodd		}
442113288Smdodd
443113288Smdodd	foundhid:
444113288Smdodd		hid_end_parse(d);
445113328Smdodd		cmd->lastseen = -1;
446113328Smdodd		cmd->lastused = -1;
447113288Smdodd		cmd->item = h;
448113288Smdodd		cmd->name = strdup(name);
449113288Smdodd		cmd->action = strdup(action);
450113288Smdodd		if (range) {
451113288Smdodd			if (cmd->value == 1)
452113288Smdodd				cmd->value = u - lo;
453113288Smdodd			else
454113288Smdodd				cmd->value = -1;
455113288Smdodd		}
456113288Smdodd
457113288Smdodd		if (verbose)
458113288Smdodd			printf("PARSE:%d %s, %d, '%s'\n", cmd->line, name,
459113288Smdodd			       cmd->value, cmd->action);
460113288Smdodd	}
461113288Smdodd	fclose(f);
462113288Smdodd	return (cmds);
463113288Smdodd}
464113288Smdodd
465113288Smdoddvoid
466113288Smdodddocmd(struct command *cmd, int value, const char *hid, int argc, char **argv)
467113288Smdodd{
468113288Smdodd	char cmdbuf[SIZE], *p, *q;
469113288Smdodd	size_t len;
470113288Smdodd	int n, r;
471113288Smdodd
472113288Smdodd	for (p = cmd->action, q = cmdbuf; *p && q < &cmdbuf[SIZE-1]; ) {
473113288Smdodd		if (*p == '$') {
474113288Smdodd			p++;
475113288Smdodd			len = &cmdbuf[SIZE-1] - q;
476113288Smdodd			if (isdigit(*p)) {
477113288Smdodd				n = strtol(p, &p, 10) - 1;
478113288Smdodd				if (n >= 0 && n < argc) {
479113288Smdodd					strncpy(q, argv[n], len);
480113288Smdodd					q += strlen(q);
481113288Smdodd				}
482113288Smdodd			} else if (*p == 'V') {
483113288Smdodd				p++;
484113288Smdodd				snprintf(q, len, "%d", value);
485113288Smdodd				q += strlen(q);
486113288Smdodd			} else if (*p == 'N') {
487113288Smdodd				p++;
488113288Smdodd				strncpy(q, cmd->name, len);
489113288Smdodd				q += strlen(q);
490113288Smdodd			} else if (*p == 'H') {
491113288Smdodd				p++;
492113288Smdodd				strncpy(q, hid, len);
493113288Smdodd				q += strlen(q);
494113288Smdodd			} else if (*p) {
495113288Smdodd				*q++ = *p++;
496113288Smdodd			}
497113288Smdodd		} else {
498113288Smdodd			*q++ = *p++;
499113288Smdodd		}
500113288Smdodd	}
501113288Smdodd	*q = 0;
502113288Smdodd
503113288Smdodd	if (verbose)
504113288Smdodd		printf("system '%s'\n", cmdbuf);
505113288Smdodd	r = system(cmdbuf);
506113288Smdodd	if (verbose > 1 && r)
507113288Smdodd		printf("return code = 0x%x\n", r);
508113288Smdodd}
509113288Smdodd
510113288Smdoddvoid
511113288Smdoddfreecommands(struct command *cmd)
512113288Smdodd{
513113288Smdodd	struct command *next;
514113288Smdodd
515113288Smdodd	while (cmd) {
516113288Smdodd		next = cmd->next;
517113288Smdodd		free(cmd);
518113288Smdodd		cmd = next;
519113288Smdodd	}
520113288Smdodd}
521