Google

usbd.c
/*	$NetBSD: usbd.c,v 1.4 1998/12/09 00:57:19 augustss Exp $	*/
/*	$FreeBSD: src/usr.sbin/usbd/usbd.c,v 1.32 2005/07/01 15:49:52 jhb Exp $	*/

/*
 * Copyright (c) 1998 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Lennart Augustsson (augustss@netbsd.org).
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *        This product includes software developed by the NetBSD
 *        Foundation, Inc. and its contributors.
 * 4. Neither the name of The NetBSD Foundation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/* USBD creates 'threads' in the kernel, used for doing discovery when a
 * device has attached or detached. This functionality should be removed
 * once kernel threads have been added to the kernel.
 * It also handles the event queue, and executing commands based on those
 * events.
 *
 * See usbd(8).
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <signal.h>
#include <paths.h>
#include <stdint.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/linker.h>
#include <sys/module.h>
#include <sys/queue.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <regex.h>

#include <dev/usb/usb.h>

/* default name of configuration file
 */

#define CONFIGFILE	"/etc/usbd.conf"

/* the name of the device spitting out usb attach/detach events as well as
 * the prefix for the individual busses (used as a semi kernel thread).
 */
#define USBDEV		"/dev/usb"

/* Maximum number of USB busses expected to be in a system
 * XXX should be replaced by dynamic allocation.
 */
#define MAXUSBDEV	40

/* Sometimes a device does not respond in time for interrupt
 * driven explore to find it.  Therefore we run an exploration
 * at regular intervals to catch those.
 */
#define TIMEOUT		30

/* The wildcard used in actions for strings and integers
 */
#define WILDCARD_STRING	NULL
#define WILDCARD_INT	-1


extern char *__progname;	/* name of program */

char *configfile = CONFIGFILE;	/* name of configuration file */

char *devs[MAXUSBDEV];		/* device names */
int fds[MAXUSBDEV];		/* file descriptors for USBDEV\d+ */
int ndevs = 0;			/* number of entries in fds / devs */
int fd = -1;			/* file descriptor for USBDEV */

int lineno;
int verbose = 0;		/* print message on what it is doing */

typedef struct event_name_s {
	int	type;		/* event number (from usb.h) */
	char	*name;
} event_name_t;

event_name_t event_names[] = {
	{USB_EVENT_CTRLR_ATTACH, "ctrlr-attach"},
	{USB_EVENT_CTRLR_DETACH, "ctrlr-detach"},
	{USB_EVENT_DRIVER_ATTACH, "driver-attach"},
	{USB_EVENT_DRIVER_DETACH, "driver-detach"},
	{USB_EVENT_DEVICE_ATTACH, "device-attach"},
	{USB_EVENT_DEVICE_DETACH, "device-detach"},
	{0, NULL}			/* NULL indicates end of list, not 0 */
};

#define DEVICE_FIELD		0	/* descriptive field */

#define VENDOR_FIELD		1	/* selective fields */
#define PRODUCT_FIELD		2
#define RELEASE_FIELD		3
#define CLASS_FIELD		4
#define SUBCLASS_FIELD		5
#define PROTOCOL_FIELD		6
#define DEVNAME_FIELD		7

#define ATTACH_FIELD		8	/* command fields */
#define DETACH_FIELD		9


typedef struct action_s {
	char 	*name;		/* descriptive string */

	int	vendor;	/* selection criteria */
	int	product;
	int	release;
	int	class;
	int	subclass;
	int	protocol;
	char 	*devname;
	regex_t	devname_regex;

	char	*attach;	/* commands to execute */
	char	*detach;

	STAILQ_ENTRY(action_s) next;
} action_t;

STAILQ_HEAD(action_list, action_s) actions = STAILQ_HEAD_INITIALIZER(actions);

typedef struct action_match_s {
	action_t *action;
	char	*devname;
} action_match_t;


/* the function returns 0 for failure, 1 for all arguments found and 2 for
 * arguments left over in trail.
 */
typedef int (*config_field_fn)	__P((action_t *action, char *args,
					char **trail));

int set_device_field(action_t *action, char *args, char **trail);
int set_vendor_field(action_t *action, char *args, char **trail);
int set_product_field(action_t *action, char *args, char **trail);
int set_release_field(action_t *action, char *args, char **trail);
int set_class_field(action_t *action, char *args, char **trail);
int set_subclass_field(action_t *action, char *args, char **trail);
int set_protocol_field(action_t *action, char *args, char **trail);
int set_devname_field(action_t *action, char *args, char **trail);
int set_attach_field(action_t *action, char *args, char **trail);
int set_detach_field(action_t *action, char *args, char **trail);

/* the list of fields supported in an entry */
typedef struct config_field_s {
	int	event;
	char 	*name;
	config_field_fn	function;
} config_field_t;

config_field_t config_fields[] = {
	{DEVICE_FIELD,		"device",	set_device_field},

	{VENDOR_FIELD,		"vendor",	set_vendor_field},
	{PRODUCT_FIELD,		"product",	set_product_field},
	{RELEASE_FIELD,		"release",	set_release_field},
	{CLASS_FIELD,		"class",	set_class_field},
	{SUBCLASS_FIELD,	"subclass",	set_subclass_field},
	{PROTOCOL_FIELD,	"protocol",	set_protocol_field},
	{DEVNAME_FIELD,		"devname",	set_devname_field},

	{ATTACH_FIELD,		"attach",	set_attach_field},
	{DETACH_FIELD,		"detach",	set_detach_field},

	{0, NULL, NULL}		/* NULL is EOL marker, not the 0 */
};


/* prototypes for some functions */
void print_event	__P((struct usb_event *event));
void print_action	__P((action_t *action, int i));
void print_actions	__P((void));
int  find_action	__P((struct usb_device_info *devinfo,
			action_match_t *action_match));


void
usage(void)
{
	fprintf(stderr, "usage: %s [-d] [-v] [-t timeout] [-e] [-f dev]\n"
			"           [-n] [-c config]\n",
		__progname);
	exit(1);
}


/* generic helper functions for the functions to set the fields of actions */
int
get_string(char *src, char **rdst, char **rsrc)
{
	/* Takes the first string from src, taking quoting into account.
	 * rsrc (if not NULL) is set to the first byte not included in the
	 * string returned in rdst.
	 *
	 * Input is:
	 *   src = 'fir"st \'par"t       second part';
	 * Returned is:
	 *   *dst = 'hello \'world';
	 *   if (rsrc != NULL)
	 *     *rsrc = 'second part';
	 *
	 * Notice the fact that the single quote enclosed in double quotes is
	 * returned. Also notice that before second part there is more than
	 * one space, which is removed in rsrc.
	 *
	 * The string in src is not modified.
	 */

	char *dst;		/* destination string */
	int i;			/* index into src */
	int j;			/* index into dst */
	int quoted = 0;		/* 1 for single, 2 for double quoted */

	dst = malloc(strlen(src)+1);	/* XXX allocation is too big, realloc?*/
	if (dst == NULL) {		/* should not happen, really */
		fprintf(stderr, "%s:%d: Out of memory\n", configfile, lineno);
		exit(2);
	}

	/* find the end of the current string. If quotes are found the search
	 * continues until the corresponding quote is found.
	 * So,
	 *   hel'lo" "wor'ld
	 * represents the string
	 *   hello" "world
	 * and not (hello world).
	 */
	for (i = 0, j = 0; i < strlen(src); i++) {
		if (src[i] == '\'' && (quoted == 0 || quoted == 1)) {
			quoted = (quoted? 0:1);
		} else if (src[i] == '"' && (quoted == 0 || quoted == 2)) {
			quoted = (quoted? 0:2);
		} else if (isspace(src[i]) && !quoted) {
			/* found a space outside quotes -> terminates src */
			break;
		} else {
			dst[j++] = src[i];	/* copy character */
		}
	}

	/* quotes being left open? */
	if (quoted) {
		fprintf(stderr, "%s:%d: Missing %s quote at end of '%s'\n",
			configfile, lineno,
			(quoted == 1? "single":"double"), src);
		exit(2);
	}

	/* skip whitespace for second part */
	for (/*i is set*/; i < strlen(src) && isspace(src[i]); i++)
		;	/* nop */

	dst[j] = '\0';			/* make sure it's NULL terminated */

	*rdst = dst;			/* and return the pointers */
	if (rsrc != NULL)		/* if info wanted */
		*rsrc = &src[i];

	if (*dst == '\0') {		/* empty string */
		return 0;
	} else if (src[i] == '\0') {	/* completely used (1 argument) */
		return 1;
	} else {			/* 2 or more args, *rsrc is rest */
		return 2;
	}
}

int
get_integer(char *src, int *dst, char **rsrc)
{
	char *endptr;

	/* Converts str to a number. If one argument was found in
	 * str, 1 is returned and *dst is set to the value of the integer.
	 * If 2 or more arguments were presented, 2 is returned,
	 * *dst is set to the converted value and rsrc, if not null, points
	 * at the start of the next argument (whitespace skipped).
	 * Else 0 is returned and nothing else is valid.
	 */

	if (src == NULL || *src == '\0')	/* empty src */
		return(0);

	*dst = (int) strtol(src, &endptr, 0);

	/* skip over whitespace of second argument */
	while (isspace(*endptr))
		endptr++;

	if (rsrc)
		*rsrc = endptr;

	if (isspace(*endptr)) {		/* partial match, 2 or more arguments */
		return(2);
	} else if (*endptr == '\0') {	/* full match, 1 argument */
		return(1);
	} else {			/* invalid src, no match */
		return(0);
	}
}

/* functions to set the fields of the actions appropriately */
int
set_device_field(action_t *action, char *args, char **trail)
{
	return(get_string(args, &action->name, trail));
}
int
set_vendor_field(action_t *action, char *args, char **trail)
{
	return(get_integer(args, &action->vendor, trail));
}
int
set_product_field(action_t *action, char *args, char **trail)
{
	return(get_integer(args, &action->product, trail));
}
int
set_release_field(action_t *action, char *args, char **trail)
{
	return(get_integer(args, &action->release, trail));
}
int
set_class_field(action_t *action, char *args, char **trail)
{
	return(get_integer(args, &action->class, trail));
}
int
set_subclass_field(action_t *action, char *args, char **trail)
{
	return(get_integer(args, &action->subclass, trail));
}
int
set_protocol_field(action_t *action, char *args, char **trail)
{
	return(get_integer(args, &action->protocol, trail));
}
int
set_devname_field(action_t *action, char *args, char **trail)
{
	int match = get_string(args, &action->devname, trail);
	int len;
	int error;
	char *string;
#	define ERRSTR_SIZE	100
	char errstr[ERRSTR_SIZE];

	if (match == 0)
		return(0);

	len = strlen(action->devname);
	string = malloc(len + 15);
	if (string == NULL)
		return(0);

	bcopy(action->devname, string+7, len);	/* make some space for */
	bcopy("[[:<:]]", string, 7);		/*   beginning of word */
	bcopy("[[:>:]]", string+7+len, 7);	/*   and end of word   */
	string[len + 14] = '\0';

	error = regcomp(&action->devname_regex, string, REG_NOSUB|REG_EXTENDED);
	if (error) {
		errstr[0] = '\0';
		regerror(error, &action->devname_regex, errstr, ERRSTR_SIZE);
		fprintf(stderr, "%s:%d: %s\n", configfile, lineno, errstr);
		return(0);
	}

	return(match);
}
int
set_attach_field(action_t *action, char *args, char **trail)
{
	return(get_string(args, &action->attach, trail));
}
int
set_detach_field(action_t *action, char *args, char **trail)
{
	return(get_string(args, &action->detach, trail));
}


void
read_configuration(void)
{
	FILE *file;		/* file descriptor */
	char *line;		/* current line */
	char *linez;		/* current line, NULL terminated */
	char *field;		/* first part, the field name */
	char *args;		/* second part, arguments */
	char *trail;		/* remaining part after parsing, should be '' */
	size_t len;		/* length of current line */
	int i,j;		/* loop counters */
	action_t *action = NULL;	/* current action */

	file = fopen(configfile, "r");
	if (file == NULL) {
		fprintf(stderr, "%s: Could not open for reading, %s\n",
			configfile, strerror(errno));
		exit(2);
	}

	for (lineno = 1; /* nop */;lineno++) {
	
		line = fgetln(file, &len);
		if (line == NULL) {
			if (feof(file))			/* EOF */
				break;
			if (ferror(file)) {
				fprintf(stderr, "%s:%d: Could not read, %s\n",
					configfile, lineno, strerror(errno));
				exit(2);
			}
		}

		/* skip initial spaces */
		while (len > 0 && isspace(*line)) {
			line++;
			len--;
		}

		if (len == 0)		/* empty line */
			continue;
		if (line[0] == '#')	/* comment line */
			continue;

		/* make a NULL terminated copy of the string */
		linez =