usbhidaction.c revision 126774
1113288Smdodd/*      $NetBSD: usbhidaction.c,v 1.8 2002/06/11 06:06:21 itojun Exp $ */
2113288Smdodd/*	$FreeBSD: head/usr.bin/usbhidaction/usbhidaction.c 126774 2004-03-09 11:35:43Z dwmalone $ */
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;
96126774Sdwmalone	int fd, fp, ch, n, val, i;
97126774Sdwmalone	size_t sz, sz1;
98113309Smdodd	int demon, ignore, dieearly;
99113288Smdodd	report_desc_t repd;
100113288Smdodd	char buf[100];
101113288Smdodd	char devnamebuf[PATH_MAX];
102113288Smdodd	struct command *cmd;
103113288Smdodd	int reportid;
104113288Smdodd
105113288Smdodd	demon = 1;
106113288Smdodd	ignore = 0;
107113309Smdodd	dieearly = 0;
108113309Smdodd	while ((ch = getopt(argc, argv, "c:def:ip:v")) != -1) {
109113288Smdodd		switch(ch) {
110113288Smdodd		case 'c':
111113288Smdodd			conf = optarg;
112113288Smdodd			break;
113113288Smdodd		case 'd':
114113288Smdodd			demon ^= 1;
115113288Smdodd			break;
116113309Smdodd		case 'e':
117113309Smdodd			dieearly = 1;
118113309Smdodd			break;
119113288Smdodd		case 'i':
120113288Smdodd			ignore++;
121113288Smdodd			break;
122113288Smdodd		case 'f':
123113288Smdodd			dev = optarg;
124113288Smdodd			break;
125113288Smdodd		case 'p':
126113288Smdodd			pidfile = optarg;
127113288Smdodd			break;
128113288Smdodd		case 'v':
129113288Smdodd			demon = 0;
130113288Smdodd			verbose++;
131113288Smdodd			break;
132113288Smdodd		case '?':
133113288Smdodd		default:
134113288Smdodd			usage();
135113288Smdodd		}
136113288Smdodd	}
137113288Smdodd	argc -= optind;
138113288Smdodd	argv += optind;
139113288Smdodd
140113288Smdodd	if (conf == NULL || dev == NULL)
141113288Smdodd		usage();
142113288Smdodd
143113288Smdodd	hid_init(NULL);
144113288Smdodd
145113288Smdodd	if (dev[0] != '/') {
146113288Smdodd		snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s",
147113288Smdodd			 isdigit(dev[0]) ? "uhid" : "", dev);
148113288Smdodd		dev = devnamebuf;
149113288Smdodd	}
150113288Smdodd
151113288Smdodd	fd = open(dev, O_RDWR);
152113288Smdodd	if (fd < 0)
153113288Smdodd		err(1, "%s", dev);
154113288Smdodd	if (ioctl(fd, USB_GET_REPORT_ID, &reportid) < 0)
155113288Smdodd		reportid = -1;
156113288Smdodd	repd = hid_get_report_desc(fd);
157113288Smdodd	if (repd == NULL)
158113288Smdodd		err(1, "hid_get_report_desc() failed");
159113288Smdodd
160113288Smdodd	commands = parse_conf(conf, repd, reportid, ignore);
161113288Smdodd
162126774Sdwmalone	sz = (size_t)hid_report_size(repd, hid_input, reportid);
163113288Smdodd
164113288Smdodd	if (verbose)
165126774Sdwmalone		printf("report size %zu\n", sz);
166113288Smdodd	if (sz > sizeof buf)
167113288Smdodd		errx(1, "report too large");
168113288Smdodd
169113288Smdodd	(void)signal(SIGHUP, sighup);
170113288Smdodd
171113288Smdodd	if (demon) {
172113288Smdodd		fp = open(pidfile, O_WRONLY|O_CREAT, S_IRUSR|S_IRGRP|S_IROTH);
173113288Smdodd		if (fp >= 0) {
174126774Sdwmalone			sz1 = snprintf(buf, sizeof buf, "%d\n", getpid());
175126774Sdwmalone			if (sz1 > sizeof buf)
176126774Sdwmalone				sz1 = sizeof buf;
177126774Sdwmalone			write(fp, buf, sz1);
178113288Smdodd			close(fp);
179113288Smdodd		} else
180113288Smdodd			err(1, "%s", pidfile);
181113309Smdodd		if (daemon(0, 0) < 0)
182113309Smdodd			err(1, "daemon()");
183113288Smdodd		isdemon = 1;
184113288Smdodd	}
185113288Smdodd
186113288Smdodd	for(;;) {
187113288Smdodd		n = read(fd, buf, sz);
188113288Smdodd		if (verbose > 2) {
189113288Smdodd			printf("read %d bytes:", n);
190113288Smdodd			for (i = 0; i < n; i++)
191113288Smdodd				printf(" %02x", buf[i]);
192113288Smdodd			printf("\n");
193113288Smdodd		}
194113288Smdodd		if (n < 0) {
195113288Smdodd			if (verbose)
196113288Smdodd				err(1, "read");
197113288Smdodd			else
198113288Smdodd				exit(1);
199113288Smdodd		}
200113288Smdodd#if 0
201113288Smdodd		if (n != sz) {
202113288Smdodd			err(2, "read size");
203113288Smdodd		}
204113288Smdodd#endif
205113288Smdodd		for (cmd = commands; cmd; cmd = cmd->next) {
206113288Smdodd			val = hid_get_data(buf, &cmd->item);
207113309Smdodd			if (cmd->value != val && cmd->anyvalue == 0)
208113309Smdodd				goto next;
209113309Smdodd			if ((cmd->debounce == 0) ||
210113328Smdodd			    ((cmd->debounce == 1) && ((cmd->lastseen == -1) ||
211113309Smdodd					       (cmd->lastseen != val)))) {
212113288Smdodd				docmd(cmd, val, dev, argc, argv);
213113309Smdodd				goto next;
214113309Smdodd			}
215113309Smdodd			if ((cmd->debounce > 1) &&
216113309Smdodd			    ((cmd->lastused == -1) ||
217113309Smdodd			     (abs(cmd->lastused - val) >= cmd->debounce))) {
218113309Smdodd				docmd(cmd, val, dev, argc, argv);
219113309Smdodd				cmd->lastused = val;
220113309Smdodd				goto next;
221113309Smdodd			}
222113309Smdoddnext:
223113309Smdodd			cmd->lastseen = val;
224113288Smdodd		}
225113309Smdodd
226113309Smdodd		if (dieearly)
227113309Smdodd			exit(0);
228113309Smdodd
229113288Smdodd		if (reparse) {
230113288Smdodd			struct command *cmds =
231113288Smdodd			    parse_conf(conf, repd, reportid, ignore);
232113288Smdodd			if (cmds) {
233113288Smdodd				freecommands(commands);
234113288Smdodd				commands = cmds;
235113288Smdodd			}
236113288Smdodd			reparse = 0;
237113288Smdodd		}
238113288Smdodd	}
239113288Smdodd
240113288Smdodd	exit(0);
241113288Smdodd}
242113288Smdodd
243113288Smdoddvoid
244113288Smdoddusage(void)
245113288Smdodd{
246113288Smdodd
247113309Smdodd	fprintf(stderr, "Usage: %s [-deiv] -c config_file -f hid_dev "
248113309Smdodd		"[-p pidfile]\n", getprogname());
249113288Smdodd	exit(1);
250113288Smdodd}
251113288Smdodd
252113288Smdoddstatic int
253113288Smdoddpeek(FILE *f)
254113288Smdodd{
255113288Smdodd	int c;
256113288Smdodd
257113288Smdodd	c = getc(f);
258113288Smdodd	if (c != EOF)
259113288Smdodd		ungetc(c, f);
260113288Smdodd	return c;
261113288Smdodd}
262113288Smdodd
263113288Smdoddstruct command *
264113288Smdoddparse_conf(const char *conf, report_desc_t repd, int reportid, int ignore)
265113288Smdodd{
266113288Smdodd	FILE *f;
267113288Smdodd	char *p;
268113288Smdodd	int line;
269113309Smdodd	char buf[SIZE], name[SIZE], value[SIZE], debounce[SIZE], action[SIZE];
270126774Sdwmalone	char usbuf[SIZE], coll[SIZE];
271113288Smdodd	struct command *cmd, *cmds;
272113288Smdodd	struct hid_data *d;
273113288Smdodd	struct hid_item h;
274113288Smdodd	int u, lo, hi, range;
275113288Smdodd
276113288Smdodd
277113288Smdodd	f = fopen(conf, "r");
278113288Smdodd	if (f == NULL)
279113288Smdodd		err(1, "%s", conf);
280113288Smdodd
281113288Smdodd	cmds = NULL;
282113288Smdodd	for (line = 1; ; line++) {
283113288Smdodd		if (fgets(buf, sizeof buf, f) == NULL)
284113288Smdodd			break;
285113288Smdodd		if (buf[0] == '#' || buf[0] == '\n')
286113288Smdodd			continue;
287113288Smdodd		p = strchr(buf, '\n');
288113288Smdodd		while (p && isspace(peek(f))) {
289113288Smdodd			if (fgets(p, sizeof buf - strlen(buf), f) == NULL)
290113288Smdodd				break;
291113288Smdodd			p = strchr(buf, '\n');
292113288Smdodd		}
293113288Smdodd		if (p)
294113288Smdodd			*p = 0;
295113309Smdodd		if (sscanf(buf, "%s %s %s %[^\n]",
296113309Smdodd			   name, value, debounce, action) != 4) {
297113288Smdodd			if (isdemon) {
298113288Smdodd				syslog(LOG_WARNING, "config file `%s', line %d"
299113288Smdodd				       ", syntax error: %s", conf, line, buf);
300113288Smdodd				freecommands(cmds);
301113288Smdodd				return (NULL);
302113288Smdodd			} else {
303113288Smdodd				errx(1, "config file `%s', line %d,"
304113288Smdodd				     ", syntax error: %s", conf, line, buf);
305113288Smdodd			}
306113288Smdodd		}
307113288Smdodd
308113288Smdodd		cmd = malloc(sizeof *cmd);
309113288Smdodd		if (cmd == NULL)
310113288Smdodd			err(1, "malloc failed");
311113288Smdodd		cmd->next = cmds;
312113288Smdodd		cmds = cmd;
313113288Smdodd		cmd->line = line;
314113288Smdodd
315113288Smdodd		if (strcmp(value, "*") == 0) {
316113288Smdodd			cmd->anyvalue = 1;
317113288Smdodd		} else {
318113288Smdodd			cmd->anyvalue = 0;
319113288Smdodd			if (sscanf(value, "%d", &cmd->value) != 1) {
320113288Smdodd				if (isdemon) {
321113288Smdodd					syslog(LOG_WARNING,
322113288Smdodd					       "config file `%s', line %d, "
323113309Smdodd					       "bad value: %s (should be * or a number)\n",
324113288Smdodd					       conf, line, value);
325113288Smdodd					freecommands(cmds);
326113288Smdodd					return (NULL);
327113288Smdodd				} else {
328113288Smdodd					errx(1, "config file `%s', line %d, "
329113309Smdodd					     "bad value: %s (should be * or a number)\n",
330113288Smdodd					     conf, line, value);
331113288Smdodd				}
332113288Smdodd			}
333113288Smdodd		}
334113288Smdodd
335113309Smdodd		if (sscanf(debounce, "%d", &cmd->debounce) != 1) {
336113309Smdodd			if (isdemon) {
337113309Smdodd				syslog(LOG_WARNING,
338113309Smdodd				       "config file `%s', line %d, "
339113309Smdodd				       "bad value: %s (should be a number >= 0)\n",
340113309Smdodd				       conf, line, debounce);
341113309Smdodd				freecommands(cmds);
342113309Smdodd				return (NULL);
343113309Smdodd			} else {
344113309Smdodd				errx(1, "config file `%s', line %d, "
345113309Smdodd				     "bad value: %s (should be a number >= 0)\n",
346113309Smdodd				     conf, line, debounce);
347113309Smdodd			}
348113309Smdodd		}
349113309Smdodd
350113288Smdodd		coll[0] = 0;
351113288Smdodd		for (d = hid_start_parse(repd, 1 << hid_input, reportid);
352113288Smdodd		     hid_get_item(d, &h); ) {
353113288Smdodd			if (verbose > 2)
354113288Smdodd				printf("kind=%d usage=%x\n", h.kind, h.usage);
355113288Smdodd			if (h.flags & HIO_CONST)
356113288Smdodd				continue;
357113288Smdodd			switch (h.kind) {
358113288Smdodd			case hid_input:
359113288Smdodd				if (h.usage_minimum != 0 ||
360113288Smdodd				    h.usage_maximum != 0) {
361113288Smdodd					lo = h.usage_minimum;
362113288Smdodd					hi = h.usage_maximum;
363113288Smdodd					range = 1;
364113288Smdodd				} else {
365113288Smdodd					lo = h.usage;
366113288Smdodd					hi = h.usage;
367113288Smdodd					range = 0;
368113288Smdodd				}
369113288Smdodd				for (u = lo; u <= hi; u++) {
370126774Sdwmalone					snprintf(usbuf, sizeof usbuf,  "%s:%s",
371113288Smdodd						 hid_usage_page(HID_PAGE(u)),
372113288Smdodd						 hid_usage_in_page(u));
373113288Smdodd					if (verbose > 2)
374126774Sdwmalone						printf("usage %s\n", usbuf);
375126774Sdwmalone					if (!strcasecmp(usbuf, name))
376113288Smdodd						goto foundhid;
377113288Smdodd					if (coll[0]) {
378126774Sdwmalone						snprintf(usbuf, sizeof usbuf,
379113288Smdodd						  "%s.%s:%s", coll+1,
380113288Smdodd						  hid_usage_page(HID_PAGE(u)),
381113288Smdodd						  hid_usage_in_page(u));
382113288Smdodd						if (verbose > 2)
383113288Smdodd							printf("usage %s\n",
384126774Sdwmalone							       usbuf);
385126774Sdwmalone						if (!strcasecmp(usbuf, name))
386113288Smdodd							goto foundhid;
387113288Smdodd					}
388113288Smdodd				}
389113288Smdodd				break;
390113288Smdodd			case hid_collection:
391113288Smdodd				snprintf(coll + strlen(coll),
392113288Smdodd				    sizeof coll - strlen(coll),  ".%s:%s",
393113288Smdodd				    hid_usage_page(HID_PAGE(h.usage)),
394113288Smdodd				    hid_usage_in_page(h.usage));
395113288Smdodd				break;
396113288Smdodd			case hid_endcollection:
397113288Smdodd				if (coll[0])
398113288Smdodd					*strrchr(coll, '.') = 0;
399113288Smdodd				break;
400113288Smdodd			default:
401113288Smdodd				break;
402113288Smdodd			}
403113288Smdodd		}
404113288Smdodd		if (ignore) {
405113288Smdodd			if (verbose)
406113288Smdodd				warnx("ignore item '%s'", name);
407113288Smdodd			continue;
408113288Smdodd		}
409113288Smdodd		if (isdemon) {
410113288Smdodd			syslog(LOG_WARNING, "config file `%s', line %d, HID "
411113288Smdodd			       "item not found: `%s'\n", conf, line, name);
412113288Smdodd			freecommands(cmds);
413113288Smdodd			return (NULL);
414113288Smdodd		} else {
415113288Smdodd			errx(1, "config file `%s', line %d, HID item "
416113288Smdodd			     "not found: `%s'\n", conf, line, name);
417113288Smdodd		}
418113288Smdodd
419113288Smdodd	foundhid:
420113288Smdodd		hid_end_parse(d);
421113328Smdodd		cmd->lastseen = -1;
422113328Smdodd		cmd->lastused = -1;
423113288Smdodd		cmd->item = h;
424113288Smdodd		cmd->name = strdup(name);
425113288Smdodd		cmd->action = strdup(action);
426113288Smdodd		if (range) {
427113288Smdodd			if (cmd->value == 1)
428113288Smdodd				cmd->value = u - lo;
429113288Smdodd			else
430113288Smdodd				cmd->value = -1;
431113288Smdodd		}
432113288Smdodd
433113288Smdodd		if (verbose)
434113288Smdodd			printf("PARSE:%d %s, %d, '%s'\n", cmd->line, name,
435113288Smdodd			       cmd->value, cmd->action);
436113288Smdodd	}
437113288Smdodd	fclose(f);
438113288Smdodd	return (cmds);
439113288Smdodd}
440113288Smdodd
441113288Smdoddvoid
442113288Smdodddocmd(struct command *cmd, int value, const char *hid, int argc, char **argv)
443113288Smdodd{
444113288Smdodd	char cmdbuf[SIZE], *p, *q;
445113288Smdodd	size_t len;
446113288Smdodd	int n, r;
447113288Smdodd
448113288Smdodd	for (p = cmd->action, q = cmdbuf; *p && q < &cmdbuf[SIZE-1]; ) {
449113288Smdodd		if (*p == '$') {
450113288Smdodd			p++;
451113288Smdodd			len = &cmdbuf[SIZE-1] - q;
452113288Smdodd			if (isdigit(*p)) {
453113288Smdodd				n = strtol(p, &p, 10) - 1;
454113288Smdodd				if (n >= 0 && n < argc) {
455113288Smdodd					strncpy(q, argv[n], len);
456113288Smdodd					q += strlen(q);
457113288Smdodd				}
458113288Smdodd			} else if (*p == 'V') {
459113288Smdodd				p++;
460113288Smdodd				snprintf(q, len, "%d", value);
461113288Smdodd				q += strlen(q);
462113288Smdodd			} else if (*p == 'N') {
463113288Smdodd				p++;
464113288Smdodd				strncpy(q, cmd->name, len);
465113288Smdodd				q += strlen(q);
466113288Smdodd			} else if (*p == 'H') {
467113288Smdodd				p++;
468113288Smdodd				strncpy(q, hid, len);
469113288Smdodd				q += strlen(q);
470113288Smdodd			} else if (*p) {
471113288Smdodd				*q++ = *p++;
472113288Smdodd			}
473113288Smdodd		} else {
474113288Smdodd			*q++ = *p++;
475113288Smdodd		}
476113288Smdodd	}
477113288Smdodd	*q = 0;
478113288Smdodd
479113288Smdodd	if (verbose)
480113288Smdodd		printf("system '%s'\n", cmdbuf);
481113288Smdodd	r = system(cmdbuf);
482113288Smdodd	if (verbose > 1 && r)
483113288Smdodd		printf("return code = 0x%x\n", r);
484113288Smdodd}
485113288Smdodd
486113288Smdoddvoid
487113288Smdoddfreecommands(struct command *cmd)
488113288Smdodd{
489113288Smdodd	struct command *next;
490113288Smdodd
491113288Smdodd	while (cmd) {
492113288Smdodd		next = cmd->next;
493113288Smdodd		free(cmd);
494113288Smdodd		cmd = next;
495113288Smdodd	}
496113288Smdodd}
497