xref: /illumos-gate/usr/src/lib/cfgadm_plugins/usb/common/cfga_configfile.c (revision 8a2b682e57a046b828f37bcde1776f131ef4629f)
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 
44 typedef 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 
52 typedef enum {
53 	USB_SELECTION, USB_VENDOR, USB_PRODUCT, USB_CFGNDX, USB_SRNO,
54 	USB_PATH, USB_DRIVER, USB_NONE
55 } config_field_t;
56 
57 typedef struct usbcfg_var {
58 	const char *name;
59 	config_field_t field;
60 } usbcfg_var_t;
61 
62 static 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 
73 typedef 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 
93 static char	usbconf_file[] = USBCONF_FILE;
94 static int	linenum = 1;
95 static int	cntr = 0;
96 static int	frec = 0;
97 static int	brec = 0;
98 static int	btoken = 0;
99 mutex_t		file_lock = DEFAULTMUTEX;
100 
101 
102 /*
103  * prototypes
104  */
105 static int	get_string(u_longlong_t *llptr, char *tchar);
106 static 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  */
114 static int
115 get_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  */
171 static int
172 getvalue(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  */
250 static config_field_t
251 usb_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 */
269 static token_t
270 lex(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);
408 digit:
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  */
451 static void
452 find_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  */
468 static token_t
469 usb_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  */
641 static boolean_t
642 usb_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  */
706 static void
707 usb_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 
722 int
723 add_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 
951 exit:
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