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