1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#include "cfga_usb.h"
28
29
30#define	MAXLINESIZE	512
31#define	FE_BUFLEN 256
32
33#define	isunary(ch)	((ch) == '~' || (ch) == '-')
34#define	iswhite(ch)	((ch) == ' ' || (ch) == '\t')
35#define	isnewline(ch)	((ch) == '\n' || (ch) == '\r' || (ch) == '\f')
36#define	isalphanum(ch)	(isalpha(ch) || isdigit(ch))
37#define	isnamechar(ch)	(isalphanum(ch) || (ch) == '_' || (ch) == '-')
38
39#define	MAX(a, b)	((a) < (b) ? (b) : (a))
40#define	GETC(a, cntr)	a[cntr++]
41#define	UNGETC(cntr)	cntr--
42
43
44typedef struct usb_configrec {
45	char    *selection;
46	int	idVendor, idProduct, cfgndx;
47	char    *serialno;
48	char    *pathname;
49	char    *driver;
50} usb_configrec_t;
51
52typedef enum {
53	USB_SELECTION, USB_VENDOR, USB_PRODUCT, USB_CFGNDX, USB_SRNO,
54	USB_PATH, USB_DRIVER, USB_NONE
55} config_field_t;
56
57typedef struct usbcfg_var {
58	const char *name;
59	config_field_t field;
60} usbcfg_var_t;
61
62static usbcfg_var_t usbcfg_varlist[] = {
63	{ "selection",	USB_SELECTION },
64	{ "idVendor",	USB_VENDOR },
65	{ "idProduct",	USB_PRODUCT },
66	{ "cfgndx",	USB_CFGNDX },
67	{ "srno",	USB_SRNO },
68	{ "pathname",	USB_PATH },
69	{ "driver",	USB_DRIVER },
70	{ NULL,		USB_NONE }
71};
72
73typedef enum {
74	EQUALS,
75	AMPERSAND,
76	BIT_OR,
77	STAR,
78	POUND,
79	COLON,
80	SEMICOLON,
81	COMMA,
82	SLASH,
83	WHITE_SPACE,
84	NEWLINE,
85	E_O_F,
86	STRING,
87	HEXVAL,
88	DECVAL,
89	NAME
90} token_t;
91
92
93static char	usbconf_file[] = USBCONF_FILE;
94static int	linenum = 1;
95static int	cntr = 0;
96static int	frec = 0;
97static int	brec = 0;
98static int	btoken = 0;
99mutex_t		file_lock = DEFAULTMUTEX;
100
101
102/*
103 * prototypes
104 */
105static int	get_string(u_longlong_t *llptr, char *tchar);
106static int	getvalue(char *token, u_longlong_t *valuep);
107
108
109/*
110 * The next item on the line is a string value. Allocate memory for
111 * it and copy the string. Return 1, and set arg ptr to newly allocated
112 * and initialized buffer, or NULL if an error occurs.
113 */
114static int
115get_string(u_longlong_t *llptr, char *tchar)
116{
117	register char *cp;
118	register char *start = NULL;
119	register int len = 0;
120
121	len = strlen(tchar);
122	start = tchar;
123	/* copy string */
124	cp = calloc(len + 1, sizeof (char));
125	if (cp == NULL) {
126		*llptr = 0;
127
128		return (0);
129	}
130
131	*llptr = (u_longlong_t)(uintptr_t)cp;
132	for (; len > 0; len--) {
133		/* convert some common escape sequences */
134		if (*start == '\\') {
135			switch (*(start + 1)) {
136			case 't':
137				/* tab */
138				*cp++ = '\t';
139				len--;
140				start += 2;
141				break;
142			case 'n':
143				/* new line */
144				*cp++ = '\n';
145				len--;
146				start += 2;
147				break;
148			case 'b':
149				/* back space */
150				*cp++ = '\b';
151				len--;
152				start += 2;
153				break;
154			default:
155				/* simply copy it */
156				*cp++ = *start++;
157				break;
158			}
159		} else {
160			*cp++ = *start++;
161		}
162	}
163	*cp = '\0';
164	return (1);
165}
166
167
168/*
169 * get a decimal octal or hex number. Handle '~' for one's complement.
170 */
171static int
172getvalue(char *token, u_longlong_t *valuep)
173{
174	register int radix;
175	register u_longlong_t retval = 0;
176	register int onescompl = 0;
177	register int negate = 0;
178	register char c;
179
180	if (*token == '~') {
181		onescompl++; /* perform one's complement on result */
182		token++;
183	} else if (*token == '-') {
184		negate++;
185		token++;
186	}
187	if (*token == '0') {
188		token++;
189		c = *token;
190
191		if (c == '\0') {
192			*valuep = 0;    /* value is 0 */
193			return (0);
194		}
195
196		if (c == 'x' || c == 'X') {
197			radix = 16;
198			token++;
199		} else {
200			radix = 8;
201		}
202	} else {
203		radix = 10;
204	}
205
206	while ((c = *token++)) {
207		switch (radix) {
208		case 8:
209			if (c >= '0' && c <= '7') {
210				c -= '0';
211			} else {
212				return (-1);    /* invalid number */
213			}
214			retval = (retval << 3) + c;
215			break;
216		case 10:
217			if (c >= '0' && c <= '9') {
218				c -= '0';
219			} else {
220				return (-1);    /* invalid number */
221			}
222			retval = (retval * 10) + c;
223			break;
224		case 16:
225			if (c >= 'a' && c <= 'f') {
226				c = c - 'a' + 10;
227			} else if (c >= 'A' && c <= 'F') {
228				c = c - 'A' + 10;
229			} else if (c >= '0' && c <= '9') {
230				c -= '0';
231			} else {
232				return (-1);    /* invalid number */
233			}
234			retval = (retval << 4) + c;
235			break;
236		}
237	}
238	if (onescompl)
239		retval = ~retval;
240	if (negate)
241		retval = -retval;
242	*valuep = retval;
243
244	return (0);
245}
246
247/*
248 * returns the field from the token
249 */
250static config_field_t
251usb_get_var_type(char *str)
252{
253	usbcfg_var_t    *cfgvar;
254
255	cfgvar = &usbcfg_varlist[0];
256	while (cfgvar->field != USB_NONE) {
257		if (strcasecmp(cfgvar->name, str) == 0) {
258			break;
259		} else {
260			cfgvar++;
261		}
262	}
263
264	return (cfgvar->field);
265}
266
267
268/* ARGSUSED */
269static token_t
270lex(char *buf, char *val, char **errmsg)
271{
272	int	ch, oval, badquote;
273	char	*cp;
274	token_t token;
275
276	cp = val;
277	while ((ch = GETC(buf, cntr)) == ' ' || ch == '\t');
278
279	/*
280	 * Note the beginning of a token
281	 */
282	btoken = cntr - 1;
283
284	*cp++ = (char)ch;
285	switch (ch) {
286	case '=':
287		token = EQUALS;
288		break;
289	case '&':
290		token = AMPERSAND;
291		break;
292	case '|':
293		token = BIT_OR;
294		break;
295	case '*':
296		token = STAR;
297		break;
298	case '#':
299		token = POUND;
300		break;
301	case ':':
302		token = COLON;
303		break;
304	case ';':
305		token = SEMICOLON;
306		break;
307	case ',':
308		token = COMMA;
309		break;
310	case '/':
311		token = SLASH;
312		break;
313	case ' ':
314	case '\t':
315	case '\f':
316		while ((ch  = GETC(buf, cntr)) == ' ' ||
317		    ch == '\t' || ch == '\f')
318			*cp++ = (char)ch;
319		(void) UNGETC(cntr);
320		token = WHITE_SPACE;
321		break;
322	case '\n':
323	case '\r':
324		token = NEWLINE;
325		break;
326	case '"':
327		cp--;
328		badquote = 0;
329		while (!badquote && (ch  = GETC(buf, cntr)) != '"') {
330			switch (ch) {
331			case '\n':
332			case -1:
333				(void) snprintf(*errmsg, MAXPATHLEN,
334				    "Missing \"");
335				cp = val;
336				*cp++ = '\n';
337				badquote = 1;
338				/* since we consumed the newline/EOF */
339				(void) UNGETC(cntr);
340				break;
341
342			case '\\':
343				ch = (char)GETC(buf, cntr);
344				if (!isdigit(ch)) {
345					/* escape the character */
346					*cp++ = (char)ch;
347					break;
348				}
349				oval = 0;
350				while (ch >= '0' && ch <= '7') {
351					ch -= '0';
352					oval = (oval << 3) + ch;
353					ch = (char)GETC(buf, cntr);
354				}
355				(void) UNGETC(cntr);
356				/* check for character overflow? */
357				if (oval > 127) {
358					(void) snprintf(*errmsg, MAXPATHLEN,
359					    "Character overflow detected.\n");
360				}
361				*cp++ = (char)oval;
362				break;
363			default:
364				*cp++ = (char)ch;
365				break;
366			}
367		}
368		token = STRING;
369		break;
370
371	default:
372		if (ch == -1) {
373			token = EOF;
374			break;
375		}
376		/*
377		 * detect a lone '-' (including at the end of a line), and
378		 * identify it as a 'name'
379		 */
380		if (ch == '-') {
381			*cp++ = (char)(ch = GETC(buf, cntr));
382			if (iswhite(ch) || (ch == '\n')) {
383				(void) UNGETC(cntr);
384				cp--;
385				token = NAME;
386				break;
387			}
388		} else if (isunary(ch)) {
389			*cp++ = (char)(ch = GETC(buf, cntr));
390		}
391
392		if (isdigit(ch)) {
393			if (ch == '0') {
394				if ((ch = GETC(buf, cntr)) == 'x') {
395					*cp++ = (char)ch;
396					ch = GETC(buf, cntr);
397					while (isxdigit(ch)) {
398						*cp++ = (char)ch;
399						ch = GETC(buf, cntr);
400					}
401					(void) UNGETC(cntr);
402					token = HEXVAL;
403				} else {
404					goto digit;
405				}
406			} else {
407				ch = GETC(buf, cntr);
408digit:
409				while (isdigit(ch)) {
410					*cp++ = (char)ch;
411					ch = GETC(buf, cntr);
412				}
413				(void) UNGETC(cntr);
414				token = DECVAL;
415			}
416		} else if (isalpha(ch) || ch == '\\') {
417			if (ch != '\\') {
418				ch = GETC(buf, cntr);
419			} else {
420				/*
421				 * if the character was a backslash,
422				 * back up so we can overwrite it with
423				 * the next (i.e. escaped) character.
424				 */
425				cp--;
426			}
427
428			while (isnamechar(ch) || ch == '\\') {
429				if (ch == '\\')
430					ch = GETC(buf, cntr);
431				*cp++ = (char)ch;
432				ch = GETC(buf, cntr);
433			}
434			(void) UNGETC(cntr);
435			token = NAME;
436		} else {
437
438			return (-1);
439		}
440		break;
441	}
442	*cp = '\0';
443
444	return (token);
445}
446
447
448/*
449 * Leave NEWLINE as the next character.
450 */
451static void
452find_eol(char *buf)
453{
454	register int ch;
455
456	while ((ch = GETC(buf, cntr)) != -1) {
457		if (isnewline(ch)) {
458			(void) UNGETC(cntr);
459			break;
460		}
461	}
462}
463
464
465/*
466 * Fetch one record from the USBCONF_FILE
467 */
468static token_t
469usb_get_conf_rec(char *buf, usb_configrec_t **rec, char **errmsg)
470{
471	token_t token;
472	char tokval[MAXLINESIZE];
473	usb_configrec_t *user_rec;
474	config_field_t  cfgvar;
475	u_longlong_t    llptr;
476	u_longlong_t    value;
477	boolean_t	sor = B_TRUE;
478
479	enum {
480		USB_NEWVAR, USB_CONFIG_VAR, USB_VAR_EQUAL, USB_VAR_VALUE,
481		USB_ERROR
482	} parse_state = USB_NEWVAR;
483
484	DPRINTF("usb_get_conf_rec:\n");
485
486	user_rec = (usb_configrec_t *)calloc(1, sizeof (usb_configrec_t));
487	if (user_rec == (usb_configrec_t *)NULL) {
488		return (0);
489	}
490
491	user_rec->idVendor = user_rec->idProduct = user_rec->cfgndx = -1;
492
493	token = lex(buf, tokval, errmsg);
494	while ((token != EOF) && (token != SEMICOLON)) {
495		switch (token) {
496		case STAR:
497		case POUND:
498			/* skip comments */
499			find_eol(buf);
500			break;
501		case NEWLINE:
502			linenum++;
503			break;
504		case NAME:
505		case STRING:
506			switch (parse_state) {
507			case USB_NEWVAR:
508				cfgvar = usb_get_var_type(tokval);
509				if (cfgvar == USB_NONE) {
510					parse_state = USB_ERROR;
511					(void) snprintf(*errmsg, MAXPATHLEN,
512					    "Syntax Error: Invalid field %s",
513					    tokval);
514				} else {
515					/*
516					 * Note the beginning of a record
517					 */
518					if (sor) {
519						brec = btoken;
520						if (frec == 0) frec = brec;
521						sor = B_FALSE;
522					}
523					parse_state = USB_CONFIG_VAR;
524				}
525				break;
526			case USB_VAR_VALUE:
527				if ((cfgvar == USB_VENDOR) ||
528				    (cfgvar == USB_PRODUCT) ||
529				    (cfgvar == USB_CFGNDX)) {
530					parse_state = USB_ERROR;
531					(void) snprintf(*errmsg, MAXPATHLEN,
532					    "Syntax Error: Invalid value %s "
533					    "for field: %s\n", tokval,
534					    usbcfg_varlist[cfgvar].name);
535				} else if (get_string(&llptr, tokval)) {
536					switch (cfgvar) {
537					case USB_SELECTION:
538						user_rec->selection =
539						    (char *)(uintptr_t)llptr;
540						parse_state = USB_NEWVAR;
541						break;
542					case USB_SRNO:
543						user_rec->serialno =
544						    (char *)(uintptr_t)llptr;
545						parse_state = USB_NEWVAR;
546						break;
547					case USB_PATH:
548						user_rec->pathname =
549						    (char *)(uintptr_t)llptr;
550						parse_state = USB_NEWVAR;
551						break;
552					case USB_DRIVER:
553						user_rec->driver =
554						    (char *)(uintptr_t)llptr;
555						parse_state = USB_NEWVAR;
556						break;
557					default:
558						parse_state = USB_ERROR;
559						free((void *)(uintptr_t)llptr);
560					}
561				} else {
562					parse_state = USB_ERROR;
563					(void) snprintf(*errmsg, MAXPATHLEN,
564					    "Syntax Error: Invalid value %s "
565					    "for field: %s\n", tokval,
566					    usbcfg_varlist[cfgvar].name);
567				}
568				break;
569			case USB_ERROR:
570				/* just skip */
571				break;
572			default:
573				parse_state = USB_ERROR;
574				(void) snprintf(*errmsg, MAXPATHLEN,
575				    "Syntax Error: at %s", tokval);
576				break;
577			}
578			break;
579		case EQUALS:
580			if (parse_state == USB_CONFIG_VAR) {
581				if (cfgvar == USB_NONE) {
582					parse_state = USB_ERROR;
583					(void) snprintf(*errmsg, MAXPATHLEN,
584					    "Syntax Error: unexpected '='");
585				} else {
586					parse_state = USB_VAR_VALUE;
587				}
588			} else if (parse_state != USB_ERROR) {
589				(void) snprintf(*errmsg, MAXPATHLEN,
590				    "Syntax Error: unexpected '='");
591				parse_state = USB_ERROR;
592			}
593			break;
594		case HEXVAL:
595		case DECVAL:
596			if ((parse_state == USB_VAR_VALUE) && (cfgvar !=
597			    USB_NONE)) {
598				(void) getvalue(tokval, &value);
599				switch (cfgvar) {
600				case USB_VENDOR:
601					user_rec->idVendor = (int)value;
602					parse_state = USB_NEWVAR;
603					break;
604				case USB_PRODUCT:
605					user_rec->idProduct = (int)value;
606					parse_state = USB_NEWVAR;
607					break;
608				case USB_CFGNDX:
609					user_rec->cfgndx = (int)value;
610					parse_state = USB_NEWVAR;
611					break;
612				default:
613					(void) snprintf(*errmsg, MAXPATHLEN,
614					    "Syntax Error: Invalid value for "
615					    "%s", usbcfg_varlist[cfgvar].name);
616				}
617			} else if (parse_state != USB_ERROR) {
618				parse_state = USB_ERROR;
619				(void) snprintf(*errmsg, MAXPATHLEN,
620				    "Syntax Error: unexpected hex/decimal: %s",
621				    tokval);
622			}
623			break;
624		default:
625			(void) snprintf(*errmsg, MAXPATHLEN,
626			    "Syntax Error: at: %s", tokval);
627			parse_state = USB_ERROR;
628			break;
629		}
630		token = lex(buf, tokval, errmsg);
631	}
632	*rec = user_rec;
633
634	return (token);
635}
636
637
638/*
639 * Here we compare the two records and determine if they are the same
640 */
641static boolean_t
642usb_cmp_rec(usb_configrec_t *cfg_rec, usb_configrec_t *user_rec)
643{
644	char		*ustr, *cstr;
645	boolean_t	srno = B_FALSE, path = B_FALSE;
646
647	DPRINTF("usb_cmp_rec:\n");
648
649	if ((cfg_rec->idVendor == user_rec->idVendor) &&
650	    (cfg_rec->idProduct == user_rec->idProduct)) {
651		if (user_rec->serialno) {
652			if (cfg_rec->serialno) {
653				srno = (strcmp(cfg_rec->serialno,
654				    user_rec->serialno) == 0);
655			} else {
656
657				return (B_FALSE);
658			}
659
660		} else if (user_rec->pathname) {
661			if (cfg_rec->pathname) {
662				/*
663				 * Comparing on this is tricky. At this point
664				 * hubd knows: ../hubd@P/device@P while user
665				 * will specify ..../hubd@P/keyboard@P
666				 * First compare till .../hubd@P
667				 * Second compare is just P in "device@P"
668				 *
669				 * XXX: note that we assume P as one character
670				 * as there are no 2 digit hubs in the market.
671				 */
672				ustr = strrchr(user_rec->pathname, '/');
673				cstr = strrchr(cfg_rec->pathname, '/');
674				path = (strncmp(cfg_rec->pathname,
675				    user_rec->pathname,
676				    MAX(ustr - user_rec->pathname,
677				    cstr - cfg_rec->pathname)) == 0);
678				path = path && (*(user_rec->pathname +
679				    strlen(user_rec->pathname) -1) ==
680					*(cfg_rec->pathname +
681					strlen(cfg_rec->pathname) - 1));
682			} else {
683
684				return (B_FALSE);
685			}
686
687		} else if (cfg_rec->serialno || cfg_rec->pathname) {
688
689			return (B_FALSE);
690		} else {
691
692			return (B_TRUE);
693		}
694
695		return (srno || path);
696	} else {
697
698		return (B_FALSE);
699	}
700}
701
702
703/*
704 * free the record allocated in usb_get_conf_rec
705 */
706static void
707usb_free_rec(usb_configrec_t *rec)
708{
709	if (rec == (usb_configrec_t *)NULL) {
710
711		return;
712	}
713
714	free(rec->selection);
715	free(rec->serialno);
716	free(rec->pathname);
717	free(rec->driver);
718	free(rec);
719}
720
721
722int
723add_entry(char *selection, int vid, int pid, int cfgndx, char *srno,
724    char *path, char *driver, char **errmsg)
725{
726	int		file;
727	int		rval = CFGA_USB_OK;
728	char		*buf = (char *)NULL;
729	char		str[MAXLINESIZE];
730	token_t		token = NEWLINE;
731	boolean_t	found = B_FALSE;
732	struct stat	st;
733	usb_configrec_t cfgrec, *user_rec = NULL;
734
735	DPRINTF("add_entry: driver=%s, path=%s\n",
736	    driver ? driver : "", path ? path : "");
737
738	if (*errmsg == (char *)NULL) {
739		if ((*errmsg = calloc(MAXPATHLEN, 1)) == (char *)NULL) {
740
741			return (CFGA_USB_CONFIG_FILE);
742		}
743	}
744
745	(void) mutex_lock(&file_lock);
746
747	/* Initialize the cfgrec */
748	cfgrec.selection = selection;
749	cfgrec.idVendor = vid;
750	cfgrec.idProduct = pid;
751	cfgrec.cfgndx = cfgndx;
752	cfgrec.serialno = srno;
753	cfgrec.pathname = path;
754	cfgrec.driver = driver;
755
756	/* open config_map.conf file */
757	file = open(usbconf_file, O_RDWR, 0666);
758	if (file == -1) {
759		(void) snprintf(*errmsg, MAXPATHLEN,
760		    "failed to open config file\n");
761		(void) mutex_unlock(&file_lock);
762
763		return (CFGA_USB_CONFIG_FILE);
764	}
765
766	if (lockf(file, F_TLOCK, 0) == -1) {
767		(void) snprintf(*errmsg, MAXPATHLEN,
768		    "failed to lock config file\n");
769		close(file);
770		(void) mutex_unlock(&file_lock);
771
772		return (CFGA_USB_LOCK_FILE);
773	}
774
775	/*
776	 * These variables need to be reinitialized here as they may
777	 * have been modified by a previous thread that called this
778	 * function
779	 */
780	linenum = 1;
781	cntr = 0;
782	frec = 0;
783	brec = 0;
784	btoken = 0;
785
786	if (fstat(file, &st) != 0) {
787		DPRINTF("add_entry: failed to fstat config file\n");
788		rval = CFGA_USB_CONFIG_FILE;
789		goto exit;
790	}
791
792	if ((buf = (char *)malloc(st.st_size)) == NULL) {
793		DPRINTF("add_entry: failed to fstat config file\n");
794		rval = CFGA_USB_ALLOC_FAIL;
795		goto exit;
796	}
797
798	if (st.st_size != read(file, buf, st.st_size)) {
799		DPRINTF("add_entry: failed to read config file\n");
800		rval = CFGA_USB_CONFIG_FILE;
801		goto exit;
802	}
803
804	/* set up for reading the file */
805
806	while ((token != EOF) && !found) {
807		if (user_rec) {
808			usb_free_rec(user_rec);
809			user_rec = NULL;
810		}
811		token = usb_get_conf_rec(buf, &user_rec, errmsg);
812		found = usb_cmp_rec(&cfgrec, user_rec);
813		DPRINTF("add_entry: token=%x, found=%x\n", token, found);
814	}
815
816	bzero(str, MAXLINESIZE);
817
818	if (found) {
819		DPRINTF("FOUND\n");
820		(void) snprintf(str, MAXLINESIZE, "selection=%s idVendor=0x%x "
821		    "idProduct=0x%x ",
822		    (cfgrec.selection) ? cfgrec.selection : user_rec->selection,
823		    user_rec->idVendor, user_rec->idProduct);
824
825		if ((user_rec->cfgndx != -1) || (cfgrec.cfgndx != -1)) {
826			(void) snprintf(&str[strlen(str)], MAXLINESIZE,
827			    "cfgndx=0x%x ", (cfgrec.cfgndx != -1) ?
828			    cfgrec.cfgndx : user_rec->cfgndx);
829		}
830
831		if (user_rec->serialno) {
832			(void) snprintf(&str[strlen(str)],  MAXLINESIZE,
833			    "srno=\"%s\" ", user_rec->serialno);
834		}
835
836		if (user_rec->pathname) {
837			(void) snprintf(&str[strlen(str)],  MAXLINESIZE,
838			    "pathname=\"%s\" ", user_rec->pathname);
839		}
840
841		if (user_rec->driver) {
842			(void) snprintf(&str[strlen(str)],  MAXLINESIZE,
843			    "driver=\"%s\" ", user_rec->driver);
844		} else if (cfgrec.driver != NULL) {
845			if (strlen(cfgrec.driver)) {
846				(void) snprintf(&str[strlen(str)],  MAXLINESIZE,
847				    "driver=\"%s\" ", cfgrec.driver);
848			}
849		}
850
851		(void) strlcat(str, ";", sizeof (str));
852
853		/*
854		 * Seek to the beginning of the record
855		 */
856		if (lseek(file, brec, SEEK_SET) == -1) {
857			DPRINTF("add_entry: failed to lseek config file\n");
858			rval = CFGA_USB_CONFIG_FILE;
859			goto exit;
860		}
861
862		/*
863		 * Write the modified record
864		 */
865		if (write(file, str, strlen(str)) == -1) {
866			DPRINTF("add_entry: failed to write config file\n");
867			rval = CFGA_USB_CONFIG_FILE;
868			goto exit;
869		}
870
871		/*
872		 * Write the rest of the file as it was
873		 */
874		if (write(file, buf+cntr, st.st_size - cntr) == -1) {
875			DPRINTF("add_entry: failed to write config file\n");
876			rval = CFGA_USB_CONFIG_FILE;
877			goto exit;
878		}
879
880	} else {
881		DPRINTF("!FOUND\n");
882		(void) snprintf(str, MAXLINESIZE,
883		    "selection=%s idVendor=0x%x idProduct=0x%x ",
884		    (cfgrec.selection) ? cfgrec.selection : "enable",
885		    cfgrec.idVendor, cfgrec.idProduct);
886
887		if (cfgrec.cfgndx != -1) {
888			(void) snprintf(&str[strlen(str)], MAXLINESIZE,
889			    "cfgndx=0x%x ", cfgrec.cfgndx);
890		}
891
892		if (cfgrec.serialno) {
893			(void) snprintf(&str[strlen(str)], MAXLINESIZE,
894			    "srno=\"%s\" ", cfgrec.serialno);
895		}
896
897		if (cfgrec.pathname != NULL) {
898			(void) snprintf(&str[strlen(str)], MAXLINESIZE,
899			    "pathname=\"%s\" ", cfgrec.pathname);
900		}
901
902		if (cfgrec.driver != NULL) {
903			if (strlen(cfgrec.driver)) {
904				(void) snprintf(&str[strlen(str)], MAXLINESIZE,
905				    "driver=\"%s\" ", cfgrec.driver);
906			}
907		}
908
909		(void) strlcat(str, ";\n", sizeof (str));
910
911		/*
912		 * Incase this is the first entry, add it after the comments
913		 */
914		if (frec == 0) {
915			frec = st.st_size;
916		}
917
918		/*
919		 * Go to the beginning of the records
920		 */
921		if (lseek(file, frec, SEEK_SET) == -1) {
922			DPRINTF("add_entry: failed to lseek config file\n");
923			rval = CFGA_USB_CONFIG_FILE;
924			goto exit;
925		}
926
927		/*
928		 * Add the entry
929		 */
930		if (write(file, str, strlen(str)) == -1) {
931			DPRINTF("add_entry: failed to write config file\n");
932			rval = CFGA_USB_CONFIG_FILE;
933			goto exit;
934		}
935
936		/*
937		 * write the remaining file as it was
938		 */
939		if (write(file, buf+frec, st.st_size - frec) == -1) {
940			DPRINTF("add_entry: failed to write config file\n");
941			rval = CFGA_USB_CONFIG_FILE;
942			goto exit;
943		}
944	}
945
946	/* no error encountered */
947	if (rval == CFGA_USB_OK) {
948		free(errmsg);
949	}
950
951exit:
952	if (buf != NULL) {
953		free(buf);
954	}
955
956	if (lockf(file, F_ULOCK, 0) == -1) {
957		DPRINTF("add_entry: failed to unlock config file\n");
958
959		rval = CFGA_USB_LOCK_FILE;
960	}
961
962	close(file);
963
964	(void) mutex_unlock(&file_lock);
965
966	return (rval);
967}
968