/*	Copyright (c) 1990 UNIX System Laboratories, Inc.	*/
/*	Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF     	*/
/*	UNIX System Laboratories, Inc.                     	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"@(#)libadm:putdev.c	1.2.6.1"
/* LINTLIBRARY */

/*
 * putdev.c
 *
 * Global Definitions:
 *	_adddevtabrec()		Add a record to the device table
 *	_putdevtabrec()		Write a record to the device table
 *	_moddevtabrec()		Modify a device-table record
 *	_rmdevtabrec()		Remove a device-table record
 *	_rmdevtabattrs()	Remove attributes from a device-table record
 *	oam_devtab		File descriptor of the open device table
 */

/*
 *  G L O B A L   R E F E R E N C E S
 *
 *	Header Files
 *	Externals Referenced
 */

/*
 * Header Files
 *	<sys/types.h>		UNIX(r) Data Types
 *	<sys/stat.h>
 *	<stdio.h>		Standard I/O definitions
 *	<fcntl.h>		Definitions for file control
 *	<errno.h>		Error handling definitions
 *	<string.h>		String Handling Definitions
 *	<devmgmt.h>		Device Management Definitions
 *	<unistd.h>		Get UNIX(r) Standard Definitions
 *	"devtab.h"		Local Device Management Definitions
 */

#include	<sys/types.h>
#include	<sys/stat.h>
#include	<stdio.h>
#include	<fcntl.h>
#include	<errno.h>
#include	<string.h>
#include	<devmgmt.h>
#include	<unistd.h>
#include	"devtab.h"


/*
 * External functions referenced
 *	malloc()		Allocate memory
 *	free()			Free malloc()ed memory
 *	getpid()		Get process-ID
 *	umask()			File-permissions mask
 *	unlink()		Unlink a file (USE rename() SOMEDAY)
 *	link()			Link a file  (USE rename() SOMEDAY)
 */

extern	void   *malloc();
extern	void	free();
extern	pid_t	getpid();
extern	mode_t	umask();
extern	int	link();
extern	int	unlink();


/*
 * External data referenced:
 *	oam_devtab	Open file descriptor for the device table
 */

extern	FILE	       *oam_devtab;

/*
 *  L O C A L   D E F I N I T I O N S
 *
 *	TDTABNM		Name of the temporary device table (in the
 *			directory of the existing table)
 *	TDTABNMLN	Number of characters added to the directory
 *			name -- the length of the device table temp name
 */

#define	TDTABNM		"%sdevtab.%6.6ld"
#define	TDTABNMLN	13


/*
 * Static functions
 *	strcatesc	Copies a character-string from one place to another
 *			escaping the appropriate characters
 *	lkdevtab	Locks the device table
 *	unlkdevtab	Unlocks the device table
 *	mkdevtabent	Builds a device-table entry from the alias and the
 *			list of attr=val pairs given
 *	opennewdevtab	Opens a new device table (as a temp file)
 *	mknewdevtab	Makes the temp device table the new devtab
 *	rmnewdevtab	Remove the temporary device table and free space
 *			allocated to the filename of that file.
 */

static	char		       *strcatesc();
static	int			lkdevtab();
static	int			unlkdevtab();
static	struct devtabent       *mkdevtabent();
static	FILE		       *opennewdevtab();
static	int			mknewdevtab();
static	int			rmnewdevtab();

/*
 * char *strcatesc(p, q)
 *	char   *p
 *	char   *q
 *
 *	Write the character-string pointed to by "q" to the place
 *	pointed to by "p", escaping those characters in "q" found in the
 *	string "DTAB_ESCS" by preceding them with '\\'.  Return a pointer to
 *	the byte beyond the last character written to "p".
 *
 *  Arguments:
 *	p		The place to begin writing to
 *	q		The string to write
 *
 *  Returns:  char *
 *	The address of the byte beyond the last character written into "p"
 */

static char *
strcatesc(p, q)
	char   *p;		/* Place to write to */
	char   *q;		/* Thing to write */
{
	while (*q) {
	    if (strchr(DTAB_ESCS, *q)) *p++ = '\\';
	    *p++ = *q++;
	}
	return(p);
}

/*
 * FILE *opennewdevtab(pname)
 *	char   **pname
 *
 *	Generates a temporary device-table name from the existing
 *	device table name (in the same directory) and opens that
 *	file for writing.  It puts a pointer to the malloc()ed space
 *	containing the temp device table's name at the place referenced
 *	by <pname>.
 *
 *  Arguments:
 *	pname	Pointer to the char * to contain the address of the name
 *		of the temporary file
 *
 *  Returns:  FILE *
 *	A pointer to the opened stream or (FILE *) NULL if an error occurred.
 *	If an error occurred, "errno" will be set to reflect the problem.
 */

static FILE *
opennewdevtab(pname)
	char  **pname;			/* A(ptr to temp filename's path) */
{
	char   *oldname;		/* Ptr to the device-table's name */
	char   *buf;			/* Ptr to the temp file's name */
	char   *dirname;		/* Directory containing devtab */
	char   *p;			/* Ptr to last '/' in devtab name */
	int    fd;			/* Opened file descriptor */
	FILE   *fp;			/* Opened file pointer */
	struct stat	sbuf;		/* stat buf for old devtab file */

	fp = (FILE *) NULL;
	if (oldname = _devtabpath()) {
	    /*
	     * It is possible for us to have sufficient permissions to create
	     * the new file without having sufficient permissions to write the
	     * original devtab file.  For consistency with the operations which
	     * modify the original file by writing it directly we require write
	     * permissions for the original file in order to make a new one.
	     */
	    if ((fd = open(oldname, O_WRONLY)) == -1)
		return(NULL);

	    if (fstat(fd, &sbuf) == -1) {
		close(fd);
		return(NULL);
	    }
	    close(fd);

	    if (p = strrchr(oldname, '/')) {
		*(p+1) = '\0';
		dirname = oldname;
	    }
	    else dirname = "./";
	    if (buf = (char *) malloc(TDTABNMLN + strlen(dirname) + 1)) {

		/* Build the name of the temp device table and open the
		 * file.  We must reset the owner, group and perms to those
		 * of the original devtab file.
		 */
		(void) sprintf(buf, TDTABNM, dirname, getpid());
		if (fp = fopen(buf, "w")) {
			*pname = buf;
			fchmod(fileno(fp), sbuf.st_mode & 0777);
			fchown(fileno(fp), sbuf.st_uid, sbuf.st_gid);
		} else {
			free(buf);
		}
	    }

	    /*
	     * Free the space containing the device table's name.
	     */
	    free(oldname);
	}

	/* Finished.  Return what we've got */
	return(fp);
}

/*
 *  int rmnewdevtab(tempname)
 *	char   *tempname
 *
 *	Unlink the temp device table and free the memory allocated to
 *	contain the name of that file
 *
 *  Arguments:
 *	tempname	Name of the temporary file
 *
 *  Returns: int
 *	TRUE if successful, FALSE otherwise
 */

static int
rmnewdevtab(tempname)
	char   *tempname;		/* Filename of new device table */
{
	int	noerr;			/* Flag, TRUE if no error, FALSE otherwise */

	/* Unlink the file */
	noerr = (unlink(tempname) == 0);

	/* Free the space allocated to the filename */
	free(tempname);

	/* Return success indicator */
	return(noerr);
}

/*
 *  int mknewdevtab(tempname)
 *	char   *tempname
 *
 *	Make the temporary device-table the new system device table
 *
 *  Arguments:
 *	tempname	Name of the temporary file
 *
 *  Returns:  int
 *	TRUE if successful, FALSE otherwise
 *
 *  Notes:
 *	- Need to use rename() someday instead of link()/unlink()
 *	- This code is somewhat ineffecient in that asks for the name
 *	  of the device-table more than once.  Done so that we don't
 *	  have to manage that space, but this may be somewhat lazy.
 */

static int
mknewdevtab(tempname)
	char   *tempname;		/* Ptr to name of temp dev tab */
{
	char   *devtabname;		/* Ptr to the device table's name */
	int	noerr;			/* FLAG, TRUE if all's well */

	/* Get the device table's pathname */
	if (devtabname = _devtabpath()) {

	    /* Unlink the existing file */
	    if (unlink(devtabname) == 0) {

		/* Make the temp file the real device table */
		noerr = (link(tempname, devtabname) == 0) ? TRUE : FALSE;

		/* Remove the temp file (and resources) */
		if (noerr) (void) rmnewdevtab(tempname);

	    } else noerr = FALSE;	/* unlink() failed */

	    /* Free the device table's name */
	    free(devtabname);

	} else noerr = FALSE; 	/* devtabpath() failed */

	/* Finished.  Return success indicator */
	return(noerr);
}

/*
 * int lkdevtab(o_mode, lktype)
 *	char   *o_mode
 *	int	lktype
 *
 *	Lock the device table for writing.  If it isn't available, it waits
 *	until it is.
 *
 *  Arguments:
 *	o_mode	The open() mode to use when opening the device table
 *	lktype	The type of lock to apply
 *
 *  Returns:  int
 *	TRUE if successful, FALSE with errno set otherwise
 */

static int
lkdevtab(o_mode, lktype)
	char   *o_mode;				/* Open mode */
	int	lktype;				/* Lock type */
{
	/* Automatic data */
	struct flock	lockinfo;		/* File locking structure */
	int		noerr;			/* FLAG, TRUE if no error */
	int		olderrno;		/* Old value of "errno" */


	/* Close the device table (if it's open) */
	_enddevtab();

	/* Open the device table for read/append */
	noerr = TRUE;
	if (_opendevtab(o_mode)) {

	    /*
	     * Lock the device table (for writing).  If it's not
	     * available, wait until it is, then close and open the
	     * table (modify and delete change the table!) and try
	     * to lock it again
	     */

	    /* Build the locking structure */
	    lockinfo.l_type = (short) lktype;
	    lockinfo.l_whence = 0;
	    lockinfo.l_start = 0L;
	    lockinfo.l_len = 0L;
	    olderrno = errno;

	    /* Keep on going until we lock the file or an error happens */
	    while ((fcntl(fileno(oam_devtab), F_SETLK, &lockinfo) == -1) && !noerr) {
		if (errno == EACCES) {
		    if (fcntl(fileno(oam_devtab), F_SETLKW, &lockinfo) == -1)
			noerr = FALSE;
		    else {
			/* Reopen the file (maybe it's moved?) */
			_enddevtab();
			if (!_opendevtab(o_mode)) noerr = FALSE;
			else errno = olderrno;
		    }
		} else noerr = FALSE;
	    }

	    if (!noerr) _enddevtab();  /* Don't keep open if in error */

	} else noerr = FALSE;

	/* Done */
	return(noerr);
}

/*
 * int unlkdevtab()
 *
 *	Unlock the locked device table.
 *
 *  Arguments:  None
 *
 *  Returns:  int
 *	Whatever fcntl() returns...
 */

static int
unlkdevtab()
{
	/* Automatic data */
	struct flock	lockinfo;		/* Locking structure */
	int		noerr;			/* FLAG, TRUE if all's well */

	/* Build the locking structure */
	lockinfo.l_type = F_UNLCK;		/* Lock type */
	lockinfo.l_whence = 0;			/* Count from top of file */
	lockinfo.l_start = 0L;			/* From beginning */
	lockinfo.l_len = 0L;			/* Length of locked data */

	/* Unlock it */
	noerr = (fcntl(fileno(oam_devtab), F_SETLK, &lockinfo) != -1);
	_enddevtab();

	/* Finished */
	return(noerr);
}

/*
 * struct devtabent *mkdevtabent(alias, attrlist)
 *	char   *alias
 *	char  **attrlist
 *
 *	This function builds a struct devtabent structure describing the
 *	alias <alias> using the information in the attribute list <attrlist>.
 *	The <attrlist> contains data of the form attr=value where attr is
 *	the name of an attribute and value is the value of that attribute.
 *
 *  Arguments:
 *	alias		The alias being added to the device table
 *	attrlist	The attributes and values for that alias
 *
 *  Returns:  struct devtabent *
 *	A completed struct devtabent structure containing the description
 *	of the alias.  The structure, and all of the data in the structure
 *	are each in space allocated using the malloc() function and should
 *	be freed using the free() function (or the _freedevtabent() function).
 *
 *  Errors:
 *	EINVAL	If "alias" is used as an attribute in an attr=val pair
 *	EAGAIN	If an attribute is specified more than once
 */

static struct devtabent *
mkdevtabent(alias, attrlist)
	char   *alias;		/* Alias of entry */
	char  **attrlist;	/* Attributes of new entry */
{
	/* Automatic data */
	struct devtabent       *devtabent;	/* * to struct we're making */
	struct attrval	       *prevattrval;	/* * to prev attr/val struct */
	struct attrval	       *attrval;	/* * to current struct */
	char		      **pp;		/* Ptr into list of ptrs */
	char		       *peq;		/* Ptr to '=' in string */
	char		       *val;		/* Ptr to space for value */
	char		       *name;		/* Ptr to space for name */
	int			len;		/* Length of name */
	int			noerr;		/* TRUE if all's well */
	int			found;		/* TRUE the attr is found */


	/* No problems (yet) */
	noerr = TRUE;

	/* Get space for the structure */
	if (devtabent = (struct devtabent *) malloc(sizeof(struct devtabent))) {

	    /* Fill in default values */
	    if (devtabent->alias = (char *) malloc(strlen(alias)+1)) {

		(void) strcpy(devtabent->alias, alias);		/* alias */
		devtabent->comment = FALSE;			/* data rec */
		devtabent->cdevice = (char *) NULL;		/* cdevice */
		devtabent->bdevice = (char *) NULL;		/* bdevice */
		devtabent->pathname = (char *) NULL;		/* pathname */
		devtabent->attrstr = (char *) NULL;		/* string */
		devtabent->attrlist = (struct attrval *) NULL;	/* attr list */

		/* Add attributes to the structure */
		prevattrval = (struct attrval *) NULL;
		if (pp = attrlist) while (*pp && noerr) {

		    /* Valid attr=value pair? */
		    if ((peq = strchr(*pp, '=')) && ((len = peq-*pp) > 0)) {

			/* Get space for the value */
			if (val = (char *) malloc((unsigned int) strlen(peq))) {
			    (void) strcpy(val, peq+1);		/* Copy it */

			    /* Get space for attribute name */
			    if (name = (char *) malloc(len + 1)) {
				(void) strncpy(name, *pp, len);
				*(name+len) = '\0';

				/* Specifying the alias?  If so, ERROR */
				if (strcmp(name, DTAB_ALIAS) == 0) {
				    noerr = FALSE;
				    free(name);
				    free(val);
				    errno = EINVAL;
				}

				/* Specifying the char device path? */
				else if (strcmp(name, DTAB_CDEVICE) == 0) {
				    if (!devtabent->cdevice) {
					if (val[0] != '/') {
						noerr = FALSE;
						free(name);
						free(val);
						errno = ENXIO;
					} else {
						devtabent->cdevice = val;
						free(name);
					}
				    } else {
					noerr = FALSE;
					free(name);
					free(val);
					errno = EAGAIN;
				    }
				}

				/* Specifying the block device path? */
				else if (strcmp(name, DTAB_BDEVICE) == 0) {
				    if (!devtabent->bdevice) {
					if (val[0] != '/') {
						noerr = FALSE;
						free(name);
						free(val);
						errno = ENXIO;
					} else {
						devtabent->bdevice = val;
						free(name);
					}
				    } else {
					noerr = FALSE;
					free(name);
					free(val);
					errno = EAGAIN;
				    }
				}

				/* Specifying the pathname (generic)? */
				else if (strcmp(name, DTAB_PATHNAME) == 0) {
				    if (!devtabent->pathname) {
					if (val[0] != '/') {
						noerr = FALSE;
						free(name);
						free(val);
						errno = ENXIO;
					} else {
						devtabent->pathname = val;
						free(name);
					}
				    } else {
					noerr = FALSE;
					free(name);
					free(val);
					errno = EAGAIN;
				    }
				}

				/* Some other attribute */
				else {
				    found = FALSE;
				    if (attrval = devtabent->attrlist) do {
					if (strcmp(attrval->attr, name) == 0) {
					    noerr = FALSE;
					    free(name);
					    free(val);
					    errno = EAGAIN;
					}
				    } while (!found && noerr && (attrval = attrval->next));

				    if (!found && noerr) {

					/* Get space for attr/val structure */
					if (attrval = (struct attrval *) malloc(sizeof(struct attrval))) {

					    /* Fill attr/val structure */
					    attrval->attr = name;
					    attrval->val = val;
					    attrval->next = (struct attrval *) NULL;

					    /* Link into the list of attributes */
					    if (prevattrval) prevattrval->next = attrval;
					    else devtabent->attrlist = attrval;
					    prevattrval = attrval;

					} else { /* malloc() for attrval failed */
					    noerr = FALSE;
					    free(name);
					    free(val);
					}
				    }
				}   /* End else (some other attribute) */

			    } else { 	/* malloc() for attribute name failed */
				noerr = FALSE;
				free(val);
			    }

			} else noerr = FALSE;	/* Malloc() for "val" failed */

			/* If we saw an error, free structure, returning NULL */
			if (!noerr) {
			    _freedevtabent(devtabent);
			    devtabent = (struct devtabent *) NULL;
			}

		    } 	/* Ignore invalid attr=val pair */

		    if (noerr) pp++;

		}   /* End attribute processing loop */

	    } else {	/* malloc() failed */
		free(devtabent);
		devtabent = (struct devtabent *) NULL;
		noerr = FALSE;
	    }
	} else noerr = FALSE;   /* if (malloc(devtabent space)) */

	/* Finished */
	return(devtabent);
}

/*
 * int _putdevtabrec(stream, rec)
 *	FILE		       *stream
 *	struct devtabent       *rec
 *
 *	Write a device table record containing the information in the struct
 *	devtab structure <rec> to the current position of the standard I/O
 *	stream <stream>.
 *
 *  Arguments:
 *	stream		The stream to write to
 *	rec		The structure containing the information to write
 *
 *  Returns:  int
 *	The number of characters written or EOF if there was some error.
 */

int
_putdevtabrec(stream, rec)
	FILE		       *stream;		/* Stream to which to write */
	struct devtabent       *rec;		/* Record to write */
{
	/* Automatic Data */
	struct attrval	       *attrval;	/* Ptr to attr/val pair */
	char		       *buf;		/* Allocated buffer */
	char		       *p;		/* Temp char pointer */
	int			count;		/* Number of chars written */
	int			size;		/* Size of needed buffer */


	/* Comment or data record? */
	if (rec->comment) {

	    /*
	     * Record is a comment
	     */

	    /* Copy (escaping chars) record into temp buffer */
	    size = (strlen(rec->attrstr)*2)+1;			/* Max rec size */
	    if (buf = (char *) malloc((unsigned int) size+1)) {	/* Alloc space */
		p = strcatesc(buf, rec->attrstr);		/* Copy "escaped" */
		*(p-2) = '\n';					/* Unescape last \n */
		*(p-1) = '\0';					/* Terminate string */

		/* Write the record */
		count = fputs(buf, stream);
		free(buf);

	    } else count = EOF;  /* malloc() failed */
	}

	else {

	    /*
	     * Record is a data record
	     */

	    /*
	     * Figure out maximum amount of space you're going to need.
	     * (Assume every escapable character is escaped to determine the
	     * maximum size needed)
	     */

	    size = strlen(rec->alias) + 1;		/* alias: */
	    size += (strlen(rec->cdevice)*2) + 1;	/* cdevice: */
	    size += (strlen(rec->bdevice)*2) + 1;	/* bdevice: */
	    size += (strlen(rec->pathname)*2) + 1;	/* pathname: */
	    if (attrval = rec->attrlist) do {		/* Attributes */
		size += (strlen(attrval->attr)*2);	    /* attr */
		size += (strlen(attrval->val)*2) +4;	    /* val & '="" ' or val & '=""\n' */
	    } while (attrval = attrval->next) ;		    /* Next attr/val */
	    else size++;				/* Else make room for trailing '\n' */

	    /* Alloc space for "escaped" record */
	    if (buf = (char *) malloc((unsigned int) size+1)) {

		/* Initializations */
		p = buf;

		/* Write the alias ("alias" attribute) */
		p = strcatesc(p, rec->alias);
		*p++ = ':';

		/* Write the character device ("cdevice" attribute) */
		if (rec->cdevice) p = strcatesc(p, rec->cdevice);
		*p++ = ':';

		/* Write the block device ("bdevice" attribute) */
		if (rec->bdevice) p = strcatesc(p, rec->bdevice);
		*p++ = ':';

		/* Write the pathname ("pathname" attribute) */
		if (rec->pathname) p = strcatesc(p, rec->pathname);
		*p++ = ':';

		/* Write the rest of the attributes */
		if (attrval = rec->attrlist) do {
		    p = strcatesc(p, attrval->attr);
		    *p++ = '=';
		    *p++ = '"';
		    p = strcatesc(p, attrval->val);
		    *p++ = '"';
		    if (attrval = attrval->next) *p++ = ' ';
		} while (attrval);

		/* Terminate the record */
		*p++ = '\n';
		*p = '\0';

		/* Write the record */
		count = fputs(buf, stream);
		free(buf);
	    }
	    else count = EOF;  /* malloc() failed */
	}

	/* Finished */
	return(count);
}

/*
 *  int _adddevtabrec(alias, attrval)
 *	char   *alias
 *	char  **attrval
 *
 *	This function adds a record to the device table.  That record will
 *	have the alias <alias> and will have the attributes described in
 *	the list referenced by <attrval>.
 *
 *	It always adds the record to the end of the table.
 *
 *  Arguments:
 *	alias		The alias of the device whose description is being
 *			added to the device table.
 *	attrval		The pointer to the first item of a list of attributes
 *			defining the device whose description is being added.
 *			(This value may be (char **) NULL).
 *
 *  Returns:  int
 *	TRUE if successful, FALSE with errno set otherwise.
 */

int
_adddevtabrec(alias, attrval)
	char   *alias;			/* Alias to add to the device table */
	char  **attrval;		/* Attributes for that device */
{
	/* Automatic data */
	struct devtabent       *devtabent;	/* Ptr to dev tab entry */
	int			olderrno;	/* Errno on entry */
	int			noerr;		/* FLAG, TRUE if all's well */


	/* Validate the device alias.  Error (EINVAL) if it's not valid */
	if (!_validalias(alias)) {
	    errno = EINVAL;
	    return(FALSE);
	}

	/* Lock the device table.  This only returns if the table is locked or
	 * some error occurred.  It waits until the table is available.  */
	if (!lkdevtab("a+", F_WRLCK)) return(FALSE);

	/* Make sure that the alias isn't already in the table */
	noerr = TRUE;
	olderrno = errno;
	if (devtabent = _getdevrec(alias)) {

	    /* The alias is already in the table */
	    _freedevtabent(devtabent);		/* Free device table info */
	    errno = EEXIST;			/* Set errno, entry exists */
	    noerr = FALSE;			/* All's not well */
	}
	else if ((errno == ENOENT) || (errno == ENODEV)) {

	    /* The alias wasn't in the table or there wasn't a table. */

	    errno = olderrno;			/* Reset errno */

	    /* Build a struct devtabent that describes the new alias */
	    if (devtabent = mkdevtabent(alias, attrval)) {

		/* Position to the end of the existing table */
		if (fseek(oam_devtab, 0, SEEK_END) == 0)

		    /* Write the new entry */
		    noerr = (_putdevtabrec(oam_devtab, devtabent) != EOF);

		/* Free the info we just wrote */
		_freedevtabent(devtabent);

	    } else noerr = FALSE;	/* mkdevtabent() failed */
	} else noerr = FALSE;		/* Some odd error, _devtab */

	/* Unlock and close the device table */
	(void) unlkdevtab();

	/* Fini */
	return(noerr);
}

/*
 * int _moddevtabrec(device, attrval)
 *	char   *device
 *	char  **attrval
 *
 *	This function modifies the description for the specified device
 *	so that it has the attributes and values as specified in the
 *	given list.
 *
 *  Arguments:
 *	device		The name of the device whose description
 *			is being modified
 *	attrval		The first attr/val value in the list (attr=val) of
 *			the attributes that are to change
 *
 *  Returns:  int
 *	TRUE if all went well, FALSE with errno set otherwise
 */

int
_moddevtabrec(device, attrval)
	char   *device;			/* Device to modify */
	char  **attrval;		/* Attributes to add or change */
{
	/* Automatic data */
	FILE		       *fd;	/* File ptr, new device table */
	struct devtabent       *ent;	/* Device's current description */
	struct devtabent       *chg;	/* Changes to make to description */
	struct attrval	       *new;	/* New attribute/value desc */
	struct attrval	       *old;	/* Old attribute/value desc */
	struct attrval	       *newnew;	/* Next "new" value to look at */
	struct attrval         *prevnew;/* Previous item in the 'new' list */
	char		       *tname;	/* name of temp devtab file */
	int			noerr;	/* FLAG, TRUE if all's well */
	int			found;	/* FLAG, TRUE if attr found for dev */

	/* Lock the device table */
	if (!lkdevtab("r", F_WRLCK)) return(FALSE);

	/* No problems (so far) */
	noerr = TRUE;

	/* Get the entry to modify */
	if (ent = _getdevrec(device)) {

	    /* Build a structure describing the changes */
	    if (chg = mkdevtabent(device, attrval)) {

		/* If the "cdevice" field is specified, change it */
		if (chg->cdevice) {
		    if (ent->cdevice) free(ent->cdevice);
		    ent->cdevice = chg->cdevice;
		    chg->cdevice = (char *) NULL;
		}

		/* If the "bdevice" field is specified, change it */
		if (chg->bdevice) {
		    if (ent->bdevice) free(ent->bdevice);
		    ent->bdevice = chg->bdevice;
		    chg->bdevice = (char *) NULL;
		}

		/* If the "pathname" field is specified, change it */
		if (chg->pathname) {
		    if (ent->pathname) free(ent->pathname);
		    ent->pathname = chg->pathname;
		    chg->pathname = (char *) NULL;
		}

		/* Change the other attributes (if any) */
		if (ent->attrlist) {
		    prevnew = (struct attrval *) NULL;
		    if (new = chg->attrlist) do {

			found = FALSE;
			for (old = ent->attrlist ; !found && old ; old = old->next) {
			    if (strcmp(old->attr, new->attr) == 0) {
				found = TRUE;
				free(old->val);
				old->val = new->val;
				new->val = (char *) NULL;
			    }
			}   /* Loop through the existing attribute list */

			/*
			 * If the attribute wasn't found, add it to the list
			 * of attributes for the device.  If it was found, just
			 * bump to the next one and look for it
			 */

			if (!found) {

			    /* Not found.  Move attr/val description to the
			     * device's list of attributes */

			    if (prevnew) prevnew->next = new->next;
			    else chg->attrlist = new->next;
			    newnew = new->next;
			    new->next = ent->attrlist;
			    ent->attrlist = new;
			    new = newnew;
			}
			else {

			    /* Attribute changed, bump to the next one */
			    prevnew = new;
			    new = new->next;
			}
		    } while (new);  /* Loop for each attr to add or modify */

		} else {

		    /* Device had no attributes -- add entire list */
		    ent->attrlist = chg->attrlist;
		    chg->attrlist = (struct attrval *) NULL;
		}

		/* Free the structure containing the changes */
		_freedevtabent(chg);

	    } else noerr = FALSE;   /* Couldn't build changes struct */

	    /* If there hasn't been an error (so far), write the new record */
	    if (noerr) {

		/* Open the new device table */
		if (fd = opennewdevtab(&tname)) {

		    /*
		     * For each entry in the existing table, write that entry
		     * to the new table.  If the entry is the one being
		     * modified, write the modified entry instead of the
		     * original entry.
		     */

		    _setdevtab();		/* Rewind existing table */
		    chg = ent;			/* Remember new record */
		    while ((ent = _getdevtabent()) && noerr) {
			if (ent->entryno != chg->entryno)
			    noerr = _putdevtabrec(fd, ent) != EOF;
			else noerr = _putdevtabrec(fd, chg) != EOF;
			_freedevtabent(ent);
		    }

		    /*
		     * If we successfully generated the new table, make it the
		     * new system device table.  Otherwise, just remove the
		     * temporary file we've created.
		     */

		    if (noerr) {
			(void) fclose(fd);
			noerr = mknewdevtab(tname);
		    } else {
			(void) fclose(fd);
			(void) rmnewdevtab(tname);
		    }

		    /* Free the changed device structure */
		    _freedevtabent(chg);

		}   /* if (_opennewdevtab()) */
	    	else noerr = FALSE;

	    } else _freedevtabent(ent);  /* if (noerr) */

	} else noerr = FALSE;	/* Device not found? */

	/* Finished.  Unlock the device table and quit */
	(void) unlkdevtab();
	return(noerr);
}

/*
 * int _rmdevtabrec(device)
 *	char   *device
 *
 *	This function removes the record in the device table for the specified
 *	device.
 *
 *  Arguments:
 *	device	The device (alias, cdevice, bdevice, pathname, or link to one)
 *		whose entry is to be removed
 *
 *  Returns:  int
 *	Success indicator:  TRUE if successful, FALSE with errno set otherwise.
 */

int
_rmdevtabrec(device)
	char   *device;			/* Device to remove */
{
	struct devtabent       *rment;
	struct devtabent       *devtabent;
	char		       *tempname;
	FILE		       *fd;
	int			noerr;

	if (!lkdevtab("r", F_WRLCK)) return(FALSE);
	noerr = TRUE;
	if  (rment = _getdevrec(device)) {
	    if (fd = opennewdevtab(&tempname)) {
		_setdevtab();
		while ((devtabent = _getdevtabent()) && noerr) {
		    if (devtabent->entryno != rment->entryno)
			noerr = _putdevtabrec(fd, devtabent) != EOF;
		    _freedevtabent(devtabent);
		}
		if (noerr) {
		    (void) fclose(fd);
		    noerr = mknewdevtab(tempname);
		} else {
		    (void) fclose(fd);
		    (void) rmnewdevtab(tempname);
		}
	    } else noerr = FALSE;
	    _freedevtabent(rment);
	} else noerr = FALSE;
	(void) unlkdevtab();
	return(noerr);
}

/*
 * int _rmdevtabattrs(device, attributes, notfounds)
 *	char   *device
 *	char  **attributes
 *	char ***notfounds
 *
 *	Remove the specified attributes from the specified device.  The
 *	device is specified by <device>, <attributes> is the address of
 *	the first char * in the list of char * pointing to the attributes
 *	to remove from the device, and <notfounds> is the address of a
 *	char ** to put the address of the first element in the malloc()ed
 *	list of (char *) pointing to requested attributes that were not
 *	defined for the device <device>.
 *
 *  Arguments:
 *	device		The device from which attributes are to be removed
 *	attributes	The address of the first element in the list of
 *			attributes to remove.  This list is terminated by
 *			(char *) NULL.
 *	notfounds	The place to put the address of the list of addresses
 *			referencing the requested attributes that are not
 *			defined for the specified device.
 *
 *  Returns: int
 *	TRUE if successful, FALSE with errno set otherwise.
 *
 *  Notes:
 *    -	"alias" may not be undefined
 *    - "cdevice", "bdevice", and "pathname" are made "null", not really
 *	undefined
 */

int
_rmdevtabattrs(device, attributes, notfounds)
	char   *device;			/* Device to modify */
	char  **attributes;		/* Attributes to remove */
	char ***notfounds;		/* Attributes req'd but not found */
{
	/* Automatics */
	char		      **pnxt;		/* Ptr to next attribute */
	char		      **pp;		/* Ptr to current attr name */
	struct devtabent       *modent;		/* Entry being modified */
	struct devtabent       *devtabent;	/* Entry being copied */
	struct attrval	       *attrval;	/* Ptr to attr/val desc */
	struct attrval	       *prevattrval;	/* Ptr to prev attr/val */
	FILE		       *fd;		/* File desc, temp file */
	char		       *tempname;	/* Name of temp file */
	int			nattrs;		/* Number of attrs to remove */
	int			nobaderr;	/* TRUE if no fatal error */
	int			noerr;		/* TRUE if no non-fatal error */
	int			found;		/* TRUE if attribute found */
	int			nonotfounds;	/* TRUE if no attrs not fount */


	/* Initializations */
	nobaderr = TRUE;
	noerr = TRUE;

	/* Count attributes to remove -- make sure "alias" isn't specified */
	for (pp = attributes, nattrs = 0 ; *pp ; pp++, nattrs++)
	    if (strcmp(*pp, DTAB_ALIAS) == 0) {
		*notfounds = (char **) NULL;
		errno = EINVAL;
		return(FALSE);
	    }

	/* Lock the device table */
	if (!lkdevtab("r", F_WRLCK)) return(FALSE);

	/* Is there a record for the requested device? */
	if (modent = _getdevrec(device)) {

	    /* Record found.  Try to modify it */
	    nonotfounds = TRUE;

	    /* For each of the attributes in the attribute list ... */
	    for (pp = attributes ; nobaderr && *pp ; pp++) {

		/*
		 * Modify the device description, removing the requested
		 * attributes from the structure
		 */

		found = FALSE;				/* Not found yet */

		/* If it's the "cdevice" attribute, make it a null-string */
		if (strcmp(*pp, DTAB_CDEVICE) == 0) {
		    if (modent->cdevice) {
			free(modent->cdevice);
			modent->cdevice = (char *) NULL;
		    }
		    found = TRUE;
		}

		/* If it's the "bdevice" attribute, make it a null-string */
		else if (strcmp(*pp, DTAB_BDEVICE) == 0) {
		    if (modent->bdevice) {
			free(modent->bdevice);
			modent->bdevice = (char *) NULL;
		    }
		    found = TRUE;
		}

		/* If it's the "pathname" attribute, make it a null-string */
		else if (strcmp(*pp, DTAB_PATHNAME) == 0) {
		    if (modent->pathname) {
			free(modent->pathname);
			modent->pathname = (char *) NULL;
		    }
		    found = TRUE;
		}

		/* Must be one of the other "auxilliary" attributes */
		else {

		    /* Search the attribute list for the attribute */
		    prevattrval = (struct attrval *) NULL;
		    if (attrval = modent->attrlist) do {
			if (strcmp(*pp, attrval->attr) == 0) {

			    /* Found.  Remove from attribute list */
			    found = TRUE;
			    free(attrval->attr);
			    free(attrval->val);
			    if (prevattrval) {
				prevattrval->next = attrval->next;
				free((char *) attrval);
				attrval = prevattrval->next;
			    } else {
				modent->attrlist = attrval->next;
				free((char *) attrval);
				attrval = modent->attrlist;
			    }
			} else {
			    prevattrval = attrval;	/* Advance to next */
			    attrval = attrval->next;
			}
		    } while (!found && attrval);

		}   /* End attribute search loop */

		/* If the requested attribute wasn't defined for the device,
		   put it in the list of attributes not found */

		if (!found) {

		    /* If there's no list (yet), alloc enough space for
		       the list */

		    if (nonotfounds)
			if (*notfounds = (char **) malloc(sizeof(char **)*(nattrs+1))) {

			    /* List allocated -- put in the first entry */
			    nonotfounds = FALSE;
			    pnxt = *notfounds;
			    if (*pnxt = (char *) malloc(strlen(*pp)+1)) {
				errno = EINVAL;
				noerr = FALSE;
				(void) strcpy(*pnxt++, *pp);
			    } else {
				/* malloc() failed, free list */
				free(*notfounds);
				*notfounds = (char **) NULL;
				nonotfounds = TRUE;
				nobaderr = FALSE;
			    }

			} else nobaderr = FALSE;  /* malloc() failed */

		    else {
			/* Already a list, add this attribute to it */
			if (*pnxt = (char *) malloc(strlen(*pp)+1))
			    (void) strcpy(*pnxt++, *pp);
			else {
			    /* Out of memory, clean up */
			    for (pnxt = *notfounds ; *pnxt ; pnxt++)
				free(*pnxt);
			    free(*notfounds);
			    *notfounds = (char **) NULL;
			    nonotfounds = TRUE;
			    nobaderr = FALSE;
			}
		    }

		}    /* end if (!found) */

		/* Terminate the not-found list */
		if (!nonotfounds) *pnxt = (char *) NULL;

	    }	/* end (for each attribute in attribute list) loop */


	    /*
	     * If we haven't seen any problems so far,
	     * write the new device table
	     */

	    if (nobaderr) {

		/* Open the new device table */
		if (fd = opennewdevtab(&tempname)) {

		    /*
		     * For each entry in the existing table, write that entry
		     * to the new table.  If the entry is the one being
		     * modified, write the modified entry instead of the
		     * original entry.
		     */

		    _setdevtab();		/* Rewind existing table */
		    while ((devtabent = _getdevtabent()) && nobaderr) {
			if (devtabent->entryno != modent->entryno)
			    nobaderr = _putdevtabrec(fd, devtabent) != EOF;
			else nobaderr = _putdevtabrec(fd, modent) != EOF;
			_freedevtabent(devtabent);
		    }

		    /*
		     * If we successfully generated the new table, make it the
		     * new system device table.  Otherwise, just remove the
		     * temporary file we've created.
		     */

		    if (nobaderr) {
			(void) fclose(fd);
			nobaderr = mknewdevtab(tempname);
		    } else {
			(void) fclose(fd);
			(void) rmnewdevtab(tempname);
		    }

		}   /* if (_opennewdevtab()) */
	    	else nobaderr = FALSE;

		/* If there was some error, we need to clean up
		   allocated resources */
		if (!nobaderr && !nonotfounds) {
		    for (pnxt = *notfounds ; *pnxt ; pnxt++)
			free(*pnxt);
		    free(*notfounds);
		    *notfounds = (char **) NULL;
		    nonotfounds = TRUE;
		}

	    }	/* if (nobaderr) */

	    /* Free the resources alloc'ed for <device>'s entry */
	    _freedevtabent(modent);

	} else {
	    /* _getdevrec(device) failed */
	    nobaderr = FALSE;
	    *notfounds = (char **) NULL;
	}

	/* Unlock the device table */
	(void) unlkdevtab();

	/* We're finished */
	return(noerr && nobaderr);
}
