usbhidaction.c revision 113328
1195534Sscottl/*      $NetBSD: usbhidaction.c,v 1.8 2002/06/11 06:06:21 itojun Exp $ */
2195534Sscottl/*	$FreeBSD: head/usr.bin/usbhidaction/usbhidaction.c 113328 2003-04-10 08:06:56Z mdodd $ */
3195534Sscottl
4195534Sscottl/*
5195534Sscottl * Copyright (c) 2000, 2002 The NetBSD Foundation, Inc.
6195534Sscottl * All rights reserved.
7195534Sscottl *
8195534Sscottl * This code is derived from software contributed to The NetBSD Foundation
9195534Sscottl * by Lennart Augustsson <lennart@augustsson.net>.
10195534Sscottl *
11195534Sscottl * Redistribution and use in source and binary forms, with or without
12195534Sscottl * modification, are permitted provided that the following conditions
13195534Sscottl * are met:
14195534Sscottl * 1. Redistributions of source code must retain the above copyright
15195534Sscottl *    notice, this list of conditions and the following disclaimer.
16195534Sscottl * 2. Redistributions in binary form must reproduce the above copyright
17195534Sscottl *    notice, this list of conditions and the following disclaimer in the
18195534Sscottl *    documentation and/or other materials provided with the distribution.
19195534Sscottl * 3. All advertising materials mentioning features or use of this software
20195534Sscottl *    must display the following acknowledgement:
21195534Sscottl *        This product includes software developed by the NetBSD
22195534Sscottl *        Foundation, Inc. and its contributors.
23195534Sscottl * 4. Neither the name of The NetBSD Foundation nor the names of its
24195534Sscottl *    contributors may be used to endorse or promote products derived
25195534Sscottl *    from this software without specific prior written permission.
26195534Sscottl *
27195534Sscottl * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28195534Sscottl * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29195534Sscottl * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30195534Sscottl * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31195534Sscottl * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32195534Sscottl * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33195534Sscottl * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34195534Sscottl * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35195534Sscottl * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36195534Sscottl * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37195534Sscottl * POSSIBILITY OF SUCH DAMAGE.
38195534Sscottl */
39195534Sscottl
40195534Sscottl#include <stdio.h>
41195534Sscottl#include <stdlib.h>
42195534Sscottl#include <string.h>
43195534Sscottl#include <ctype.h>
44195534Sscottl#include <err.h>
45195534Sscottl#include <fcntl.h>
46195534Sscottl#include <limits.h>
47195534Sscottl#include <unistd.h>
48195534Sscottl#include <sys/types.h>
49195534Sscottl#include <sys/ioctl.h>
50195534Sscottl#include <dev/usb/usb.h>
51195534Sscottl#include <dev/usb/usbhid.h>
52195534Sscottl#include <usbhid.h>
53195534Sscottl#include <syslog.h>
54195534Sscottl#include <signal.h>
55195534Sscottl#include <errno.h>
56195534Sscottl#include <sys/stat.h>
57195534Sscottl
58195534Sscottlstatic int	verbose = 0;
59195534Sscottlstatic int	isdemon = 0;
60195534Sscottlstatic int	reparse = 1;
61195534Sscottlstatic char *	pidfile = "/var/run/usbaction.pid";
62195534Sscottl
63195534Sscottlstruct command {
64195534Sscottl	struct command *next;
65195534Sscottl	int line;
66195534Sscottl
67195534Sscottl	struct hid_item item;
68195534Sscottl	int value;
69195534Sscottl	int lastseen;
70195534Sscottl	int lastused;
71198849Smav	int debounce;
72198849Smav	char anyvalue;
73198849Smav	char *name;
74198849Smav	char *action;
75220616Smav};
76220616Smavstruct command *commands;
77198849Smav
78198849Smav#define SIZE 4000
79198849Smav
80201139Smavvoid usage(void);
81201139Smavstruct command *parse_conf(const char *, report_desc_t, int, int);
82201139Smavvoid docmd(struct command *, int, const char *, int, char **);
83201139Smavvoid freecommands(struct command *);
84201139Smav
85198849Smavstatic void
86198849Smavsighup(int sig)
87198849Smav{
88198849Smav	reparse = 1;
89198849Smav}
90198849Smav
91198849Smavint
92198849Smavmain(int argc, char **argv)
93198849Smav{
94198849Smav	const char *conf = NULL;
95198849Smav	const char *dev = NULL;
96198849Smav	int fd, fp, ch, sz, n, val, i;
97198849Smav	int demon, ignore, dieearly;
98198849Smav	report_desc_t repd;
99198849Smav	char buf[100];
100198849Smav	char devnamebuf[PATH_MAX];
101198849Smav	struct command *cmd;
102198849Smav	int reportid;
103200008Smav
104200008Smav	demon = 1;
105198849Smav	ignore = 0;
106198849Smav	dieearly = 0;
107198849Smav	while ((ch = getopt(argc, argv, "c:def:ip:v")) != -1) {
108198849Smav		switch(ch) {
109198849Smav		case 'c':
110198849Smav			conf = optarg;
111235897Smav			break;
112235897Smav		case 'd':
113235897Smav			demon ^= 1;
114235897Smav			break;
115235897Smav		case 'e':
116235897Smav			dieearly = 1;
117235897Smav			break;
118235897Smav		case 'i':
119235897Smav			ignore++;
120235897Smav			break;
121198849Smav		case 'f':
122198849Smav			dev = optarg;
123198849Smav			break;
124198849Smav		case 'p':
125198849Smav			pidfile = optarg;
126198849Smav			break;
127198849Smav		case 'v':
128198849Smav			demon = 0;
129198849Smav			verbose++;
130198849Smav			break;
131198849Smav		case '?':
132198849Smav		default:
133198849Smav			usage();
134198849Smav		}
135198849Smav	}
136198849Smav	argc -= optind;
137198849Smav	argv += optind;
138198849Smav
139200008Smav	if (conf == NULL || dev == NULL)
140198849Smav		usage();
141198849Smav
142198849Smav	hid_init(NULL);
143198849Smav
144198849Smav	if (dev[0] != '/') {
145198849Smav		snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s",
146198849Smav			 isdigit(dev[0]) ? "uhid" : "", dev);
147198849Smav		dev = devnamebuf;
148198849Smav	}
149198849Smav
150198849Smav	fd = open(dev, O_RDWR);
151198849Smav	if (fd < 0)
152198849Smav		err(1, "%s", dev);
153198849Smav	if (ioctl(fd, USB_GET_REPORT_ID, &reportid) < 0)
154198849Smav		reportid = -1;
155198849Smav	repd = hid_get_report_desc(fd);
156198849Smav	if (repd == NULL)
157198849Smav		err(1, "hid_get_report_desc() failed");
158198849Smav
159198849Smav	commands = parse_conf(conf, repd, reportid, ignore);
160198849Smav
161203421Smav	sz = hid_report_size(repd, hid_input, reportid);
162203421Smav
163203421Smav	if (verbose)
164220616Smav		printf("report size %d\n", sz);
165220616Smav	if (sz > sizeof buf)
166198849Smav		errx(1, "report too large");
167198849Smav
168198849Smav	(void)signal(SIGHUP, sighup);
169198849Smav
170198849Smav	if (demon) {
171198849Smav		fp = open(pidfile, O_WRONLY|O_CREAT, S_IRUSR|S_IRGRP|S_IROTH);
172198849Smav		if (fp >= 0) {
173198849Smav			sz=snprintf(buf,100, "%d\n", getpid());
174238363Sbrueffer			write(fp, buf, sz);
175238363Sbrueffer			close(fp);
176198849Smav		} else
177198849Smav			err(1, "%s", pidfile);
178198849Smav		if (daemon(0, 0) < 0)
179198849Smav			err(1, "daemon()");
180198849Smav		isdemon = 1;
181198849Smav	}
182198849Smav
183198849Smav	for(;;) {
184198849Smav		n = read(fd, buf, sz);
185198849Smav		if (verbose > 2) {
186198849Smav			printf("read %d bytes:", n);
187198849Smav			for (i = 0; i < n; i++)
188198849Smav				printf(" %02x", buf[i]);
189198849Smav			printf("\n");
190198849Smav		}
191198849Smav		if (n < 0) {
192198849Smav			if (verbose)
193198849Smav				err(1, "read");
194198849Smav			else
195198849Smav				exit(1);
196198849Smav		}
197198849Smav#if 0
198198849Smav		if (n != sz) {
199198849Smav			err(2, "read size");
200198849Smav		}
201198849Smav#endif
202198849Smav		for (cmd = commands; cmd; cmd = cmd->next) {
203198849Smav			val = hid_get_data(buf, &cmd->item);
204198849Smav			if (cmd->value != val && cmd->anyvalue == 0)
205198849Smav				goto next;
206198849Smav			if ((cmd->debounce == 0) ||
207198849Smav			    ((cmd->debounce == 1) && ((cmd->lastseen == -1) ||
208198849Smav					       (cmd->lastseen != val)))) {
209198849Smav				docmd(cmd, val, dev, argc, argv);
210198849Smav				goto next;
211198849Smav			}
212198849Smav			if ((cmd->debounce > 1) &&
213198849Smav			    ((cmd->lastused == -1) ||
214198849Smav			     (abs(cmd->lastused - val) >= cmd->debounce))) {
215198849Smav				docmd(cmd, val, dev, argc, argv);
216198849Smav				cmd->lastused = val;
217198849Smav				goto next;
218203108Smav			}
219198849Smavnext:
220198849Smav			cmd->lastseen = val;
221198849Smav		}
222198849Smav
223198849Smav		if (dieearly)
224198849Smav			exit(0);
225198849Smav
226198849Smav		if (reparse) {
227198849Smav			struct command *cmds =
228198849Smav			    parse_conf(conf, repd, reportid, ignore);
229198849Smav			if (cmds) {
230198849Smav				freecommands(commands);
231198849Smav				commands = cmds;
232203108Smav			}
233198849Smav			reparse = 0;
234198849Smav		}
235198849Smav	}
236198849Smav
237198849Smav	exit(0);
238198849Smav}
239198849Smav
240198849Smavvoid
241198849Smavusage(void)
242198849Smav{
243203108Smav
244198849Smav	fprintf(stderr, "Usage: %s [-deiv] -c config_file -f hid_dev "
245198849Smav		"[-p pidfile]\n", getprogname());
246198849Smav	exit(1);
247198849Smav}
248198849Smav
249198849Smavstatic int
250198849Smavpeek(FILE *f)
251198849Smav{
252198849Smav	int c;
253198849Smav
254198849Smav	c = getc(f);
255198849Smav	if (c != EOF)
256198849Smav		ungetc(c, f);
257198849Smav	return c;
258198849Smav}
259198849Smav
260198849Smavstruct command *
261198849Smavparse_conf(const char *conf, report_desc_t repd, int reportid, int ignore)
262198849Smav{
263198849Smav	FILE *f;
264198849Smav	char *p;
265198849Smav	int line;
266198849Smav	char buf[SIZE], name[SIZE], value[SIZE], debounce[SIZE], action[SIZE];
267198849Smav	char usage[SIZE], coll[SIZE];
268198849Smav	struct command *cmd, *cmds;
269198849Smav	struct hid_data *d;
270198849Smav	struct hid_item h;
271198849Smav	int u, lo, hi, range;
272195534Sscottl
273195534Sscottl
274195534Sscottl	f = fopen(conf, "r");
275195534Sscottl	if (f == NULL)
276195534Sscottl		err(1, "%s", conf);
277195534Sscottl
278195534Sscottl	cmds = NULL;
279195534Sscottl	for (line = 1; ; line++) {
280195534Sscottl		if (fgets(buf, sizeof buf, f) == NULL)
281200218Smav			break;
282200218Smav		if (buf[0] == '#' || buf[0] == '\n')
283223019Smav			continue;
284200218Smav		p = strchr(buf, '\n');
285200218Smav		while (p && isspace(peek(f))) {
286195534Sscottl			if (fgets(p, sizeof buf - strlen(buf), f) == NULL)
287197541Smav				break;
288197541Smav			p = strchr(buf, '\n');
289197541Smav		}
290195534Sscottl		if (p)
291195534Sscottl			*p = 0;
292195534Sscottl		if (sscanf(buf, "%s %s %s %[^\n]",
293195534Sscottl			   name, value, debounce, action) != 4) {
294195534Sscottl			if (isdemon) {
295195534Sscottl				syslog(LOG_WARNING, "config file `%s', line %d"
296195534Sscottl				       ", syntax error: %s", conf, line, buf);
297195534Sscottl				freecommands(cmds);
298195534Sscottl				return (NULL);
299235897Smav			} else {
300235897Smav				errx(1, "config file `%s', line %d,"
301235897Smav				     ", syntax error: %s", conf, line, buf);
302235897Smav			}
303235897Smav		}
304235897Smav
305235897Smav		cmd = malloc(sizeof *cmd);
306235897Smav		if (cmd == NULL)
307235897Smav			err(1, "malloc failed");
308235897Smav		cmd->next = cmds;
309235897Smav		cmds = cmd;
310235897Smav		cmd->line = line;
311235897Smav
312235897Smav		if (strcmp(value, "*") == 0) {
313235897Smav			cmd->anyvalue = 1;
314198897Smav		} else {
315198897Smav			cmd->anyvalue = 0;
316198897Smav			if (sscanf(value, "%d", &cmd->value) != 1) {
317198897Smav				if (isdemon) {
318198897Smav					syslog(LOG_WARNING,
319198897Smav					       "config file `%s', line %d, "
320198897Smav					       "bad value: %s (should be * or a number)\n",
321198897Smav					       conf, line, value);
322198897Smav					freecommands(cmds);
323198897Smav					return (NULL);
324198897Smav				} else {
325198897Smav					errx(1, "config file `%s', line %d, "
326198897Smav					     "bad value: %s (should be * or a number)\n",
327198897Smav					     conf, line, value);
328198897Smav				}
329198897Smav			}
330198897Smav		}
331198897Smav
332198897Smav		if (sscanf(debounce, "%d", &cmd->debounce) != 1) {
333198897Smav			if (isdemon) {
334198897Smav				syslog(LOG_WARNING,
335198897Smav				       "config file `%s', line %d, "
336198897Smav				       "bad value: %s (should be a number >= 0)\n",
337198897Smav				       conf, line, debounce);
338198897Smav				freecommands(cmds);
339198897Smav				return (NULL);
340198897Smav			} else {
341198897Smav				errx(1, "config file `%s', line %d, "
342198897Smav				     "bad value: %s (should be a number >= 0)\n",
343198897Smav				     conf, line, debounce);
344198897Smav			}
345198897Smav		}
346195534Sscottl
347196659Smav		coll[0] = 0;
348195534Sscottl		for (d = hid_start_parse(repd, 1 << hid_input, reportid);
349195534Sscottl		     hid_get_item(d, &h); ) {
350195534Sscottl			if (verbose > 2)
351195534Sscottl				printf("kind=%d usage=%x\n", h.kind, h.usage);
352200008Smav			if (h.flags & HIO_CONST)
353200008Smav				continue;
354200008Smav			switch (h.kind) {
355200008Smav			case hid_input:
356200008Smav				if (h.usage_minimum != 0 ||
357195534Sscottl				    h.usage_maximum != 0) {
358195534Sscottl					lo = h.usage_minimum;
359195534Sscottl					hi = h.usage_maximum;
360195534Sscottl					range = 1;
361195534Sscottl				} else {
362238393Sbrueffer					lo = h.usage;
363195534Sscottl					hi = h.usage;
364195534Sscottl					range = 0;
365195534Sscottl				}
366195534Sscottl				for (u = lo; u <= hi; u++) {
367195534Sscottl					snprintf(usage, sizeof usage,  "%s:%s",
368195534Sscottl						 hid_usage_page(HID_PAGE(u)),
369195534Sscottl						 hid_usage_in_page(u));
370248695Smav					if (verbose > 2)
371195534Sscottl						printf("usage %s\n", usage);
372200008Smav					if (!strcasecmp(usage, name))
373200008Smav						goto foundhid;
374200008Smav					if (coll[0]) {
375200008Smav						snprintf(usage, sizeof usage,
376200008Smav						  "%s.%s:%s", coll+1,
377200008Smav						  hid_usage_page(HID_PAGE(u)),
378200008Smav						  hid_usage_in_page(u));
379201139Smav						if (verbose > 2)
380201139Smav							printf("usage %s\n",
381200008Smav							       usage);
382195534Sscottl						if (!strcasecmp(usage, name))
383195534Sscottl							goto foundhid;
384195534Sscottl					}
385195534Sscottl				}
386195534Sscottl				break;
387238393Sbrueffer			case hid_collection:
388195534Sscottl				snprintf(coll + strlen(coll),
389195534Sscottl				    sizeof coll - strlen(coll),  ".%s:%s",
390195534Sscottl				    hid_usage_page(HID_PAGE(h.usage)),
391195534Sscottl				    hid_usage_in_page(h.usage));
392195534Sscottl				break;
393195534Sscottl			case hid_endcollection:
394248695Smav				if (coll[0])
395195534Sscottl					*strrchr(coll, '.') = 0;
396195534Sscottl				break;
397195534Sscottl			default:
398195534Sscottl				break;
399195534Sscottl			}
400195534Sscottl		}
401248695Smav		if (ignore) {
402195534Sscottl			if (verbose)
403195534Sscottl				warnx("ignore item '%s'", name);
404195534Sscottl			continue;
405195534Sscottl		}
406195534Sscottl		if (isdemon) {
407195534Sscottl			syslog(LOG_WARNING, "config file `%s', line %d, HID "
408238393Sbrueffer			       "item not found: `%s'\n", conf, line, name);
409195534Sscottl			freecommands(cmds);
410195534Sscottl			return (NULL);
411195534Sscottl		} else {
412195534Sscottl			errx(1, "config file `%s', line %d, HID item "
413248695Smav			     "not found: `%s'\n", conf, line, name);
414248695Smav		}
415248695Smav
416195534Sscottl	foundhid:
417195534Sscottl		hid_end_parse(d);
418195534Sscottl		cmd->lastseen = -1;
419195534Sscottl		cmd->lastused = -1;
420195534Sscottl		cmd->item = h;
421195534Sscottl		cmd->name = strdup(name);
422195534Sscottl		cmd->action = strdup(action);
423195534Sscottl		if (range) {
424195534Sscottl			if (cmd->value == 1)
425195534Sscottl				cmd->value = u - lo;
426195534Sscottl			else
427195534Sscottl				cmd->value = -1;
428195534Sscottl		}
429195534Sscottl
430198904Smav		if (verbose)
431195534Sscottl			printf("PARSE:%d %s, %d, '%s'\n", cmd->line, name,
432195534Sscottl			       cmd->value, cmd->action);
433195534Sscottl	}
434195534Sscottl	fclose(f);
435195534Sscottl	return (cmds);
436195534Sscottl}
437198904Smav
438195534Sscottlvoid
439195534Sscottldocmd(struct command *cmd, int value, const char *hid, int argc, char **argv)
440198904Smav{
441195534Sscottl	char cmdbuf[SIZE], *p, *q;
442195534Sscottl	size_t len;
443198904Smav	int n, r;
444195534Sscottl
445195534Sscottl	for (p = cmd->action, q = cmdbuf; *p && q < &cmdbuf[SIZE-1]; ) {
446195534Sscottl		if (*p == '$') {
447195534Sscottl			p++;
448195534Sscottl			len = &cmdbuf[SIZE-1] - q;
449195534Sscottl			if (isdigit(*p)) {
450195534Sscottl				n = strtol(p, &p, 10) - 1;
451195534Sscottl				if (n >= 0 && n < argc) {
452195534Sscottl					strncpy(q, argv[n], len);
453195534Sscottl					q += strlen(q);
454195534Sscottl				}
455195534Sscottl			} else if (*p == 'V') {
456195534Sscottl				p++;
457195534Sscottl				snprintf(q, len, "%d", value);
458195534Sscottl				q += strlen(q);
459195534Sscottl			} else if (*p == 'N') {
460195534Sscottl				p++;
461195534Sscottl				strncpy(q, cmd->name, len);
462195534Sscottl				q += strlen(q);
463195534Sscottl			} else if (*p == 'H') {
464195534Sscottl				p++;
465195534Sscottl				strncpy(q, hid, len);
466195534Sscottl				q += strlen(q);
467195534Sscottl			} else if (*p) {
468195534Sscottl				*q++ = *p++;
469195534Sscottl			}
470195534Sscottl		} else {
471195534Sscottl			*q++ = *p++;
472195534Sscottl		}
473195534Sscottl	}
474195534Sscottl	*q = 0;
475195534Sscottl
476195534Sscottl	if (verbose)
477195534Sscottl		printf("system '%s'\n", cmdbuf);
478195534Sscottl	r = system(cmdbuf);
479195534Sscottl	if (verbose > 1 && r)
480195534Sscottl		printf("return code = 0x%x\n", r);
481195534Sscottl}
482195534Sscottl
483195534Sscottlvoid
484195534Sscottlfreecommands(struct command *cmd)
485195534Sscottl{
486195534Sscottl	struct command *next;
487195534Sscottl
488195534Sscottl	while (cmd) {
489195534Sscottl		next = cmd->next;
490195534Sscottl		free(cmd);
491195534Sscottl		cmd = next;
492195534Sscottl	}
493195534Sscottl}
494195534Sscottl