usb/122819: Patch to provide dynamic additions to the usb quirks table

Maurice Castro maurice at castro.aus.net
Thu Apr 17 03:10:04 UTC 2008


The following reply was made to PR usb/122819; it has been noted by GNATS.

From: Maurice Castro <maurice at castro.aus.net>
To: bug-followup at FreeBSD.org, maurice at castro.aus.net
Cc:  
Subject: Re: usb/122819: Patch to provide dynamic additions to the usb quirks table
Date: Thu, 17 Apr 2008 12:23:36 +1000

 --Apple-Mail-2-311066617
 Content-Type: text/plain;
 	charset=US-ASCII;
 	format=flowed;
 	delsp=yes
 Content-Transfer-Encoding: 7bit
 
 Hi Hans,
 	in addition to the corrections below I have added an extra utility to  
 the system this utility calls the ioctl to load the dynamic quirks  
 table. Patch is attached. As noted below I have not incorporated the  
 bsearch for the reasons stated.
 
 	Maurice Castro
 
 From: Hans Petter Selasky <hselasky at c2i.net>
 Date: Wed, 16 Apr 2008 18:54:02 +0200
 > Hi,
 >
 > Maybe you can prefix the string version of the quirks by the USB  
 > module name ?
 >
 > For example:
 >
 > MS_REVZ -> UMS_REVZ
 > AU_NO_FRAC -> UAUDIO_NO_FRAC
 >
 
 Done
 
 > Then it is easier to know where the quirk belongs.
 >
 > Add a short description of what the quirk does in the "usb" manpage.
 >
 
 Done
 
 > There might be a race condition reading and writing the quirks.  
 > Probably a
 > mutex is appropriate.
 >
 
 Done
 
 > You can improve the quirk string to numerical conversion code by  
 > using binary
 > search.
 >
 
 
 Wrote code to use bsearch and then backed it out. Value of binary  
 searching on lists less than 20 items is minimal and required  
 additional memory copy operation and release, due to non-destructive  
 parsing technique used on environment variables.
 
 > Else your patch looks fine to me.
 
 
 
 --Apple-Mail-2-311066617
 Content-Disposition: attachment;
 	filename=usb.diff
 Content-Type: application/octet-stream;
 	x-unix-mode=0644;
 	name="usb.diff"
 Content-Transfer-Encoding: 7bit
 
 diff -ru /usr/src/share/man/man4/usb.4 /scratch/src/share/man/man4/usb.4
 --- /usr/src/share/man/man4/usb.4	2008-04-11 22:43:31.000000000 +1000
 +++ /scratch/src/share/man/man4/usb.4	2008-04-17 08:39:01.000000000 +1000
 @@ -288,6 +288,66 @@
  .Em DANGEROUS
  and should be used with great care since it
  can destroy the bus integrity.
 +.It Dv USB_SETDYNQUIRKS
 +This command will cause the dynamic quirks table to be rebuilt from the
 +contents of the kernel environment. Environment strings of the form
 +.Pp
 +.Ic usb.quirk.N="VENDOR PRODUCT REVISION FLAGS"
 +.Pp
 +where 
 +.Ic N
 +is a number between 0 and 9 and quirks must be numbered contiguously;
 +.Ic VENDOR PRODUCT 
 +and 
 +.Ic REVISION
 +are constants that identify the device (the value 0xffff for
 +.Ic REVISION
 +denotes all revisions); and 
 +.Ic FLAGS
 +is any combination of 
 +.Bl -tag -width "UOPEN_CLEARSTALL" -compact -offset indent
 +.It USWAP_UNICODE
 +has some Unicode strings swapped.
 +.It UMS_REVZ
 +mouse has Z-axis reversed
 +.It UNO_STRINGS
 +string descriptors are broken.
 +.It UBAD_ADC
 +bad audio spec version number.
 +.It UBUS_POWERED
 +device is bus powered, despite claim
 +.It UBAD_AUDIO
 +device claims audio class, but isn't
 +.It USPUR_BUT_UP
 +spurious mouse button up events
 +.It UAU_NO_XU
 +audio device has broken extension unit
 +.It UPOWER_CLAIM
 +hub lies about power status
 +.It UAU_NO_FRAC
 +don't adjust for fractional samples
 +.It UAU_INP_ASYNC
 +input is async despite claim of adaptive
 +.It UBROKEN_BIDIR
 +printer has broken bidir mode
 +.It UOPEN_CLEARSTALL
 +device needs clear endpoint stall
 +.It UHID_IGNORE
 +device should be ignored by hid class
 +.It UKBD_IGNORE
 +device should be ignored by both kbd and hid class
 +.It UMS_BAD_CLASS
 +doesn't identify properly
 +.It UMS_LEADING_BYTE
 +mouse sends an unknown leading byte.
 +.El
 +separated by "|" characters. These lines set the quirks for each device 
 +identified.
 +.Pp
 +The dynamic quirks table is designed to supplement the quirks table built
 +in to the kernel. It is of particular use to developers working with devices
 +that inappropriately share vendor, product and revision information and hence
 +cannot be correctly added in to the kernel's quirks table.
  .El
  .Pp
  The include file
 diff -ru /usr/src/sys/dev/usb/usb.c /scratch/src/sys/dev/usb/usb.c
 --- /usr/src/sys/dev/usb/usb.c	2008-04-11 22:43:56.000000000 +1000
 +++ /scratch/src/sys/dev/usb/usb.c	2008-04-16 23:23:55.000000000 +1000
 @@ -668,6 +668,10 @@
  		*(struct usb_device_stats *)data = sc->sc_bus->stats;
  		break;
  
 +	case USB_SETDYNQUIRKS:
 +    		usbd_populate_dynamic_quirks();
 +		break;
 +
  	default:
  		return (EINVAL);
  	}
 diff -ru /usr/src/sys/dev/usb/usb.h /scratch/src/sys/dev/usb/usb.h
 --- /usr/src/sys/dev/usb/usb.h	2008-04-11 22:43:56.000000000 +1000
 +++ /scratch/src/sys/dev/usb/usb.h	2008-04-16 23:22:34.000000000 +1000
 @@ -673,6 +673,7 @@
  #define USB_DISCOVER		_IO  ('U', 3)
  #define USB_DEVICEINFO		_IOWR('U', 4, struct usb_device_info)
  #define USB_DEVICESTATS		_IOR ('U', 5, struct usb_device_stats)
 +#define USB_SETDYNQUIRKS	_IO  ('U', 6)
  
  /* Generic HID device */
  #define USB_GET_REPORT_DESC	_IOR ('U', 21, struct usb_ctl_report_desc)
 diff -ru /usr/src/sys/dev/usb/usb_quirks.c /scratch/src/sys/dev/usb/usb_quirks.c
 --- /usr/src/sys/dev/usb/usb_quirks.c	2008-04-11 22:43:56.000000000 +1000
 +++ /scratch/src/sys/dev/usb/usb_quirks.c	2008-04-17 12:02:12.000000000 +1000
 @@ -42,8 +42,13 @@
  
  #include <sys/param.h>
  #include <sys/systm.h>
 +#include <sys/kernel.h>
 +#include <sys/lock.h>
 +#include <sys/mutex.h>
 +#include <sys/ctype.h>
  
  #include <dev/usb/usb.h>
 +#include <sys/kenv.h>
  
  #include "usbdevs.h"
  #include <dev/usb/usb_quirks.h>
 @@ -54,12 +59,14 @@
  
  #define ANY 0xffff
  
 -static const struct usbd_quirk_entry {
 +struct usbd_quirk_entry {
  	u_int16_t idVendor;
  	u_int16_t idProduct;
  	u_int16_t bcdDevice;
  	struct usbd_quirks quirks;
 -} usb_quirks[] = {
 +};
 +
 +static struct usbd_quirk_entry usb_quirks[] = {
   { USB_VENDOR_INSIDEOUT, USB_PRODUCT_INSIDEOUT_EDGEPORT4,
     						    0x094, { UQ_SWAP_UNICODE}},
   { USB_VENDOR_DALLAS, USB_PRODUCT_DALLAS_J6502,	    0x0a2, { UQ_BAD_ADC }},
 @@ -117,15 +124,24 @@
  
  const struct usbd_quirks usbd_no_quirk = { 0 };
  
 -const struct usbd_quirks *
 -usbd_find_quirk(usb_device_descriptor_t *d)
 +#define MAX_DYNAMIC_USB_QUIRKS 10
 +#define ENVNAMEROOT "usb.quirk."
 +#define SEPCHAR "|"
 +
 +static struct usbd_quirk_entry dynamic_usb_quirks[MAX_DYNAMIC_USB_QUIRKS];
 +
 +static struct usbd_quirks *
 +usbd_search_quirk(struct usbd_quirk_entry *quirks, usb_device_descriptor_t *d);
 +
 +static struct usbd_quirks *
 +usbd_search_quirk(struct usbd_quirk_entry *quirks, usb_device_descriptor_t *d)
  {
 -	const struct usbd_quirk_entry *t;
 +	struct usbd_quirk_entry *t;
  	u_int16_t vendor = UGETW(d->idVendor);
  	u_int16_t product = UGETW(d->idProduct);
  	u_int16_t revision = UGETW(d->bcdDevice);
  
 -	for (t = usb_quirks; t->idVendor != 0; t++) {
 +	for (t = quirks; t->idVendor != 0; t++) {
  		if (t->idVendor  == vendor &&
  		    t->idProduct == product &&
  		    (t->bcdDevice == ANY || t->bcdDevice == revision))
 @@ -139,3 +155,150 @@
  #endif
  	return (&t->quirks);
  }
 +
 +struct mtx dyn_mtx;
 +
 +const struct usbd_quirks *
 +usbd_find_quirk(usb_device_descriptor_t *d)
 +{
 +	struct usbd_quirks *quirks;
 +	/* although it should NEVER happen that this routine is called */
 +	/* before the populate routine has been called, we check that */
 +	/* the initialisation of the mutex has occured */
 +	if (mtx_initialized(&dyn_mtx))
 +	{
 +		/* check the dynamic quirks list first for local entries */
 +		mtx_lock(&dyn_mtx);
 +		quirks = usbd_search_quirk((struct usbd_quirk_entry *) dynamic_usb_quirks, d);
 +		mtx_unlock(&dyn_mtx);
 +		if (quirks->uq_flags != 0)
 +			return quirks;
 +	}
 +	/* check the compiled in quirks list if dynamic entry not set */
 +	return(usbd_search_quirk((struct usbd_quirk_entry *) usb_quirks, d));
 +}
 +
 +void usbd_populate_dynamic_quirks()
 +{
 +	/* the size of envkey must exceed the length of ENVNAMEROOT */
 +	/* and the maximum number of digits in MAX_DYNAMIC_USB_QUIRKS plus 1 */
 +	/* as the environment size is limitted to 512 entries and a maximum */
 +	/* of 128 usb devices are supported 3 digits is appropriate for 
 +	all valid values of MAX_DYNAMIC_USB_QUIRKS */
 +	const int envkeysz = strlen(ENVNAMEROOT)+3+1;
 +	char envkey[envkeysz];
 +	int i;
 +	char *env;
 +	char *pt;
 +	char *e;
 +	int n;
 +	/* this routine should be first called well before any USB */
 +	/* busses or devices are detected */
 +	if (!mtx_initialized(&dyn_mtx))
 +		mtx_init(&dyn_mtx, "usb_dynquirks", NULL, MTX_DEF);
 +	mtx_lock(&dyn_mtx);
 +	for (i=0; i<MAX_DYNAMIC_USB_QUIRKS; i++)
 +	{
 +		dynamic_usb_quirks[i].quirks.uq_flags = 0;
 +		dynamic_usb_quirks[i].idVendor = 0;
 +		dynamic_usb_quirks[i].idProduct = 0;
 +		dynamic_usb_quirks[i].bcdDevice = 0;
 +		snprintf(envkey,envkeysz,"%s%d",ENVNAMEROOT,i);
 +		if (testenv(envkey))
 +		{
 +#ifdef USB_DEBUG
 +			printf("usbd config %s\n", envkey);
 +#endif
 +			env = getenv(envkey);
 +			dynamic_usb_quirks[i].idVendor = strtoul(env, &pt, 0);
 +			dynamic_usb_quirks[i].idProduct = strtoul(pt, &pt, 0);
 +			dynamic_usb_quirks[i].bcdDevice = strtoul(pt, &pt, 0);
 +			/* skip anything which isn't a flag */
 +			while (*pt && !isalpha(*pt)) pt++;
 +			/* read in flags */
 +			while (*pt)
 +			{
 +				e = strstr(pt,SEPCHAR);
 +				if (!e)
 +				{
 +					n = strlen(pt);
 +				}
 +				else
 +				{
 +					n = e - pt;
 +				}
 +				if (!strncmp("USWAP_UNICODE", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_SWAP_UNICODE;
 +				if (!strncmp("UMS_REVZ", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_MS_REVZ;
 +				if (!strncmp("UNO_STRINGS", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_NO_STRINGS;
 +				if (!strncmp("UBAD_ADC", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_BAD_ADC;
 +				if (!strncmp("UBUS_POWERED", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_BUS_POWERED;
 +				if (!strncmp("UBAD_AUDIO", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_BAD_AUDIO;
 +				if (!strncmp("USPUR_BUT_UP", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_SPUR_BUT_UP;
 +				if (!strncmp("UAU_NO_XU", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_AU_NO_XU;
 +				if (!strncmp("UPOWER_CLAIM", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_POWER_CLAIM;
 +				if (!strncmp("UAU_NO_FRAC", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_AU_NO_FRAC;
 +				if (!strncmp("UAU_INP_ASYNC", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_AU_INP_ASYNC;
 +				if (!strncmp("UBROKEN_BIDIR", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_BROKEN_BIDIR;
 +				if (!strncmp("UOPEN_CLEARSTALL", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_OPEN_CLEARSTALL;
 +				if (!strncmp("UHID_IGNORE", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_HID_IGNORE;
 +				if (!strncmp("UKBD_IGNORE", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_KBD_IGNORE;
 +				if (!strncmp("UMS_BAD_CLASS", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_MS_BAD_CLASS;
 +				if (!strncmp("UMS_LEADING_BYTE", pt, n))
 +					dynamic_usb_quirks[i].quirks.uq_flags |= UQ_MS_LEADING_BYTE;
 +				pt += n;
 +				pt += strspn(pt, SEPCHAR);
 +			}
 +#ifdef USB_DEBUG
 +			printf("usbd quirk %d %x %x %x %x\n", 
 +				dynamic_usb_quirks[i].quirks.uq_flags,
 +				dynamic_usb_quirks[i].idVendor,
 +				dynamic_usb_quirks[i].idProduct,
 +				dynamic_usb_quirks[i].bcdDevice,
 +				dynamic_usb_quirks[i].quirks.uq_flags
 +				);
 +#endif
 +			freeenv(env);
 +		}
 +		else
 +		{
 +			break;
 +		}
 +	}
 +	for (; i<MAX_DYNAMIC_USB_QUIRKS; i++)
 +	{
 +#ifdef USB_DEBUG
 +		printf("usbd clear dynamic quirk %d\n", i);
 +#endif
 +		dynamic_usb_quirks[i].quirks.uq_flags = 0;
 +		dynamic_usb_quirks[i].idVendor = 0;
 +		dynamic_usb_quirks[i].idProduct = 0;
 +		dynamic_usb_quirks[i].bcdDevice = 0;
 +		snprintf(envkey,envkeysz,"%s%d",ENVNAMEROOT,i);
 +		if (testenv(envkey))
 +		{
 +#ifdef USB_DEBUG
 +			printf("usbd key %s invalid as earlier entry missing\n", envkey);
 +#endif
 +		}
 +	}
 +	mtx_unlock(&dyn_mtx);
 +}
 +
 +SYSINIT(usbd_populate_dynamic_quirks, SI_SUB_KLD, SI_ORDER_MIDDLE,
 +    usbd_populate_dynamic_quirks, NULL);
 diff -ru /usr/src/sys/dev/usb/usb_quirks.h /scratch/src/sys/dev/usb/usb_quirks.h
 --- /usr/src/sys/dev/usb/usb_quirks.h	2008-04-11 22:43:56.000000000 +1000
 +++ /scratch/src/sys/dev/usb/usb_quirks.h	2008-04-16 11:09:35.000000000 +1000
 @@ -62,3 +62,5 @@
  extern const struct usbd_quirks usbd_no_quirk;
  
  const struct usbd_quirks *usbd_find_quirk(usb_device_descriptor_t *);
 +
 +void usbd_populate_dynamic_quirks(void);
 diff -rNu /usr/src/usr.bin/usbquirksload/Makefile /scratch/src/usr.bin/usbquirksload/Makefile
 --- /usr/src/usr.bin/usbquirksload/Makefile	1970-01-01 10:00:00.000000000 +1000
 +++ /scratch/src/usr.bin/usbquirksload/Makefile	2008-04-17 10:28:40.000000000 +1000
 @@ -0,0 +1,7 @@
 +#	$NetBSD: Makefile,v 1.4 1999/05/11 21:02:25 augustss Exp $
 +# $FreeBSD: src/usr.bin/usbhidctl/Makefile,v 1.4 2002/04/15 09:33:34 ru Exp $
 +
 +PROG=	usbquirksload
 +SRCS=	usbquirksload.c
 +
 +.include <bsd.prog.mk>
 diff -rNu /usr/src/usr.bin/usbquirksload/usbquirksload.1 /scratch/src/usr.bin/usbquirksload/usbquirksload.1
 --- /usr/src/usr.bin/usbquirksload/usbquirksload.1	1970-01-01 10:00:00.000000000 +1000
 +++ /scratch/src/usr.bin/usbquirksload/usbquirksload.1	2008-04-17 10:44:32.000000000 +1000
 @@ -0,0 +1,50 @@
 +.\" $FreeBSD$
 +.\"
 +.\" Copyright (c) 2008 The FreeBSD Project.
 +.\" All rights reserved.
 +.\"
 +.\" This code is derived from software contributed to the FreeBSD Project
 +.\" by Maurice Castro.
 +.\"
 +.\" 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.
 +.\"
 +.\" THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT 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.
 +.\"
 +.Dd April 17, 2008
 +.Dt USBQUIRKSLOAD 1
 +.Os
 +.Sh NAME
 +.Nm usbquirksload
 +.Nd Load usb dynamic quirks table from environment data
 +.Sh SYNOPSIS
 +.Nm
 +.Sh DESCRIPTION
 +The
 +.Nm
 +utility loads the kernel's dynamic quirks table from strings contained in
 +the kernel environment.
 +.Sh SEE ALSO
 +.Xr usb 4
 +.Xr kenv 1
 +.Sh HISTORY
 +The
 +.Nm
 +command appeared in
 +.Fx 7.0 .
 diff -rNu /usr/src/usr.bin/usbquirksload/usbquirksload.c /scratch/src/usr.bin/usbquirksload/usbquirksload.c
 --- /usr/src/usr.bin/usbquirksload/usbquirksload.c	1970-01-01 10:00:00.000000000 +1000
 +++ /scratch/src/usr.bin/usbquirksload/usbquirksload.c	2008-04-17 10:40:13.000000000 +1000
 @@ -0,0 +1,54 @@
 +/*
 + * Copyright (c) 2008 The FreeBSD Project.
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to the FreeBSD Project
 + * by Maurice Castro.
 + *
 + * 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.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT 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.
 + * $FreeBSD$ 
 + */
 +
 +#include <stdio.h>
 +#include <stdlib.h>
 +#include <dev/usb/usb.h>
 +#include <sys/ioctl.h>
 +#include <fcntl.h>
 +
 +int main(int argc, char *argv[])
 +{
 +	int d, r;
 +	d = open("/dev/usb0", O_RDONLY);
 +	if (d == -1)
 +	{
 +		perror(argv[0]);
 +		exit(1);
 +	}
 +     	r = ioctl(d, USB_SETDYNQUIRKS, 0);
 +	if (r == -1)
 +	{
 +		perror(argv[0]);
 +		exit(2);
 +	}
 +	close(d); 
 +	exit(0);
 +}
 
 --Apple-Mail-2-311066617
 Content-Type: text/plain;
 	charset=US-ASCII;
 	format=flowed
 Content-Transfer-Encoding: 7bit
 
 
 
 
 
 --Apple-Mail-2-311066617--


More information about the freebsd-usb mailing list