parser.y revision 164700
1%{
2/*
3 * parser.y
4 */
5
6/*-
7 * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 *
31 * $Id: parser.y,v 1.7 2006/09/07 21:06:53 max Exp $
32 * $FreeBSD: head/usr.sbin/bluetooth/bthidd/parser.y 164700 2006-11-27 22:52:11Z emax $
33 */
34
35#include <sys/queue.h>
36#include <bluetooth.h>
37#include <dev/usb/usb.h>
38#include <dev/usb/usbhid.h>
39#include <errno.h>
40#include <limits.h>
41#include <stdio.h>
42#include <string.h>
43#include <unistd.h>
44#include <usbhid.h>
45
46#ifndef BTHIDCONTROL
47#include <stdarg.h>
48#include <syslog.h>
49#define	SYSLOG		syslog
50#define	LOGCRIT		LOG_CRIT
51#define	LOGERR		LOG_ERR
52#define	LOGWARNING	LOG_WARNING
53#define	EOL
54#else
55#define	SYSLOG		fprintf
56#define	LOGCRIT		stderr
57#define	LOGERR		stderr
58#define	LOGWARNING	stderr
59#define	EOL	"\n"
60#endif /* ndef BTHIDCONTROL */
61
62#include "bthid_config.h"
63
64	int	yyparse		(void);
65	int	yylex		(void);
66	void	yyerror		(char const *);
67static	int32_t	check_hid_device(hid_device_p hid_device);
68static	void	free_hid_device	(hid_device_p hid_device);
69
70extern	FILE			*yyin;
71extern	int			 yylineno;
72	char const		*config_file = BTHIDD_CONFFILE;
73	char const		*hids_file   = BTHIDD_HIDSFILE;
74
75static	char			 buffer[1024];
76static	int32_t			 hid_descriptor_size;
77static	hid_device_t		*hid_device = NULL;
78static	LIST_HEAD(, hid_device)	 hid_devices;
79
80%}
81
82%union {
83	bdaddr_t	bdaddr;
84	int32_t		num;
85}
86
87%token <bdaddr> T_BDADDRSTRING
88%token <num>	T_HEXBYTE
89%token T_DEVICE T_BDADDR T_CONTROL_PSM T_INTERRUPT_PSM T_RECONNECT_INITIATE
90%token T_BATTERY_POWER T_NORMALLY_CONNECTABLE T_HID_DESCRIPTOR
91%token T_TRUE T_FALSE T_ERROR
92
93%%
94
95config:		line
96		| config line
97		;
98
99line:		T_DEVICE
100			{
101			hid_device = (hid_device_t *) calloc(1, sizeof(*hid_device));
102			if (hid_device == NULL) {
103				SYSLOG(LOGCRIT, "Could not allocate new " \
104						"config entry" EOL);
105				YYABORT;
106			}
107
108			hid_device->new_device = 1;
109			}
110		'{' options '}'
111			{
112			if (check_hid_device(hid_device))
113				LIST_INSERT_HEAD(&hid_devices,hid_device,next);
114			else
115				free_hid_device(hid_device);
116
117			hid_device = NULL;
118			}
119		;
120
121options:	option ';'
122		| options option ';'
123		;
124
125option:		bdaddr
126		| control_psm
127		| interrupt_psm
128		| reconnect_initiate
129		| battery_power
130		| normally_connectable
131		| hid_descriptor
132		| parser_error
133		;
134
135bdaddr:		T_BDADDR T_BDADDRSTRING
136			{
137			memcpy(&hid_device->bdaddr, &$2, sizeof(hid_device->bdaddr));
138			}
139		;
140
141control_psm:	T_CONTROL_PSM T_HEXBYTE
142			{
143			hid_device->control_psm = $2;
144			}
145		;
146
147interrupt_psm:	T_INTERRUPT_PSM T_HEXBYTE
148			{
149			hid_device->interrupt_psm = $2;
150			}
151		;
152
153reconnect_initiate: T_RECONNECT_INITIATE T_TRUE
154			{
155			hid_device->reconnect_initiate = 1;
156			}
157		| T_RECONNECT_INITIATE T_FALSE
158			{
159			hid_device->reconnect_initiate = 0;
160			}
161		;
162
163battery_power:	T_BATTERY_POWER T_TRUE
164			{
165			hid_device->battery_power = 1;
166			}
167		| T_BATTERY_POWER T_FALSE
168			{
169			hid_device->battery_power = 0;
170			}
171		;
172
173normally_connectable: T_NORMALLY_CONNECTABLE T_TRUE
174			{
175			hid_device->normally_connectable = 1;
176			}
177		| T_NORMALLY_CONNECTABLE T_FALSE
178			{
179			hid_device->normally_connectable = 0;
180			}
181		;
182
183hid_descriptor:	T_HID_DESCRIPTOR
184			{
185			hid_descriptor_size = 0;
186			}
187		'{' hid_descriptor_bytes '}'
188			{
189			if (hid_device->desc != NULL)
190				hid_dispose_report_desc(hid_device->desc);
191
192			hid_device->desc = hid_use_report_desc((unsigned char *) buffer, hid_descriptor_size);
193			if (hid_device->desc == NULL) {
194				SYSLOG(LOGCRIT, "Could not use HID descriptor" EOL);
195				YYABORT;
196			}
197			}
198		;
199
200hid_descriptor_bytes: hid_descriptor_byte
201		| hid_descriptor_bytes hid_descriptor_byte
202		;
203
204hid_descriptor_byte: T_HEXBYTE
205			{
206			if (hid_descriptor_size >= (int32_t) sizeof(buffer)) {
207				SYSLOG(LOGCRIT, "HID descriptor is too big" EOL);
208				YYABORT;
209			}
210
211			buffer[hid_descriptor_size ++] = $1;
212			}
213		;
214
215parser_error:	T_ERROR
216			{
217				YYABORT;
218			}
219
220%%
221
222/* Display parser error message */
223void
224yyerror(char const *message)
225{
226	SYSLOG(LOGERR, "%s in line %d" EOL, message, yylineno);
227}
228
229/* Re-read config file */
230int32_t
231read_config_file(void)
232{
233	int32_t	e;
234
235	if (config_file == NULL) {
236		SYSLOG(LOGERR, "Unknown config file name!" EOL);
237		return (-1);
238	}
239
240	if ((yyin = fopen(config_file, "r")) == NULL) {
241		SYSLOG(LOGERR, "Could not open config file '%s'. %s (%d)" EOL,
242				config_file, strerror(errno), errno);
243		return (-1);
244	}
245
246	clean_config();
247	if (yyparse() < 0) {
248		SYSLOG(LOGERR, "Could not parse config file '%s'" EOL,
249				config_file);
250		e = -1;
251	} else
252		e = 0;
253
254	fclose(yyin);
255	yyin = NULL;
256
257	return (e);
258}
259
260/* Clean config */
261void
262clean_config(void)
263{
264	while (!LIST_EMPTY(&hid_devices)) {
265		hid_device_p	d = LIST_FIRST(&hid_devices);
266
267		LIST_REMOVE(d, next);
268		free_hid_device(d);
269	}
270}
271
272/* Lookup config entry */
273hid_device_p
274get_hid_device(bdaddr_p bdaddr)
275{
276	hid_device_p	d;
277
278	LIST_FOREACH(d, &hid_devices, next)
279		if (memcmp(&d->bdaddr, bdaddr, sizeof(bdaddr_t)) == 0)
280			break;
281
282	return (d);
283}
284
285/* Get next config entry */
286hid_device_p
287get_next_hid_device(hid_device_p d)
288{
289	return ((d == NULL)? LIST_FIRST(&hid_devices) : LIST_NEXT(d, next));
290}
291
292/* Print config entry */
293void
294print_hid_device(hid_device_p d, FILE *f)
295{
296	/* XXX FIXME hack! */
297	struct report_desc {
298		unsigned int	size;
299		unsigned char	data[1];
300	};
301	/* XXX FIXME hack! */
302
303	struct report_desc	*desc = (struct report_desc *) d->desc;
304	uint32_t		 i;
305
306	fprintf(f,
307"device {\n"					\
308"	bdaddr			%s;\n"		\
309"	control_psm		0x%x;\n"	\
310"	interrupt_psm		0x%x;\n"	\
311"	reconnect_initiate	%s;\n"		\
312"	battery_power		%s;\n"		\
313"	normally_connectable	%s;\n"		\
314"	hid_descriptor		{",
315		bt_ntoa(&d->bdaddr, NULL),
316		d->control_psm, d->interrupt_psm,
317                d->reconnect_initiate? "true" : "false",
318                d->battery_power? "true" : "false",
319                d->normally_connectable? "true" : "false");
320
321	for (i = 0; i < desc->size; i ++) {
322			if ((i % 8) == 0)
323				fprintf(f, "\n		");
324
325			fprintf(f, "0x%2.2x ", desc->data[i]);
326	}
327
328	fprintf(f,
329"\n"		\
330"	};\n"	\
331"}\n");
332}
333
334/* Check config entry */
335static int32_t
336check_hid_device(hid_device_p d)
337{
338	hid_data_t	hd;
339	hid_item_t	hi;
340	int32_t		page;
341
342	if (get_hid_device(&d->bdaddr) != NULL) {
343		SYSLOG(LOGERR, "Ignoring duplicated entry for bdaddr %s" EOL,
344				bt_ntoa(&d->bdaddr, NULL));
345		return (0);
346	}
347
348	if (d->control_psm == 0) {
349		SYSLOG(LOGERR, "Ignoring entry with invalid control PSM" EOL);
350		return (0);
351	}
352
353	if (d->interrupt_psm == 0) {
354		SYSLOG(LOGERR, "Ignoring entry with invalid interrupt PSM" EOL);
355		return (0);
356	}
357
358	if (d->desc == NULL) {
359		SYSLOG(LOGERR, "Ignoring entry without HID descriptor" EOL);
360		return (0);
361	}
362
363	/* XXX somehow need to make sure descriptor is valid */
364	for (hd = hid_start_parse(d->desc, ~0, -1); hid_get_item(hd, &hi) > 0; ) {
365		switch (hi.kind) {
366		case hid_collection:
367		case hid_endcollection:
368		case hid_output:
369		case hid_feature:
370			break;
371
372		case hid_input:
373			/* Check if the device may send keystrokes */
374			page = HID_PAGE(hi.usage);
375			if (page == HUP_KEYBOARD)
376				d->keyboard = 1;
377			break;
378		}
379	}
380	hid_end_parse(hd);
381
382	return (1);
383}
384
385/* Free config entry */
386static void
387free_hid_device(hid_device_p d)
388{
389	if (d->desc != NULL)
390		hid_dispose_report_desc(d->desc);
391
392	memset(d, 0, sizeof(*d));
393	free(d);
394}
395
396/* Re-read hids file */
397int32_t
398read_hids_file(void)
399{
400	FILE		*f;
401	hid_device_t	*d;
402	char		*line;
403	bdaddr_t	 bdaddr;
404	int32_t		 lineno;
405
406	if (hids_file == NULL) {
407		SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
408		return (-1);
409	}
410
411	if ((f = fopen(hids_file, "r")) == NULL) {
412		if (errno == ENOENT)
413			return (0);
414
415		SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
416			hids_file, strerror(errno), errno);
417		return (-1);
418	}
419
420	for (lineno = 1; fgets(buffer, sizeof(buffer), f) != NULL; lineno ++) {
421		if ((line = strtok(buffer, "\r\n\t ")) == NULL)
422			continue; /* ignore empty lines */
423
424		if (!bt_aton(line, &bdaddr)) {
425			SYSLOG(LOGWARNING, "Ignoring unparseable BD_ADDR in " \
426				"%s:%d" EOL, hids_file, lineno);
427			continue;
428		}
429
430		if ((d = get_hid_device(&bdaddr)) != NULL)
431			d->new_device = 0;
432	}
433
434	fclose(f);
435
436	return (0);
437}
438
439/* Write hids file */
440int32_t
441write_hids_file(void)
442{
443	char		 path[PATH_MAX];
444	FILE		*f;
445	hid_device_t	*d;
446
447	if (hids_file == NULL) {
448		SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
449		return (-1);
450	}
451
452	snprintf(path, sizeof(path), "%s.new", hids_file);
453
454	if ((f = fopen(path, "w")) == NULL) {
455		SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
456			path, strerror(errno), errno);
457		return (-1);
458	}
459
460	LIST_FOREACH(d, &hid_devices, next)
461		if (!d->new_device)
462			fprintf(f, "%s\n", bt_ntoa(&d->bdaddr, NULL));
463
464	fclose(f);
465
466	if (rename(path, hids_file) < 0) {
467		SYSLOG(LOGERR, "Could not rename new HIDs file '%s' to '%s'. " \
468			"%s (%d)" EOL, path, hids_file, strerror(errno), errno);
469		unlink(path);
470		return (-1);
471	}
472
473	return (0);
474}
475
476