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