/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include "cfga_usb.h"


#define	MAXLINESIZE	512
#define	FE_BUFLEN 256

#define	isunary(ch)	((ch) == '~' || (ch) == '-')
#define	iswhite(ch)	((ch) == ' ' || (ch) == '\t')
#define	isnewline(ch)	((ch) == '\n' || (ch) == '\r' || (ch) == '\f')
#define	isalphanum(ch)	(isalpha(ch) || isdigit(ch))
#define	isnamechar(ch)	(isalphanum(ch) || (ch) == '_' || (ch) == '-')

#define	MAX(a, b)	((a) < (b) ? (b) : (a))
#define	GETC(a, cntr)	a[cntr++]
#define	UNGETC(cntr)	cntr--


typedef struct usb_configrec {
	char    *selection;
	int	idVendor, idProduct, cfgndx;
	char    *serialno;
	char    *pathname;
	char    *driver;
} usb_configrec_t;

typedef enum {
	USB_SELECTION, USB_VENDOR, USB_PRODUCT, USB_CFGNDX, USB_SRNO,
	USB_PATH, USB_DRIVER, USB_NONE
} config_field_t;

typedef struct usbcfg_var {
	const char *name;
	config_field_t field;
} usbcfg_var_t;

static usbcfg_var_t usbcfg_varlist[] = {
	{ "selection",	USB_SELECTION },
	{ "idVendor",	USB_VENDOR },
	{ "idProduct",	USB_PRODUCT },
	{ "cfgndx",	USB_CFGNDX },
	{ "srno",	USB_SRNO },
	{ "pathname",	USB_PATH },
	{ "driver",	USB_DRIVER },
	{ NULL,		USB_NONE }
};

typedef enum {
	EQUALS,
	AMPERSAND,
	BIT_OR,
	STAR,
	POUND,
	COLON,
	SEMICOLON,
	COMMA,
	SLASH,
	WHITE_SPACE,
	NEWLINE,
	E_O_F,
	STRING,
	HEXVAL,
	DECVAL,
	NAME
} token_t;


static char	usbconf_file[] = USBCONF_FILE;
static int	linenum = 1;
static int	cntr = 0;
static int	frec = 0;
static int	brec = 0;
static int	btoken = 0;
mutex_t		file_lock = DEFAULTMUTEX;


/*
 * prototypes
 */
static int	get_string(u_longlong_t *llptr, char *tchar);
static int	getvalue(char *token, u_longlong_t *valuep);


/*
 * The next item on the line is a string value. Allocate memory for
 * it and copy the string. Return 1, and set arg ptr to newly allocated
 * and initialized buffer, or NULL if an error occurs.
 */
static int
get_string(u_longlong_t *llptr, char *tchar)
{
	register char *cp;
	register char *start = NULL;
	register int len = 0;

	len = strlen(tchar);
	start = tchar;
	/* copy string */
	cp = calloc(len + 1, sizeof (char));
	if (cp == NULL) {
		*llptr = 0;

		return (0);
	}

	*llptr = (u_longlong_t)(uintptr_t)cp;
	for (; len > 0; len--) {
		/* convert some common escape sequences */
		if (*start == '\\') {
			switch (*(start + 1)) {
			case 't':
				/* tab */
				*cp++ = '\t';
				len--;
				start += 2;
				break;
			case 'n':
				/* new line */
				*cp++ = '\n';
				len--;
				start += 2;
				break;
			case 'b':
				/* back space */
				*cp++ = '\b';
				len--;
				start += 2;
				break;
			default:
				/* simply copy it */
				*cp++ = *start++;
				break;
			}
		} else {
			*cp++ = *start++;
		}
	}
	*cp = '\0';
	return (1);
}


/*
 * get a decimal octal or hex number. Handle '~' for one's complement.
 */
static int
getvalue(char *token, u_longlong_t *valuep)
{
	register int radix;
	register u_longlong_t retval = 0;
	register int onescompl = 0;
	register int negate = 0;
	register char c;

	if (*token == '~') {
		onescompl++; /* perform one's complement on result */
		token++;
	} else if (*token == '-') {
		negate++;
		token++;
	}
	if (*token == '0') {
		token++;
		c = *token;

		if (c == '\0') {
			*valuep = 0;    /* value is 0 */
			return (0);
		}

		if (c == 'x' || c == 'X') {
			radix = 16;
			token++;
		} else {
			radix = 8;
		}
	} else {
		radix = 10;
	}

	while ((c = *token++)) {
		switch (radix) {
		case 8:
			if (c >= '0' && c <= '7') {
				c -= '0';
			} else {
				return (-1);    /* invalid number */
			}
			retval = (retval << 3) + c;
			break;
		case 10:
			if (c >= '0' && c <= '9') {
				c -= '0';
			} else {
				return (-1);    /* invalid number */
			}
			retval = (retval * 10) + c;
			break;
		case 16:
			if (c >= 'a' && c <= 'f') {
				c = c - 'a' + 10;
			} else if (c >= 'A' && c <= 'F') {
				c = c - 'A' + 10;
			} else if (c >= '0' && c <= '9') {
				c -= '0';
			} else {
				return (-1);    /* invalid number */
			}
			retval = (retval << 4) + c;
			break;
		}
	}
	if (onescompl)
		retval = ~retval;
	if (negate)
		retval = -retval;
	*valuep = retval;

	return (0);
}

/*
 * returns the field from the token
 */
static config_field_t
usb_get_var_type(char *str)
{
	usbcfg_var_t    *cfgvar;

	cfgvar = &usbcfg_varlist[0];
	while (cfgvar->field != USB_NONE) {
		if (strcasecmp(cfgvar->name, str) == 0) {
			break;
		} else {
			cfgvar++;
		}
	}

	return (cfgvar->field);
}


/* ARGSUSED */
static token_t
lex(char *buf, char *val, char **errmsg)
{
	int	ch, oval, badquote;
	char	*cp;
	token_t token;

	cp = val;
	while ((ch = GETC(buf, cntr)) == ' ' || ch == '\t');

	/*
	 * Note the beginning of a token
	 */
	btoken = cntr - 1;

	*cp++ = (char)ch;
	switch (ch) {
	case '=':
		token = EQUALS;
		break;
	case '&':
		token = AMPERSAND;
		break;
	case '|':
		token = BIT_OR;
		break;
	case '*':
		token = STAR;
		break;
	case '#':
		token = POUND;
		break;
	case ':':
		token = COLON;
		break;
	case ';':
		token = SEMICOLON;
		break;
	case ',':
		token = COMMA;
		break;
	case '/':
		token = SLASH;
		break;
	case ' ':
	case '\t':
	case '\f':
		while ((ch  = GETC(buf, cntr)) == ' ' ||
		    ch == '\t' || ch == '\f')
			*cp++ = (char)ch;
		(void) UNGETC(cntr);
		token = WHITE_SPACE;
		break;
	case '\n':
	case '\r':
		token = NEWLINE;
		break;
	case '"':
		cp--;
		badquote = 0;
		while (!badquote && (ch  = GETC(buf, cntr)) != '"') {
			switch (ch) {
			case '\n':
			case -1:
				(void) snprintf(*errmsg, MAXPATHLEN,
				    "Missing \"");
				cp = val;
				*cp++ = '\n';
				badquote = 1;
				/* since we consumed the newline/EOF */
				(void) UNGETC(cntr);
				break;

			case '\\':
				ch = (char)GETC(buf, cntr);
				if (!isdigit(ch)) {
					/* escape the character */
					*cp++ = (char)ch;
					break;
				}
				oval = 0;
				while (ch >= '0' && ch <= '7') {
					ch -= '0';
					oval = (oval << 3) + ch;
					ch = (char)GETC(buf, cntr);
				}
				(void) UNGETC(cntr);
				/* check for character overflow? */
				if (oval > 127) {
					(void) snprintf(*errmsg, MAXPATHLEN,
					    "Character overflow detected.\n");
				}
				*cp++ = (char)oval;
				break;
			default:
				*cp++ = (char)ch;
				break;
			}
		}
		token = STRING;
		break;

	default:
		if (ch == -1) {
			token = EOF;
			break;
		}
		/*
		 * detect a lone '-' (including at the end of a line), and
		 * identify it as a 'name'
		 */
		if (ch == '-') {
			*cp++ = (char)(ch = GETC(buf, cntr));
			if (iswhite(ch) || (ch == '\n')) {
				(void) UNGETC(cntr);
				cp--;
				token = NAME;
				break;
			}
		} else if (isunary(ch)) {
			*cp++ = (char)(ch = GETC(buf, cntr));
		}

		if (isdigit(ch)) {
			if (ch == '0') {
				if ((ch = GETC(buf, cntr)) == 'x') {
					*cp++ = (char)ch;
					ch = GETC(buf, cntr);
					while (isxdigit(ch)) {
						*cp++ = (char)ch;
						ch = GETC(buf, cntr);
					}
					(void) UNGETC(cntr);
					token = HEXVAL;
				} else {
					goto digit;
				}
			} else {
				ch = GETC(buf, cntr);
digit:
				while (isdigit(ch)) {
					*cp++ = (char)ch;
					ch = GETC(buf, cntr);
				}
				(void) UNGETC(cntr);
				token = DECVAL;
			}
		} else if (isalpha(ch) || ch == '\\') {
			if (ch != '\\') {
				ch = GETC(buf, cntr);
			} else {
				/*
				 * if the character was a backslash,
				 * back up so we can overwrite it with
				 * the next (i.e. escaped) character.
				 */
				cp--;
			}

			while (isnamechar(ch) || ch == '\\') {
				if (ch == '\\')
					ch = GETC(buf, cntr);
				*cp++ = (char)ch;
				ch = GETC(buf, cntr);
			}
			(void) UNGETC(cntr);
			token = NAME;
		} else {

			return (-1);
		}
		break;
	}
	*cp = '\0';

	return (token);
}


/*
 * Leave NEWLINE as the next character.
 */
static void
find_eol(char *buf)
{
	register int ch;

	while ((ch = GETC(buf, cntr)) != -1) {
		if (isnewline(ch)) {
			(void) UNGETC(cntr);
			break;
		}
	}
}


/*
 * Fetch one record from the USBCONF_FILE
 */
static token_t
usb_get_conf_rec(char *buf, usb_configrec_t **rec, char **errmsg)
{
	token_t token;
	char tokval[MAXLINESIZE];
	usb_configrec_t *user_rec;
	config_field_t  cfgvar;
	u_longlong_t    llptr;
	u_longlong_t    value;
	boolean_t	sor = B_TRUE;

	enum {
		USB_NEWVAR, USB_CONFIG_VAR, USB_VAR_EQUAL, USB_VAR_VALUE,
		USB_ERROR
	} parse_state = USB_NEWVAR;

	DPRINTF("usb_get_conf_rec:\n");

	user_rec = (usb_configrec_t *)calloc(1, sizeof (usb_configrec_t));
	if (user_rec == (usb_configrec_t *)NULL) {
		return (0);
	}

	user_rec->idVendor = user_rec->idProduct = user_rec->cfgndx = -1;

	token = lex(buf, tokval, errmsg);
	while ((token != EOF) && (token != SEMICOLON)) {
		switch (token) {
		case STAR:
		case POUND:
			/* skip comments */
			find_eol(buf);
			break;
		case NEWLINE:
			linenum++;
			break;
		case NAME:
		case STRING:
			switch (parse_state) {
			case USB_NEWVAR:
				cfgvar = usb_get_var_type(tokval);
				if (cfgvar == USB_NONE) {
					parse_state = USB_ERROR;
					(void) snprintf(*errmsg, MAXPATHLEN,
					    "Syntax Error: Invalid field %s",
					    tokval);
				} else {
					/*
					 * Note the beginning of a record
					 */
					if (sor) {
						brec = btoken;
						if (frec == 0) frec = brec;
						sor = B_FALSE;
					}
					parse_state = USB_CONFIG_VAR;
				}
				break;
			case USB_VAR_VALUE:
				if ((cfgvar == USB_VENDOR) ||
				    (cfgvar == USB_PRODUCT) ||
				    (cfgvar == USB_CFGNDX)) {
					parse_state = USB_ERROR;
					(void) snprintf(*errmsg, MAXPATHLEN,
					    "Syntax Error: Invalid value %s "
					    "for field: %s\n", tokval,
					    usbcfg_varlist[cfgvar].name);
				} else if (get_string(&llptr, tokval)) {
					switch (cfgvar) {
					case USB_SELECTION:
						user_rec->selection =
						    (char *)(uintptr_t)llptr;
						parse_state = USB_NEWVAR;
						break;
					case USB_SRNO:
						user_rec->serialno =
						    (char *)(uintptr_t)llptr;
						parse_state = USB_NEWVAR;
						break;
					case USB_PATH:
						user_rec->pathname =
						    (char *)(uintptr_t)llptr;
						parse_state = USB_NEWVAR;
						break;
					case USB_DRIVER:
						user_rec->driver =
						    (char *)(uintptr_t)llptr;
						parse_state = USB_NEWVAR;
						break;
					default:
						parse_state = USB_ERROR;
						free((void *)(uintptr_t)llptr);
					}
				} else {
					parse_state = USB_ERROR;
					(void) snprintf(*errmsg, MAXPATHLEN,
					    "Syntax Error: Invalid value %s "
					    "for field: %s\n", tokval,
					    usbcfg_varlist[cfgvar].name);
				}
				break;
			case USB_ERROR:
				/* just skip */
				break;
			default:
				parse_state = USB_ERROR;
				(void) snprintf(*errmsg, MAXPATHLEN,
				    "Syntax Error: at %s", tokval);
				break;
			}
			break;
		case EQUALS:
			if (parse_state == USB_CONFIG_VAR) {
				if (cfgvar == USB_NONE) {
					parse_state = USB_ERROR;
					(void) snprintf(*errmsg, MAXPATHLEN,
					    "Syntax Error: unexpected '='");
				} else {
					parse_state = USB_VAR_VALUE;
				}
			} else if (parse_state != USB_ERROR) {
				(void) snprintf(*errmsg, MAXPATHLEN,
				    "Syntax Error: unexpected '='");
				parse_state = USB_ERROR;
			}
			break;
		case HEXVAL:
		case DECVAL:
			if ((parse_state == USB_VAR_VALUE) && (cfgvar !=
			    USB_NONE)) {
				(void) getvalue(tokval, &value);
				switch (cfgvar) {
				case USB_VENDOR:
					user_rec->idVendor = (int)value;
					parse_state = USB_NEWVAR;
					break;
				case USB_PRODUCT:
					user_rec->idProduct = (int)value;
					parse_state = USB_NEWVAR;
					break;
				case USB_CFGNDX:
					user_rec->cfgndx = (int)value;
					parse_state = USB_NEWVAR;
					break;
				default:
					(void) snprintf(*errmsg, MAXPATHLEN,
					    "Syntax Error: Invalid value for "
					    "%s", usbcfg_varlist[cfgvar].name);
				}
			} else if (parse_state != USB_ERROR) {
				parse_state = USB_ERROR;
				(void) snprintf(*errmsg, MAXPATHLEN,
				    "Syntax Error: unexpected hex/decimal: %s",
				    tokval);
			}
			break;
		default:
			(void) snprintf(*errmsg, MAXPATHLEN,
			    "Syntax Error: at: %s", tokval);
			parse_state = USB_ERROR;
			break;
		}
		token = lex(buf, tokval, errmsg);
	}
	*rec = user_rec;

	return (token);
}


/*
 * Here we compare the two records and determine if they are the same
 */
static boolean_t
usb_cmp_rec(usb_configrec_t *cfg_rec, usb_configrec_t *user_rec)
{
	char		*ustr, *cstr;
	boolean_t	srno = B_FALSE, path = B_FALSE;

	DPRINTF("usb_cmp_rec:\n");

	if ((cfg_rec->idVendor == user_rec->idVendor) &&
	    (cfg_rec->idProduct == user_rec->idProduct)) {
		if (user_rec->serialno) {
			if (cfg_rec->serialno) {
				srno = (strcmp(cfg_rec->serialno,
				    user_rec->serialno) == 0);
			} else {

				return (B_FALSE);
			}

		} else if (user_rec->pathname) {
			if (cfg_rec->pathname) {
				/*
				 * Comparing on this is tricky. At this point
				 * hubd knows: ../hubd@P/device@P while user
				 * will specify ..../hubd@P/keyboard@P
				 * First compare till .../hubd@P
				 * Second compare is just P in "device@P"
				 *
				 * XXX: note that we assume P as one character
				 * as there are no 2 digit hubs in the market.
				 */
				ustr = strrchr(user_rec->pathname, '/');
				cstr = strrchr(cfg_rec->pathname, '/');
				path = (strncmp(cfg_rec->pathname,
				    user_rec->pathname,
				    MAX(ustr - user_rec->pathname,
				    cstr - cfg_rec->pathname)) == 0);
				path = path && (*(user_rec->pathname +
				    strlen(user_rec->pathname) -1) ==
					*(cfg_rec->pathname +
					strlen(cfg_rec->pathname) - 1));
			} else {

				return (B_FALSE);
			}

		} else if (cfg_rec->serialno || cfg_rec->pathname) {

			return (B_FALSE);
		} else {

			return (B_TRUE);
		}

		return (srno || path);
	} else {

		return (B_FALSE);
	}
}


/*
 * free the record allocated in usb_get_conf_rec
 */
static void
usb_free_rec(usb_configrec_t *rec)
{
	if (rec == (usb_configrec_t *)NULL) {

		return;
	}

	free(rec->selection);
	free(rec->serialno);
	free(rec->pathname);
	free(rec->driver);
	free(rec);
}


int
add_entry(char *selection, int vid, int pid, int cfgndx, char *srno,
    char *path, char *driver, char **errmsg)
{
	int		file;
	int		rval = CFGA_USB_OK;
	char		*buf = (char *)NULL;
	char		str[MAXLINESIZE];
	token_t		token = NEWLINE;
	boolean_t	found = B_FALSE;
	struct stat	st;
	usb_configrec_t cfgrec, *user_rec = NULL;

	DPRINTF("add_entry: driver=%s, path=%s\n",
	    driver ? driver : "", path ? path : "");

	if (*errmsg == (char *)NULL) {
		if ((*errmsg = calloc(MAXPATHLEN, 1)) == (char *)NULL) {

			return (CFGA_USB_CONFIG_FILE);
		}
	}

	(void) mutex_lock(&file_lock);

	/* Initialize the cfgrec */
	cfgrec.selection = selection;
	cfgrec.idVendor = vid;
	cfgrec.idProduct = pid;
	cfgrec.cfgndx = cfgndx;
	cfgrec.serialno = srno;
	cfgrec.pathname = path;
	cfgrec.driver = driver;

	/* open config_map.conf file */
	file = open(usbconf_file, O_RDWR, 0666);
	if (file == -1) {
		(void) snprintf(*errmsg, MAXPATHLEN,
		    "failed to open config file\n");
		(void) mutex_unlock(&file_lock);

		return (CFGA_USB_CONFIG_FILE);
	}

	if (lockf(file, F_TLOCK, 0) == -1) {
		(void) snprintf(*errmsg, MAXPATHLEN,
		    "failed to lock config file\n");
		close(file);
		(void) mutex_unlock(&file_lock);

		return (CFGA_USB_LOCK_FILE);
	}

	/*
	 * These variables need to be reinitialized here as they may
	 * have been modified by a previous thread that called this
	 * function
	 */
	linenum = 1;
	cntr = 0;
	frec = 0;
	brec = 0;
	btoken = 0;

	if (fstat(file, &st) != 0) {
		DPRINTF("add_entry: failed to fstat config file\n");
		rval = CFGA_USB_CONFIG_FILE;
		goto exit;
	}

	if ((buf = (char *)malloc(st.st_size)) == NULL) {
		DPRINTF("add_entry: failed to fstat config file\n");
		rval = CFGA_USB_ALLOC_FAIL;
		goto exit;
	}

	if (st.st_size != read(file, buf, st.st_size)) {
		DPRINTF("add_entry: failed to read config file\n");
		rval = CFGA_USB_CONFIG_FILE;
		goto exit;
	}

	/* set up for reading the file */

	while ((token != EOF) && !found) {
		if (user_rec) {
			usb_free_rec(user_rec);
			user_rec = NULL;
		}
		token = usb_get_conf_rec(buf, &user_rec, errmsg);
		found = usb_cmp_rec(&cfgrec, user_rec);
		DPRINTF("add_entry: token=%x, found=%x\n", token, found);
	}

	bzero(str, MAXLINESIZE);

	if (found) {
		DPRINTF("FOUND\n");
		(void) snprintf(str, MAXLINESIZE, "selection=%s idVendor=0x%x "
		    "idProduct=0x%x ",
		    (cfgrec.selection) ? cfgrec.selection : user_rec->selection,
		    user_rec->idVendor, user_rec->idProduct);

		if ((user_rec->cfgndx != -1) || (cfgrec.cfgndx != -1)) {
			(void) snprintf(&str[strlen(str)], MAXLINESIZE,
			    "cfgndx=0x%x ", (cfgrec.cfgndx != -1) ?
			    cfgrec.cfgndx : user_rec->cfgndx);
		}

		if (user_rec->serialno) {
			(void) snprintf(&str[strlen(str)],  MAXLINESIZE,
			    "srno=\"%s\" ", user_rec->serialno);
		}

		if (user_rec->pathname) {
			(void) snprintf(&str[strlen(str)],  MAXLINESIZE,
			    "pathname=\"%s\" ", user_rec->pathname);
		}

		if (user_rec->driver) {
			(void) snprintf(&str[strlen(str)],  MAXLINESIZE,
			    "driver=\"%s\" ", user_rec->driver);
		} else if (cfgrec.driver != NULL) {
			if (strlen(cfgrec.driver)) {
				(void) snprintf(&str[strlen(str)],  MAXLINESIZE,
				    "driver=\"%s\" ", cfgrec.driver);
			}
		}

		(void) strlcat(str, ";", sizeof (str));

		/*
		 * Seek to the beginning of the record
		 */
		if (lseek(file, brec, SEEK_SET) == -1) {
			DPRINTF("add_entry: failed to lseek config file\n");
			rval = CFGA_USB_CONFIG_FILE;
			goto exit;
		}

		/*
		 * Write the modified record
		 */
		if (write(file, str, strlen(str)) == -1) {
			DPRINTF("add_entry: failed to write config file\n");
			rval = CFGA_USB_CONFIG_FILE;
			goto exit;
		}

		/*
		 * Write the rest of the file as it was
		 */
		if (write(file, buf+cntr, st.st_size - cntr) == -1) {
			DPRINTF("add_entry: failed to write config file\n");
			rval = CFGA_USB_CONFIG_FILE;
			goto exit;
		}

	} else {
		DPRINTF("!FOUND\n");
		(void) snprintf(str, MAXLINESIZE,
		    "selection=%s idVendor=0x%x idProduct=0x%x ",
		    (cfgrec.selection) ? cfgrec.selection : "enable",
		    cfgrec.idVendor, cfgrec.idProduct);

		if (cfgrec.cfgndx != -1) {
			(void) snprintf(&str[strlen(str)], MAXLINESIZE,
			    "cfgndx=0x%x ", cfgrec.cfgndx);
		}

		if (cfgrec.serialno) {
			(void) snprintf(&str[strlen(str)], MAXLINESIZE,
			    "srno=\"%s\" ", cfgrec.serialno);
		}

		if (cfgrec.pathname != NULL) {
			(void) snprintf(&str[strlen(str)], MAXLINESIZE,
			    "pathname=\"%s\" ", cfgrec.pathname);
		}

		if (cfgrec.driver != NULL) {
			if (strlen(cfgrec.driver)) {
				(void) snprintf(&str[strlen(str)], MAXLINESIZE,
				    "driver=\"%s\" ", cfgrec.driver);
			}
		}

		(void) strlcat(str, ";\n", sizeof (str));

		/*
		 * Incase this is the first entry, add it after the comments
		 */
		if (frec == 0) {
			frec = st.st_size;
		}

		/*
		 * Go to the beginning of the records
		 */
		if (lseek(file, frec, SEEK_SET) == -1) {
			DPRINTF("add_entry: failed to lseek config file\n");
			rval = CFGA_USB_CONFIG_FILE;
			goto exit;
		}

		/*
		 * Add the entry
		 */
		if (write(file, str, strlen(str)) == -1) {
			DPRINTF("add_entry: failed to write config file\n");
			rval = CFGA_USB_CONFIG_FILE;
			goto exit;
		}

		/*
		 * write the remaining file as it was
		 */
		if (write(file, buf+frec, st.st_size - frec) == -1) {
			DPRINTF("add_entry: failed to write config file\n");
			rval = CFGA_USB_CONFIG_FILE;
			goto exit;
		}
	}

	/* no error encountered */
	if (rval == CFGA_USB_OK) {
		free(errmsg);
	}

exit:
	if (buf != NULL) {
		free(buf);
	}

	if (lockf(file, F_ULOCK, 0) == -1) {
		DPRINTF("add_entry: failed to unlock config file\n");

		rval = CFGA_USB_LOCK_FILE;
	}

	close(file);

	(void) mutex_unlock(&file_lock);

	return (rval);
}