FreeBSD-5.3/usr.sbin/usbd/usbd.c

Compare this file to the similar file:
Show the results in this format:

/*	$NetBSD: usbd.c,v 1.4 1998/12/09 00:57:19 augustss Exp $	*/
/*	$FreeBSD: src/usr.sbin/usbd/usbd.c,v 1.29 2003/10/25 22:03:10 jmg 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 <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	4

/* 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 = malloc(len+1);
		if (linez == NULL) {
			fprintf(stderr, "%s:%d: Out of memory\n",
				configfile, lineno);
			exit(2);
		}
		strncpy(linez, line, len);
		linez[len] = '\0';

		/* find the end of the current word (is field), that's the
		 * start of the arguments
		 */
		field = linez;
		args = linez;
		while (*args != '\0' && !isspace(*args))
			args++;

		/* If arguments is not the empty string, NULL terminate the
		 * field and move the argument pointer to the first character
		 * of the arguments.
		 * If arguments is the empty string field and arguments both
		 * are terminated (strlen(field) >= 0, strlen(arguments) == 0).
		 */
		if (*args != '\0') {
			*args = '\0';
			args++;
		}

		/* Skip initial spaces */
		while (*args != '\0' && isspace(*args))
			args++;

		/* Cut off trailing whitespace */
		for (i = 0, j = 0; args[i] != '\0'; i++)
			if (!isspace(args[i]))
				j = i+1;
		args[j] = '\0';

		/* We now have the field and the argument separated into
		 * two strings that are NULL terminated
		 */

		/* If the field is 'device' we have to start a new action. */
		if (strcmp(field, "device") == 0) {
			/* Allocate a new action and set defaults */
			action = malloc(sizeof(*action));
			if (action == NULL) {
				fprintf(stderr, "%s:%d: Out of memory\n",
					configfile, lineno);
				exit(2);
			}
			memset(action, 0, sizeof(*action));
			action->product = WILDCARD_INT;
			action->vendor = WILDCARD_INT;
			action->release = WILDCARD_INT;
			action->class = WILDCARD_INT;
			action->subclass = WILDCARD_INT;
			action->protocol = WILDCARD_INT;
			action->devname = WILDCARD_STRING;

			/* Add it to the end of the list to preserve order */
			STAILQ_INSERT_TAIL(&actions, action, next);
		}

		if (action == NULL) {
			line[len] = '\0';	/* XXX zero terminate */
			fprintf(stderr, "%s:%d: Doesn't start with 'device' "
				"but '%s'\n", configfile, lineno, field);
			exit(2);
		}
		
		for (i = 0; config_fields[i].name  ; i++) {
			/* does the field name match? */
			if (strcmp(config_fields[i].name, field) == 0) {
				/* execute corresponding set-field function */
				if ((config_fields[i].function)(action, args,
								&trail)
				    != 1) {
					fprintf(stderr,"%s:%d: "
						"Syntax error in '%s'\n",
						configfile, lineno, linez);
					exit(2);
				}
				break;
			}
		}
		if (config_fields[i].name == NULL) {	/* Reached end of list*/
			fprintf(stderr, "%s:%d: Unknown field '%s'\n",
				configfile, lineno, field);
			exit(2);
		}
	}

	fclose(file);

	if (verbose >= 2)
		print_actions();
}


void
print_event(struct usb_event *event)
{
	int i;
	struct timespec *timespec = &event->ue_time;
	struct usb_device_info *devinfo = &event->u.ue_device;

	printf("%s: ", __progname);
	for (i = 0; event_names[i].name != NULL; i++) {
		if (event->ue_type == event_names[i].type) {
			printf("%s event", event_names[i].name);
			break;
		}
	}
	if (event_names[i].name == NULL)
		printf("unknown event %d", event->ue_type);

	if (event->ue_type == USB_EVENT_DEVICE_ATTACH ||
	    event->ue_type == USB_EVENT_DEVICE_DETACH) {
		devinfo = &event->u.ue_device;

		printf(" at %ld.%09ld, %s, %s:\n",
			timespec->tv_sec, timespec->tv_nsec,
			devinfo->udi_product, devinfo->udi_vendor);

		printf("  vndr=0x%04x prdct=0x%04x rlse=0x%04x "
		       "clss=0x%04x subclss=0x%04x prtcl=0x%04x\n",
		       devinfo->udi_vendorNo, devinfo->udi_productNo,
		       devinfo->udi_releaseNo,
		       devinfo->udi_class, devinfo->udi_subclass, devinfo->udi_protocol);

		if (devinfo->udi_devnames[0][0] != '\0') {
			char c = ' ';

			printf("  device names:");
			for (i = 0; i < USB_MAX_DEVNAMES; i++) {
				if (devinfo->udi_devnames[i][0] == '\0')
					break;

				printf("%c%s", c, devinfo->udi_devnames[i]);
				c = ',';
			}
		}
	} else if (event->ue_type == USB_EVENT_CTRLR_ATTACH ||
	    event->ue_type == USB_EVENT_CTRLR_DETACH) {
		printf(" bus=%d", &event->u.ue_ctrlr.ue_bus);
	} else if (event->ue_type == USB_EVENT_DRIVER_ATTACH ||
	    event->ue_type == USB_EVENT_DRIVER_DETACH) {
		printf(" cookie=%u devname=%s",
		    &event->u.ue_driver.ue_cookie.cookie,
		    &event->u.ue_driver.ue_devname);
	}
	printf("\n");
}

void
print_action(action_t *action, int i)
{
	if (action == NULL)
		return;

	printf("%s: action %d: %s\n",
		__progname, i,
		(action->name? action->name:""));
	if (action->product != WILDCARD_INT ||
	    action->vendor != WILDCARD_INT ||
	    action->release != WILDCARD_INT ||
	    action->class != WILDCARD_INT ||
	    action->subclass != WILDCARD_INT ||
	    action->protocol != WILDCARD_INT)
		printf(" ");
	if (action->vendor != WILDCARD_INT)
		printf(" vndr=0x%04x", action->vendor);
	if (action->product != WILDCARD_INT)
		printf(" prdct=0x%04x", action->product);
	if (action->release != WILDCARD_INT)
		printf(" rlse=0x%04x", action->release);
	if (action->class != WILDCARD_INT)
		printf(" clss=0x%04x", action->class);
	if (action->subclass != WILDCARD_INT)
		printf(" subclss=0x%04x", action->subclass);
	if (action->protocol != WILDCARD_INT)
		printf(" prtcl=0x%04x", action->protocol);
	if (action->vendor != WILDCARD_INT ||
	    action->product != WILDCARD_INT ||
	    action->release != WILDCARD_INT ||
	    action->class != WILDCARD_INT ||
	    action->subclass != WILDCARD_INT ||
	    action->protocol != WILDCARD_INT)
		printf("\n");
	if (action->devname != WILDCARD_STRING)
		printf("  devname: %s\n", action->devname);

	if (action->attach != NULL)
		printf("  attach='%s'\n",
			action->attach);
	if (action->detach != NULL)
		printf("  detach='%s'\n",
			action->detach);
}

void
print_actions()
{
	int i = 0;
	action_t *action;

	STAILQ_FOREACH(action, &actions, next)
		print_action(action, ++i);

	printf("%s: %d action%s\n", __progname, i, (i == 1? "":"s"));
}


int
match_devname(action_t *action, struct usb_device_info *devinfo)
{
	int i;
	regmatch_t match;
	int error;

	for (i = 0; i < USB_MAX_DEVNAMES; i++) {
		if (devinfo->udi_devnames[i][0] == '\0')
			break;

		error = regexec(&action->devname_regex, devinfo->udi_devnames[i],
				1, &match, 0);
		if (error == 0) {
			if (verbose >= 2)
				printf("%s: %s matches %s\n", __progname,
					devinfo->udi_devnames[i], action->devname);
			return(i);
		}
	}
	
	return(-1);
}


int
find_action(struct usb_device_info *devinfo, action_match_t *action_match)
{
	action_t *action;
	char *devname = NULL;
	int match = -1;

	STAILQ_FOREACH(action, &actions, next) {
		if ((action->vendor == WILDCARD_INT ||
		     action->vendor == devinfo->udi_vendorNo) &&
		    (action->product == WILDCARD_INT ||
		     action->product == devinfo->udi_productNo) &&
		    (action->release == WILDCARD_INT ||
		     action->release == devinfo->udi_releaseNo) &&
		    (action->class == WILDCARD_INT ||
		     action->class == devinfo->udi_class) &&
		    (action->subclass == WILDCARD_INT ||
		     action->subclass == devinfo->udi_subclass) &&
		    (action->protocol == WILDCARD_INT ||
		     action->protocol == devinfo->udi_protocol) &&
		    (action->devname == WILDCARD_STRING ||
		     (match = match_devname(action, devinfo)) != -1)) {
			/* found match !*/

			/* Find a devname for pretty printing. Either
			 * the matched one or otherwise, if there is only
			 * one devname for that device, use that.
			 */
			if (match >= 0)
				devname = devinfo->udi_devnames[match];
			else if (devinfo->udi_devnames[0][0] != '\0' &&
				 devinfo->udi_devnames[1][0] == '\0')
				/* if we have exactly 1 device name */
				devname = devinfo->udi_devnames[0];

			if (verbose) {
				printf("%s: Found action '%s' for %s, %s",
					__progname, action->name,
					devinfo->udi_product, devinfo->udi_vendor);
				if (devname)
					printf(" at %s", devname);
				printf("\n");
			}

			action_match->action = action;
			action_match->devname = devname;

			return(1);
		}
	}

	return(0);
}

void
execute_command(char *cmd)
{
	pid_t pid;
	struct sigaction ign, intact, quitact;
	sigset_t newsigblock, oldsigblock;
	int status;
	int i;

	if (verbose)
		printf("%s: Executing '%s'\n", __progname, cmd);
	if (cmd == NULL)
		return;

	/* The code below is directly taken from the system(3) call.
	 * Added to it is the closing of open file descriptors.
	 */
	/*
	 * Ignore SIGINT and SIGQUIT, block SIGCHLD. Remember to save
	 * existing signal dispositions.
	 */
	ign.sa_handler = SIG_IGN;
	(void) sigemptyset(&ign.sa_mask);
	ign.sa_flags = 0;
	(void) sigaction(SIGINT, &ign, &intact);
	(void) sigaction(SIGQUIT, &ign, &quitact);
	(void) sigemptyset(&newsigblock);
	(void) sigaddset(&newsigblock, SIGCHLD);
	(void) sigprocmask(SIG_BLOCK, &newsigblock, &oldsigblock);
	pid = fork();
	if (pid == -1) {
		fprintf(stderr, "%s: fork failed, %s\n",
			__progname, strerror(errno));
	} else if (pid == 0) {
		/* child here */

		/* close all open file handles for USBDEV\d* devices */
		for (i = 0; i < ndevs; i++)
			close(fds[i]);		/* USBDEV\d+ */
		close(fd);			/* USBDEV */

		/* Restore original signal dispositions and exec the command. */
		(void) sigaction(SIGINT, &intact, NULL);
		(void) sigaction(SIGQUIT,  &quitact, NULL);
		(void) sigprocmask(SIG_SETMASK, &oldsigblock, NULL);

		execl(_PATH_BSHELL, "sh", "-c", cmd, (char *)NULL);

		/* should only be reached in case of error */
		exit(127);
	} else {
		/* parent here */
		do {
			pid = waitpid(pid, &status, 0);
		} while (pid == -1 && errno == EINTR);
	}
	(void) sigaction(SIGINT, &intact, NULL);
	(void) sigaction(SIGQUIT,  &quitact, NULL);
	(void) sigprocmask(SIG_SETMASK, &oldsigblock, NULL);

	if (pid == -1) {
		fprintf(stderr, "%s: waitpid returned: %s\n",
			__progname, strerror(errno));
	} else if (pid == 0) {
		fprintf(stderr, "%s: waitpid returned 0 ?!\n",
			__progname);
	} else {
		if (status == -1) {
			fprintf(stderr, "%s: Could not start '%s'\n",
				__progname, cmd);
		} else if (status == 127) {
			fprintf(stderr, "%s: Shell failed for '%s'\n",
				__progname, cmd);
		} else if (WIFEXITED(status) && WEXITSTATUS(status)) {
			fprintf(stderr, "%s: '%s' returned %d\n",
				__progname, cmd, WEXITSTATUS(status));
		} else if (WIFSIGNALED(status)) {
			fprintf(stderr, "%s: '%s' caught signal %d\n",
				__progname, cmd, WTERMSIG(status));
		} else if (verbose >= 2) {
			printf("%s: '%s' is ok\n", __progname, cmd);
		}
	}
}

void
process_event_queue(int fd)
{
	struct usb_event event;
	int error;
	int len;
	action_match_t action_match;

	for (;;) {
		len = read(fd, &event, sizeof(event));
		if (len == -1) {
			if (errno == EWOULDBLOCK) {
				/* no more events */
				break;
			} else {
				fprintf(stderr,"%s: Could not read event, %s\n",
					__progname, strerror(errno));
				exit(1);
			}
		}
		if (len == 0)
			break;
		if (len != sizeof(event)) {
			fprintf(stderr, "partial read on %s\n", USBDEV);
			exit(1);
		}

		/* we seem to have gotten a valid event */

		if (verbose)
			print_event(&event);

		/* handle the event appropriately */
		switch (event.ue_type) {
		case USB_EVENT_CTRLR_ATTACH:
			if (verbose)
				printf("USB_EVENT_CTRLR_ATTACH\n");
			break;
		case USB_EVENT_CTRLR_DETACH:
			if (verbose)
				printf("USB_EVENT_CTRLR_DETACH\n");
			break;
		case USB_EVENT_DEVICE_ATTACH:
		case USB_EVENT_DEVICE_DETACH:
			if (find_action(&event.u.ue_device, &action_match) == 0)
				/* nothing found */
				break;

			if (verbose >= 2)
				print_action(action_match.action, 0);

			if (action_match.devname) {
				if (verbose >= 2)
					printf("%s: Setting DEVNAME='%s'\n",
						__progname, action_match.devname);

				error = setenv("DEVNAME", action_match.devname, 1);
				if (error)
					fprintf(stderr, "%s: setenv(\"DEVNAME\", \"%s\",1) failed, %s\n",
						__progname, action_match.devname, strerror(errno));
			}

			if (USB_EVENT_IS_ATTACH(event.ue_type) &&
			    action_match.action->attach) 
				execute_command(action_match.action->attach);
			if (USB_EVENT_IS_DETACH(event.ue_type) &&
			    action_match.action->detach)
				execute_command(action_match.action->detach);
			break;
		case USB_EVENT_DRIVER_ATTACH:
			if (verbose)
				printf("USB_EVENT_DRIVER_ATTACH\n");
			break;
		case USB_EVENT_DRIVER_DETACH:
			if (verbose)
				printf("USB_EVENT_DRIVER_DETACH\n");
			break;
		default:
			printf("Unknown USB event %d\n", event.ue_type);
		}
	}	
}


int
main(int argc, char **argv)
{
	int error, i;
	int ch;			/* getopt option */
	int debug = 0;		/* print debugging output */
	int explore_once = 0;	/* don't do only explore */
	int handle_events = 1;	/* do handle the event queue */
	int maxfd;		/* maximum fd in use */
	char buf[50];		/* for creation of the filename */
	fd_set r,w;
	int itimeout = TIMEOUT;	/* timeout for select */
	struct timeval tv;

	if (modfind(USB_UHUB) < 0) {
		if (kldload(USB_KLD) < 0 || modfind(USB_UHUB) < 0) {
			perror(USB_KLD ": Kernel module not available");
			return 1;
		}
	}

	while ((ch = getopt(argc, argv, "c:def:nt:v")) != -1) {
		switch(ch) {
		case 'c':
			configfile = strdup(optarg);
			if (configfile == NULL) {
				fprintf(stderr, "strdup returned NULL\n");
				return 1;
			}
			break;
		case 'd':
			debug++;
			break;
		case 'e':
			explore_once = 1;
			break;
		case 'f':
			if (ndevs < MAXUSBDEV)
				devs[ndevs++] = optarg;
			break;
		case 'n':
			handle_events = 0;
			break;
		case 't':
			itimeout = atoi(optarg);
			break;
		case 'v':
			verbose++;
			break;
		case '?':
		default:
			usage();
		}
	}
	argc -= optind;
	argv += optind;

	maxfd = 0;
	if (ndevs == 0) {
		/* open all the USBDEVS\d+ devices */
		for (i = 0; i < MAXUSBDEV; i++) {
			sprintf(buf, "%s%d", USBDEV, i);
			fds[ndevs] = open(buf, O_RDWR);
			if (fds[ndevs] >= 0) {
				devs[ndevs] = strdup(buf);
				if (devs[ndevs] == NULL) {
					fprintf(stderr, "strdup returned NULL\n");
					return 1;
				}
				if (verbose)
					printf("%s: opened %s\n", 
					       __progname, devs[ndevs]);
				if (fds[ndevs] > maxfd)
					maxfd = fds[ndevs];
				ndevs++;
			} else if (errno != ENXIO && errno != ENOENT) {
				/* there was an error, on a device that does
				 * exist (device is configured)
				 */
				fprintf(stderr, "%s: Could not open %s, %s\n",
					__progname, buf, strerror(errno));
				exit(1);
			}
		}
	} else {
		/* open all the files specified with -f */
		for (i = 0; i < ndevs; i++) {
			fds[i] = open(devs[i], O_RDWR);
			if (fds[i] < 0) {
				fprintf(stderr, "%s: Could not open %s, %s\n",
					__progname, devs[i], strerror(errno));
				exit(1);
			} else {
				if (verbose)
					printf("%s: opened %s\n", 
					       __progname, devs[i]);
				if (fds[i] > maxfd)
					maxfd = fds[i];
			}
		}
	}

	if (ndevs == 0) {
		fprintf(stderr, "No USB host controllers found\n");
		exit(1);
	}


	/* Do the explore once and exit */
	if (explore_once) {
		for (i = 0; i < ndevs; i++) {
			error = ioctl(fds[i], USB_DISCOVER);
			if (error < 0) {
				fprintf(stderr, "%s: ioctl(%s, USB_DISCOVER) "
					"failed, %s\n",
					__progname, devs[i], strerror(errno));
				exit(1);
			}
		}
		exit(0);
	}

	if (handle_events) {
		if (verbose)
			printf("%s: reading configuration file %s\n",
				__progname, configfile);
		read_configuration();

		fd = open(USBDEV, O_RDONLY | O_NONBLOCK);
		if (fd < 0) {
			fprintf(stderr, "%s: Could not open %s, %s\n",
				__progname, USBDEV, strerror(errno));
			exit(1);
		}
		if (verbose)
			printf("%s: opened %s\n", __progname, USBDEV);
		if (fd > maxfd)
			maxfd = fd;

		process_event_queue(fd);	/* dequeue the initial events */
	}

	/* move to the background */
	if (!debug)
		daemon(0, 0);

	/* start select on all the open file descriptors */
	for (;;) {
		FD_ZERO(&r);
		FD_ZERO(&w);
		if (handle_events)
			FD_SET(fd, &r);		/* device USBDEV */
		for (i = 0; i < ndevs; i++)
			FD_SET(fds[i], &w);	/* device USBDEV\d+ */
		tv.tv_usec = 0;
		tv.tv_sec = itimeout;
		error = select(maxfd+1, &r, &w, 0, itimeout ? &tv : 0);
		if (error < 0) {
			fprintf(stderr, "%s: Select failed, %s\n",
				__progname, strerror(errno));
			exit(1);
		}

		/* USBDEV\d+ devices have signaled change, do a usb_discover */
		for (i = 0; i < ndevs; i++) {
			if (error == 0 || FD_ISSET(fds[i], &w)) {
				if (verbose >= 2)
					printf("%s: doing %sdiscovery on %s\n", 
					       __progname,
					       (error? "":"timeout "), devs[i]);
				if (ioctl(fds[i], USB_DISCOVER) < 0) {
					fprintf(stderr, "%s: ioctl(%s, "
						"USB_DISCOVER) failed, %s\n",
						__progname, devs[i],
						strerror(errno));
					exit(1);
				}
			}
		}

		/* check the event queue */
		if (handle_events && (FD_ISSET(fd, &r) || error == 0)) {
			if (verbose >= 2)
				printf("%s: processing event queue %son %s\n",
					__progname,
				       (error? "":"due to timeout "), USBDEV);
			process_event_queue(fd);
		}
	}
}