usbhidaction.c revision 171101
1113288Smdodd/*      $NetBSD: usbhidaction.c,v 1.8 2002/06/11 06:06:21 itojun Exp $ */
2113288Smdodd/*	$FreeBSD: head/usr.bin/usbhidaction/usbhidaction.c 171101 2007-06-30 03:58:01Z imp $ */
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 * 3. All advertising materials mentioning features or use of this software
20113288Smdodd *    must display the following acknowledgement:
21113288Smdodd *        This product includes software developed by the NetBSD
22113288Smdodd *        Foundation, Inc. and its contributors.
23113288Smdodd * 4. Neither the name of The NetBSD Foundation nor the names of its
24113288Smdodd *    contributors may be used to endorse or promote products derived
25113288Smdodd *    from this software without specific prior written permission.
26113288Smdodd *
27113288Smdodd * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28113288Smdodd * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29113288Smdodd * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30113288Smdodd * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31113288Smdodd * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32113288Smdodd * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33113288Smdodd * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34113288Smdodd * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35113288Smdodd * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36113288Smdodd * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37113288Smdodd * POSSIBILITY OF SUCH DAMAGE.
38113288Smdodd */
39113288Smdodd
40113288Smdodd#include <stdio.h>
41113288Smdodd#include <stdlib.h>
42113288Smdodd#include <string.h>
43113288Smdodd#include <ctype.h>
44113288Smdodd#include <err.h>
45113288Smdodd#include <fcntl.h>
46113288Smdodd#include <limits.h>
47113288Smdodd#include <unistd.h>
48113288Smdodd#include <sys/types.h>
49113288Smdodd#include <sys/ioctl.h>
50113288Smdodd#include <dev/usb/usb.h>
51113288Smdodd#include <dev/usb/usbhid.h>
52113288Smdodd#include <usbhid.h>
53113288Smdodd#include <syslog.h>
54113288Smdodd#include <signal.h>
55113288Smdodd#include <errno.h>
56113288Smdodd#include <sys/stat.h>
57113288Smdodd
58113288Smdoddstatic int	verbose = 0;
59113288Smdoddstatic int	isdemon = 0;
60113288Smdoddstatic int	reparse = 1;
61126774Sdwmalonestatic const char *	pidfile = "/var/run/usbaction.pid";
62113288Smdodd
63113288Smdoddstruct command {
64113288Smdodd	struct command *next;
65113288Smdodd	int line;
66113288Smdodd
67113288Smdodd	struct hid_item item;
68113288Smdodd	int value;
69113309Smdodd	int lastseen;
70113309Smdodd	int lastused;
71113309Smdodd	int debounce;
72113288Smdodd	char anyvalue;
73113288Smdodd	char *name;
74113288Smdodd	char *action;
75113288Smdodd};
76113288Smdoddstruct command *commands;
77113288Smdodd
78113288Smdodd#define SIZE 4000
79113288Smdodd
80113288Smdoddvoid usage(void);
81113288Smdoddstruct command *parse_conf(const char *, report_desc_t, int, int);
82113288Smdoddvoid docmd(struct command *, int, const char *, int, char **);
83113288Smdoddvoid freecommands(struct command *);
84113288Smdodd
85113288Smdoddstatic void
86126774Sdwmalonesighup(int sig __unused)
87113288Smdodd{
88113288Smdodd	reparse = 1;
89113288Smdodd}
90113288Smdodd
91113288Smdoddint
92113288Smdoddmain(int argc, char **argv)
93113288Smdodd{
94113288Smdodd	const char *conf = NULL;
95113288Smdodd	const char *dev = NULL;
96171101Simp	const char *table = NULL;
97126774Sdwmalone	int fd, fp, ch, n, val, i;
98126774Sdwmalone	size_t sz, sz1;
99113309Smdodd	int demon, ignore, dieearly;
100113288Smdodd	report_desc_t repd;
101113288Smdodd	char buf[100];
102113288Smdodd	char devnamebuf[PATH_MAX];
103113288Smdodd	struct command *cmd;
104113288Smdodd	int reportid;
105113288Smdodd
106113288Smdodd	demon = 1;
107113288Smdodd	ignore = 0;
108113309Smdodd	dieearly = 0;
109171101Simp	while ((ch = getopt(argc, argv, "c:def:ip:t:v")) != -1) {
110113288Smdodd		switch(ch) {
111113288Smdodd		case 'c':
112113288Smdodd			conf = optarg;
113113288Smdodd			break;
114113288Smdodd		case 'd':
115113288Smdodd			demon ^= 1;
116113288Smdodd			break;
117113309Smdodd		case 'e':
118113309Smdodd			dieearly = 1;
119113309Smdodd			break;
120113288Smdodd		case 'i':
121113288Smdodd			ignore++;
122113288Smdodd			break;
123113288Smdodd		case 'f':
124113288Smdodd			dev = optarg;
125113288Smdodd			break;
126113288Smdodd		case 'p':
127113288Smdodd			pidfile = optarg;
128113288Smdodd			break;
129171101Simp		case 't':
130171101Simp			table = optarg;
131171101Simp			break;
132113288Smdodd		case 'v':
133113288Smdodd			demon = 0;
134113288Smdodd			verbose++;
135113288Smdodd			break;
136113288Smdodd		case '?':
137113288Smdodd		default:
138113288Smdodd			usage();
139113288Smdodd		}
140113288Smdodd	}
141113288Smdodd	argc -= optind;
142113288Smdodd	argv += optind;
143113288Smdodd
144113288Smdodd	if (conf == NULL || dev == NULL)
145113288Smdodd		usage();
146113288Smdodd
147171101Simp	hid_init(table);
148113288Smdodd
149113288Smdodd	if (dev[0] != '/') {
150113288Smdodd		snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s",
151113288Smdodd			 isdigit(dev[0]) ? "uhid" : "", dev);
152113288Smdodd		dev = devnamebuf;
153113288Smdodd	}
154113288Smdodd
155113288Smdodd	fd = open(dev, O_RDWR);
156113288Smdodd	if (fd < 0)
157113288Smdodd		err(1, "%s", dev);
158113288Smdodd	if (ioctl(fd, USB_GET_REPORT_ID, &reportid) < 0)
159113288Smdodd		reportid = -1;
160113288Smdodd	repd = hid_get_report_desc(fd);
161113288Smdodd	if (repd == NULL)
162113288Smdodd		err(1, "hid_get_report_desc() failed");
163113288Smdodd
164113288Smdodd	commands = parse_conf(conf, repd, reportid, ignore);
165113288Smdodd
166126774Sdwmalone	sz = (size_t)hid_report_size(repd, hid_input, reportid);
167113288Smdodd
168113288Smdodd	if (verbose)
169126774Sdwmalone		printf("report size %zu\n", sz);
170113288Smdodd	if (sz > sizeof buf)
171113288Smdodd		errx(1, "report too large");
172113288Smdodd
173113288Smdodd	(void)signal(SIGHUP, sighup);
174113288Smdodd
175113288Smdodd	if (demon) {
176113288Smdodd		fp = open(pidfile, O_WRONLY|O_CREAT, S_IRUSR|S_IRGRP|S_IROTH);
177113288Smdodd		if (fp >= 0) {
178126774Sdwmalone			sz1 = snprintf(buf, sizeof buf, "%d\n", getpid());
179126774Sdwmalone			if (sz1 > sizeof buf)
180126774Sdwmalone				sz1 = sizeof buf;
181126774Sdwmalone			write(fp, buf, sz1);
182113288Smdodd			close(fp);
183113288Smdodd		} else
184113288Smdodd			err(1, "%s", pidfile);
185113309Smdodd		if (daemon(0, 0) < 0)
186113309Smdodd			err(1, "daemon()");
187113288Smdodd		isdemon = 1;
188113288Smdodd	}
189113288Smdodd
190113288Smdodd	for(;;) {
191113288Smdodd		n = read(fd, buf, sz);
192113288Smdodd		if (verbose > 2) {
193113288Smdodd			printf("read %d bytes:", n);
194113288Smdodd			for (i = 0; i < n; i++)
195113288Smdodd				printf(" %02x", buf[i]);
196113288Smdodd			printf("\n");
197113288Smdodd		}
198113288Smdodd		if (n < 0) {
199113288Smdodd			if (verbose)
200113288Smdodd				err(1, "read");
201113288Smdodd			else
202113288Smdodd				exit(1);
203113288Smdodd		}
204113288Smdodd#if 0
205113288Smdodd		if (n != sz) {
206113288Smdodd			err(2, "read size");
207113288Smdodd		}
208113288Smdodd#endif
209113288Smdodd		for (cmd = commands; cmd; cmd = cmd->next) {
210113288Smdodd			val = hid_get_data(buf, &cmd->item);
211113309Smdodd			if (cmd->value != val && cmd->anyvalue == 0)
212113309Smdodd				goto next;
213113309Smdodd			if ((cmd->debounce == 0) ||
214113328Smdodd			    ((cmd->debounce == 1) && ((cmd->lastseen == -1) ||
215113309Smdodd					       (cmd->lastseen != val)))) {
216113288Smdodd				docmd(cmd, val, dev, argc, argv);
217113309Smdodd				goto next;
218113309Smdodd			}
219113309Smdodd			if ((cmd->debounce > 1) &&
220113309Smdodd			    ((cmd->lastused == -1) ||
221113309Smdodd			     (abs(cmd->lastused - val) >= cmd->debounce))) {
222113309Smdodd				docmd(cmd, val, dev, argc, argv);
223113309Smdodd				cmd->lastused = val;
224113309Smdodd				goto next;
225113309Smdodd			}
226113309Smdoddnext:
227113309Smdodd			cmd->lastseen = val;
228113288Smdodd		}
229113309Smdodd
230113309Smdodd		if (dieearly)
231113309Smdodd			exit(0);
232113309Smdodd
233113288Smdodd		if (reparse) {
234113288Smdodd			struct command *cmds =
235113288Smdodd			    parse_conf(conf, repd, reportid, ignore);
236113288Smdodd			if (cmds) {
237113288Smdodd				freecommands(commands);
238113288Smdodd				commands = cmds;
239113288Smdodd			}
240113288Smdodd			reparse = 0;
241113288Smdodd		}
242113288Smdodd	}
243113288Smdodd
244113288Smdodd	exit(0);
245113288Smdodd}
246113288Smdodd
247113288Smdoddvoid
248113288Smdoddusage(void)
249113288Smdodd{
250113288Smdodd
251113309Smdodd	fprintf(stderr, "Usage: %s [-deiv] -c config_file -f hid_dev "
252171101Simp		"[-p pidfile] [-t tablefile]\n", getprogname());
253113288Smdodd	exit(1);
254113288Smdodd}
255113288Smdodd
256113288Smdoddstatic int
257113288Smdoddpeek(FILE *f)
258113288Smdodd{
259113288Smdodd	int c;
260113288Smdodd
261113288Smdodd	c = getc(f);
262113288Smdodd	if (c != EOF)
263113288Smdodd		ungetc(c, f);
264113288Smdodd	return c;
265113288Smdodd}
266113288Smdodd
267113288Smdoddstruct command *
268113288Smdoddparse_conf(const char *conf, report_desc_t repd, int reportid, int ignore)
269113288Smdodd{
270113288Smdodd	FILE *f;
271113288Smdodd	char *p;
272113288Smdodd	int line;
273113309Smdodd	char buf[SIZE], name[SIZE], value[SIZE], debounce[SIZE], action[SIZE];
274126774Sdwmalone	char usbuf[SIZE], coll[SIZE];
275113288Smdodd	struct command *cmd, *cmds;
276113288Smdodd	struct hid_data *d;
277113288Smdodd	struct hid_item h;
278113288Smdodd	int u, lo, hi, range;
279113288Smdodd
280113288Smdodd
281113288Smdodd	f = fopen(conf, "r");
282113288Smdodd	if (f == NULL)
283113288Smdodd		err(1, "%s", conf);
284113288Smdodd
285113288Smdodd	cmds = NULL;
286113288Smdodd	for (line = 1; ; line++) {
287113288Smdodd		if (fgets(buf, sizeof buf, f) == NULL)
288113288Smdodd			break;
289113288Smdodd		if (buf[0] == '#' || buf[0] == '\n')
290113288Smdodd			continue;
291113288Smdodd		p = strchr(buf, '\n');
292113288Smdodd		while (p && isspace(peek(f))) {
293113288Smdodd			if (fgets(p, sizeof buf - strlen(buf), f) == NULL)
294113288Smdodd				break;
295113288Smdodd			p = strchr(buf, '\n');
296113288Smdodd		}
297113288Smdodd		if (p)
298113288Smdodd			*p = 0;
299113309Smdodd		if (sscanf(buf, "%s %s %s %[^\n]",
300113309Smdodd			   name, value, debounce, action) != 4) {
301113288Smdodd			if (isdemon) {
302113288Smdodd				syslog(LOG_WARNING, "config file `%s', line %d"
303113288Smdodd				       ", syntax error: %s", conf, line, buf);
304113288Smdodd				freecommands(cmds);
305113288Smdodd				return (NULL);
306113288Smdodd			} else {
307113288Smdodd				errx(1, "config file `%s', line %d,"
308113288Smdodd				     ", syntax error: %s", conf, line, buf);
309113288Smdodd			}
310113288Smdodd		}
311113288Smdodd
312113288Smdodd		cmd = malloc(sizeof *cmd);
313113288Smdodd		if (cmd == NULL)
314113288Smdodd			err(1, "malloc failed");
315113288Smdodd		cmd->next = cmds;
316113288Smdodd		cmds = cmd;
317113288Smdodd		cmd->line = line;
318113288Smdodd
319113288Smdodd		if (strcmp(value, "*") == 0) {
320113288Smdodd			cmd->anyvalue = 1;
321113288Smdodd		} else {
322113288Smdodd			cmd->anyvalue = 0;
323113288Smdodd			if (sscanf(value, "%d", &cmd->value) != 1) {
324113288Smdodd				if (isdemon) {
325113288Smdodd					syslog(LOG_WARNING,
326113288Smdodd					       "config file `%s', line %d, "
327113309Smdodd					       "bad value: %s (should be * or a number)\n",
328113288Smdodd					       conf, line, value);
329113288Smdodd					freecommands(cmds);
330113288Smdodd					return (NULL);
331113288Smdodd				} else {
332113288Smdodd					errx(1, "config file `%s', line %d, "
333113309Smdodd					     "bad value: %s (should be * or a number)\n",
334113288Smdodd					     conf, line, value);
335113288Smdodd				}
336113288Smdodd			}
337113288Smdodd		}
338113288Smdodd
339113309Smdodd		if (sscanf(debounce, "%d", &cmd->debounce) != 1) {
340113309Smdodd			if (isdemon) {
341113309Smdodd				syslog(LOG_WARNING,
342113309Smdodd				       "config file `%s', line %d, "
343113309Smdodd				       "bad value: %s (should be a number >= 0)\n",
344113309Smdodd				       conf, line, debounce);
345113309Smdodd				freecommands(cmds);
346113309Smdodd				return (NULL);
347113309Smdodd			} else {
348113309Smdodd				errx(1, "config file `%s', line %d, "
349113309Smdodd				     "bad value: %s (should be a number >= 0)\n",
350113309Smdodd				     conf, line, debounce);
351113309Smdodd			}
352113309Smdodd		}
353113309Smdodd
354113288Smdodd		coll[0] = 0;
355113288Smdodd		for (d = hid_start_parse(repd, 1 << hid_input, reportid);
356113288Smdodd		     hid_get_item(d, &h); ) {
357113288Smdodd			if (verbose > 2)
358113288Smdodd				printf("kind=%d usage=%x\n", h.kind, h.usage);
359113288Smdodd			if (h.flags & HIO_CONST)
360113288Smdodd				continue;
361113288Smdodd			switch (h.kind) {
362113288Smdodd			case hid_input:
363113288Smdodd				if (h.usage_minimum != 0 ||
364113288Smdodd				    h.usage_maximum != 0) {
365113288Smdodd					lo = h.usage_minimum;
366113288Smdodd					hi = h.usage_maximum;
367113288Smdodd					range = 1;
368113288Smdodd				} else {
369113288Smdodd					lo = h.usage;
370113288Smdodd					hi = h.usage;
371113288Smdodd					range = 0;
372113288Smdodd				}
373113288Smdodd				for (u = lo; u <= hi; u++) {
374126774Sdwmalone					snprintf(usbuf, sizeof usbuf,  "%s:%s",
375113288Smdodd						 hid_usage_page(HID_PAGE(u)),
376113288Smdodd						 hid_usage_in_page(u));
377113288Smdodd					if (verbose > 2)
378126774Sdwmalone						printf("usage %s\n", usbuf);
379126774Sdwmalone					if (!strcasecmp(usbuf, name))
380113288Smdodd						goto foundhid;
381113288Smdodd					if (coll[0]) {
382126774Sdwmalone						snprintf(usbuf, sizeof usbuf,
383113288Smdodd						  "%s.%s:%s", coll+1,
384113288Smdodd						  hid_usage_page(HID_PAGE(u)),
385113288Smdodd						  hid_usage_in_page(u));
386113288Smdodd						if (verbose > 2)
387113288Smdodd							printf("usage %s\n",
388126774Sdwmalone							       usbuf);
389126774Sdwmalone						if (!strcasecmp(usbuf, name))
390113288Smdodd							goto foundhid;
391113288Smdodd					}
392113288Smdodd				}
393113288Smdodd				break;
394113288Smdodd			case hid_collection:
395113288Smdodd				snprintf(coll + strlen(coll),
396113288Smdodd				    sizeof coll - strlen(coll),  ".%s:%s",
397113288Smdodd				    hid_usage_page(HID_PAGE(h.usage)),
398113288Smdodd				    hid_usage_in_page(h.usage));
399113288Smdodd				break;
400113288Smdodd			case hid_endcollection:
401113288Smdodd				if (coll[0])
402113288Smdodd					*strrchr(coll, '.') = 0;
403113288Smdodd				break;
404113288Smdodd			default:
405113288Smdodd				break;
406113288Smdodd			}
407113288Smdodd		}
408113288Smdodd		if (ignore) {
409113288Smdodd			if (verbose)
410113288Smdodd				warnx("ignore item '%s'", name);
411113288Smdodd			continue;
412113288Smdodd		}
413113288Smdodd		if (isdemon) {
414113288Smdodd			syslog(LOG_WARNING, "config file `%s', line %d, HID "
415113288Smdodd			       "item not found: `%s'\n", conf, line, name);
416113288Smdodd			freecommands(cmds);
417113288Smdodd			return (NULL);
418113288Smdodd		} else {
419113288Smdodd			errx(1, "config file `%s', line %d, HID item "
420113288Smdodd			     "not found: `%s'\n", conf, line, name);
421113288Smdodd		}
422113288Smdodd
423113288Smdodd	foundhid:
424113288Smdodd		hid_end_parse(d);
425113328Smdodd		cmd->lastseen = -1;
426113328Smdodd		cmd->lastused = -1;
427113288Smdodd		cmd->item = h;
428113288Smdodd		cmd->name = strdup(name);
429113288Smdodd		cmd->action = strdup(action);
430113288Smdodd		if (range) {
431113288Smdodd			if (cmd->value == 1)
432113288Smdodd				cmd->value = u - lo;
433113288Smdodd			else
434113288Smdodd				cmd->value = -1;
435113288Smdodd		}
436113288Smdodd
437113288Smdodd		if (verbose)
438113288Smdodd			printf("PARSE:%d %s, %d, '%s'\n", cmd->line, name,
439113288Smdodd			       cmd->value, cmd->action);
440113288Smdodd	}
441113288Smdodd	fclose(f);
442113288Smdodd	return (cmds);
443113288Smdodd}
444113288Smdodd
445113288Smdoddvoid
446113288Smdodddocmd(struct command *cmd, int value, const char *hid, int argc, char **argv)
447113288Smdodd{
448113288Smdodd	char cmdbuf[SIZE], *p, *q;
449113288Smdodd	size_t len;
450113288Smdodd	int n, r;
451113288Smdodd
452113288Smdodd	for (p = cmd->action, q = cmdbuf; *p && q < &cmdbuf[SIZE-1]; ) {
453113288Smdodd		if (*p == '$') {
454113288Smdodd			p++;
455113288Smdodd			len = &cmdbuf[SIZE-1] - q;
456113288Smdodd			if (isdigit(*p)) {
457113288Smdodd				n = strtol(p, &p, 10) - 1;
458113288Smdodd				if (n >= 0 && n < argc) {
459113288Smdodd					strncpy(q, argv[n], len);
460113288Smdodd					q += strlen(q);
461113288Smdodd				}
462113288Smdodd			} else if (*p == 'V') {
463113288Smdodd				p++;
464113288Smdodd				snprintf(q, len, "%d", value);
465113288Smdodd				q += strlen(q);
466113288Smdodd			} else if (*p == 'N') {
467113288Smdodd				p++;
468113288Smdodd				strncpy(q, cmd->name, len);
469113288Smdodd				q += strlen(q);
470113288Smdodd			} else if (*p == 'H') {
471113288Smdodd				p++;
472113288Smdodd				strncpy(q, hid, len);
473113288Smdodd				q += strlen(q);
474113288Smdodd			} else if (*p) {
475113288Smdodd				*q++ = *p++;
476113288Smdodd			}
477113288Smdodd		} else {
478113288Smdodd			*q++ = *p++;
479113288Smdodd		}
480113288Smdodd	}
481113288Smdodd	*q = 0;
482113288Smdodd
483113288Smdodd	if (verbose)
484113288Smdodd		printf("system '%s'\n", cmdbuf);
485113288Smdodd	r = system(cmdbuf);
486113288Smdodd	if (verbose > 1 && r)
487113288Smdodd		printf("return code = 0x%x\n", r);
488113288Smdodd}
489113288Smdodd
490113288Smdoddvoid
491113288Smdoddfreecommands(struct command *cmd)
492113288Smdodd{
493113288Smdodd	struct command *next;
494113288Smdodd
495113288Smdodd	while (cmd) {
496113288Smdodd		next = cmd->next;
497113288Smdodd		free(cmd);
498113288Smdodd		cmd = next;
499113288Smdodd	}
500113288Smdodd}
501