/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <utime.h>
#include <synch.h>
#include <strings.h>
#include <string.h>
#include <libintl.h>
#include <errno.h>
#include <auth_list.h>
#include <syslog.h>
#include <bsm/devices.h>
#include <bsm/devalloc.h>
#include <tsol/label.h>

#define	DA_DEFS	"/etc/security/tsol/devalloc_defaults"

extern int _readbufline(char *, int, char *, int, int *);
extern char *strtok_r(char *, const char *, char **);
extern char *_strtok_escape(char *, char *, char **);
extern int getdaon(void);
extern int da_matchname(devalloc_t *, char *);
extern int da_match(devalloc_t *, da_args *);
extern int dmap_matchname(devmap_t *, char *);
extern int dm_match(devmap_t *, da_args *);
extern int dmap_matchtype(devmap_t *dmap, char *type);
extern int dmap_matchdev(devmap_t *dmap, char *dev);
extern int dmap_exact_dev(devmap_t *dmap, char *dev, int *num);
extern char *dmap_physname(devmap_t *dmap);

/*
 * The following structure is for recording old entries to be retained.
 * We read the entries from the database into a linked list in memory,
 * then turn around and write them out again.
 */
typedef struct strentry {
	struct strentry	*se_next;
	char		se_str[4096 + 1];
} strentry_t;

/*
 * da_check_longindevperm -
 *	reads /etc/logindevperm and checks if specified device is in the file.
 *	returns 1 if specified device found in /etc/logindevperm, else returns 0
 */
int
da_check_logindevperm(char *devname)
{
	int		ret = 0;
	int		fd = -1;
	int		nlen, plen, slen, lineno, fsize;
	char		line[MAX_CANON];
	char		*field_delims = " \t\n";
	char		*fbuf = NULL;
	char		*ptr, *device;
	char		*lasts = NULL;
	FILE		*fp;
	struct stat	f_stat;

	/*
	 * check if /etc/logindevperm exists and get its size
	 */
	if ((fd = open(LOGINDEVPERM, O_RDONLY)) == -1)
		return (0);
	if (fstat(fd, &f_stat) != 0) {
		(void) close(fd);
		return (0);
	}
	fsize = f_stat.st_size;
	if ((fbuf = (char *)malloc(fsize)) == NULL) {
		(void) close(fd);
		return (0);
	}
	if ((fp = fdopen(fd, "rF")) == NULL) {
		free(fbuf);
		(void) close(fd);
		return (0);
	}

	/*
	 * read and parse /etc/logindevperm
	 */
	plen = nlen = lineno = 0;
	while (fgets(line, MAX_CANON, fp) != NULL) {
		lineno++;
		if ((ptr = strchr(line, '#')) != NULL)
			*ptr = '\0';	/* handle comments */
		if (strtok_r(line, field_delims, &lasts) == NULL)
			continue;	/* ignore blank lines */
		if (strtok_r(NULL, field_delims, &lasts) == NULL)
			/* invalid entry */
			continue;
		if ((ptr = strtok_r(NULL, field_delims, &lasts)) == NULL)
			/* empty device list */
			continue;
		nlen = strlen(ptr) + 1;		/* +1 terminator */
		nlen += (plen + 1);
		if (plen == 0)
			slen = snprintf(fbuf, nlen, "%s", ptr);
		else
			slen = snprintf(fbuf + plen, nlen - plen, ":%s", ptr);
		if (slen >= fsize) {
			fbuf[0] = '\0';
			(void) fclose(fp);
			return (slen);
		}
		plen += slen;
	}
	(void) fclose(fp);

	/*
	 * check if devname exists in /etc/logindevperm
	 */
	device = strtok_r(fbuf, ":", &lasts);
	while (device != NULL) {
		/*
		 * device and devname may be one of these types -
		 *    /dev/xx
		 *    /dev/xx*
		 *    /dev/dir/xx
		 *    /dev/dir/xx*
		 *    /dev/dir/"*"
		 */
		if (strcmp(device, devname) == 0) {
			/* /dev/xx, /dev/dir/xx */
			free(fbuf);
			return (1);
		}
		if ((ptr = strrchr(device, KV_WILDCHAR)) != NULL) {
			/* all wildcard types */
			*ptr = '\0';
			if (strncmp(device, devname, strlen(device)) == 0) {
				free(fbuf);
				return (1);
			}
		}
		device = strtok_r(NULL, ":", &lasts);
	}

	return (ret);
}

/*
 * _da_read_file -
 *	establishes readers/writer lock on fname; reads in the file if its
 *	contents changed since the last time we read it.
 *	returns size of buffer read, or -1 on failure.
 */
int
_da_read_file(char *fname, char **fbuf, time_t *ftime, rwlock_t *flock,
    int flag)
{
	int		fd = -1;
	int		fsize = 0;
	time_t		newtime;
	struct stat	f_stat;

	if (flag & DA_FORCE)
		*ftime = 0;

	/* check the size and the time stamp on the file */
	if (rw_rdlock(flock) != 0)
		return (-1);
	if (stat(fname, &f_stat) != 0) {
		(void) rw_unlock(flock);
		return (-1);
	}
	fsize = f_stat.st_size;
	newtime = f_stat.st_mtime;
	(void) rw_unlock(flock);

	while (newtime > *ftime) {
		/*
		 * file has been modified since we last read it; or this
		 * is a forced read.
		 * read file into the buffer with rw lock.
		 */
		if (rw_wrlock(flock) != 0)
			return (-1);
		if ((fd = open(fname, O_RDONLY)) == -1) {
			(void) rw_unlock(flock);
			return (-1);
		}
		if (*fbuf != NULL) {
			free(*fbuf);
			*fbuf = NULL;
		}
		if ((*fbuf = malloc(fsize)) == NULL) {
			(void) rw_unlock(flock);
			(void) close(fd);
			return (-1);
		}
		if (read(fd, *fbuf, fsize) < fsize) {
			free(*fbuf);
			(void) rw_unlock(flock);
			(void) close(fd);
			return (-1);
		}
		(void) rw_unlock(flock);
		/*
		 * verify that the file did not change just after we read it.
		 */
		if (rw_rdlock(flock) != 0) {
			free(*fbuf);
			(void) close(fd);
			return (-1);
		}
		if (stat(fname, &f_stat) != 0) {
			free(*fbuf);
			(void) rw_unlock(flock);
			(void) close(fd);
			return (-1);
		}
		fsize = f_stat.st_size;
		newtime = f_stat.st_mtime;
		(void) rw_unlock(flock);
		(void) close(fd);
		*ftime = newtime;
	}

	return (fsize);
}

/*
 * _update_zonename -
 *	add/remove current zone's name to the given devalloc_t.
 */
void
_update_zonename(da_args *dargs, devalloc_t *dap)
{
	int		i, j;
	int		oldsize, newsize;
	int		has_zonename = 0;
	char		*zonename;
	kva_t		*newkva, *oldkva;
	kv_t		*newdata, *olddata;
	devinfo_t	*devinfo;

	devinfo = dargs->devinfo;
	oldkva = dap->da_devopts;
	if (oldkva == NULL) {
		if (dargs->optflag & DA_REMOVE_ZONE)
			return;
		if (dargs->optflag & DA_ADD_ZONE) {
			newkva = _str2kva(devinfo->devopts, KV_ASSIGN,
			    KV_TOKEN_DELIMIT);
			if (newkva != NULL)
				dap->da_devopts = newkva;
			return;
		}
	}
	newsize = oldsize = oldkva->length;
	if (kva_match(oldkva, DAOPT_ZONE))
		has_zonename = 1;
	if (dargs->optflag & DA_ADD_ZONE) {
		if ((zonename = index(devinfo->devopts, '=')) == NULL)
			return;
		zonename++;
		if (has_zonename) {
			(void) _insert2kva(oldkva, DAOPT_ZONE, zonename);
			return;
		}
		newsize += 1;
	} else if (dargs->optflag & DA_REMOVE_ZONE) {
		if (has_zonename) {
			newsize -= 1;
			if (newsize == 0) {
				/*
				 * If zone name was the only key/value pair,
				 * put 'reserved' in the empty slot.
				 */
				_kva_free(oldkva);
				dap->da_devopts = NULL;
				return;
			}
		} else {
			return;
		}
	}
	newkva = _new_kva(newsize);
	newkva->length = 0;
	newdata = newkva->data;
	olddata = oldkva->data;
	for (i = 0, j = 0; i < oldsize; i++) {
		if ((dargs->optflag & DA_REMOVE_ZONE) &&
		    (strcmp(olddata[i].key, DAOPT_ZONE) == 0))
			continue;
		newdata[j].key = strdup(olddata[i].key);
		newdata[j].value = strdup(olddata[i].value);
		newkva->length++;
		j++;
	}
	if (dargs->optflag & DA_ADD_ZONE) {
		newdata[j].key = strdup(DAOPT_ZONE);
		newdata[j].value = strdup(zonename);
		newkva->length++;
	}
	_kva_free(oldkva);
	dap->da_devopts = newkva;
}

/*
 * _dmap2str -
 *	converts a device_map entry into a printable string
 *	returns 0 on success, -1 on error.
 */
/*ARGSUSED*/
static int
_dmap2str(devmap_t *dmp, char *buf, int size, const char *sep)
{
	int	length;

	length = snprintf(buf, size, "%s%s", dmp->dmap_devname, sep);
	if (length >= size)
		return (-1);
	length += snprintf(buf + length, size - length, "%s%s",
	    dmp->dmap_devtype, sep);
	if (length >= size)
		return (-1);
	length += snprintf(buf + length, size - length, "%s\n",
	    dmp->dmap_devlist);
	if (length >= size)
		return (-1);
	return (0);
}

/*
 * _dmap2strentry -
 *	calls dmap2str to break given devmap_t into printable entry.
 *	returns pointer to decoded entry, NULL on error.
 */
static strentry_t *
_dmap2strentry(devmap_t *devmapp)
{
	strentry_t	*sep;

	if ((sep = (strentry_t *)malloc(sizeof (strentry_t))) == NULL)
		return (NULL);
	if (_dmap2str(devmapp, sep->se_str, sizeof (sep->se_str),
	    KV_TOKEN_DELIMIT"\\\n\t") != 0) {
		free(sep);
		return (NULL);
	}
	return (sep);
}

/*
 * fix_optstr -
 * 	removes trailing ':' from buf.
 */
void
fix_optstr(char *buf)
{
	char	*p = NULL;

	if (p = rindex(buf, ':'))
		*p = ';';
}

/*
 * _da2str -
 *	converts a device_allocate entry into a printable string
 *	returns 0 on success, -1 on error.
 */
static int
_da2str(da_args *dargs, devalloc_t *dap, char *buf, int size, const char *sep,
    const char *osep)
{
	int	length;
	int	matching_entry = 0;
	char	**dnames;

	if (dargs->optflag & DA_UPDATE &&
	    (dargs->optflag & DA_ADD_ZONE ||
	    dargs->optflag & DA_REMOVE_ZONE) &&
	    dargs->devnames) {
		for (dnames = dargs->devnames; *dnames != NULL; dnames++) {
			if (da_matchname(dap, *dnames)) {
				matching_entry = 1;
				break;
			}
		}
	}
	length = snprintf(buf, size, "%s%s", dap->da_devname, sep);
	if (length >= size)
		return (-1);
	length += snprintf(buf + length, size - length, "%s%s",
	    dap->da_devtype, sep);
	if (length >= size)
		return (-1);
	if (matching_entry)
		_update_zonename(dargs, dap);
	if ((dap->da_devopts == NULL) || ((dap->da_devopts->length == 1) &&
	    (strcmp(dap->da_devopts->data->key, DA_RESERVED) == 0))) {
		length += snprintf(buf + length, size - length, "%s%s",
		    DA_RESERVED, sep);
	} else {
		if (_kva2str(dap->da_devopts, buf + length, size - length,
		    KV_ASSIGN, (char *)osep) != 0)
			return (-1);
		length = strlen(buf);
	}
	if (dap->da_devopts)
		fix_optstr(buf);
	if (length >= size)
		return (-1);
	length += snprintf(buf + length, size - length, "%s%s",
	    DA_RESERVED, sep);
	if (length >= size)
		return (-1);
	length += snprintf(buf + length, size - length, "%s%s",
	    dap->da_devauth ? dap->da_devauth : DA_ANYUSER, sep);
	if (length >= size)
		return (-1);
	length += snprintf(buf + length, size - length, "%s\n",
	    dap->da_devexec ? dap->da_devexec : "");
	if (length >= size)
		return (-1);

	return (0);
}

/*
 * _da2strentry -
 *	calls da2str to break given devalloc_t into printable entry.
 *	returns pointer to decoded entry, NULL on error.
 */
static strentry_t *
_da2strentry(da_args *dargs, devalloc_t *dap)
{
	strentry_t	*sep;

	if ((sep = (strentry_t *)malloc(sizeof (strentry_t))) == NULL)
		return (NULL);
	if (_da2str(dargs, dap, sep->se_str, sizeof (sep->se_str),
	    KV_DELIMITER "\\\n\t", KV_TOKEN_DELIMIT "\\\n\t") != 0) {
		free(sep);
		return (NULL);
	}
	return (sep);
}

/*
 * _def2str
 *	converts da_defs_t into a printable string.
 *	returns 0 on success, -1 on error.
 */
static int
_def2str(da_defs_t *da_defs, char *buf, int size, const char *sep)
{
	int length;

	length = snprintf(buf, size, "%s%s", da_defs->devtype, sep);
	if (length >= size)
		return (-1);
	if (da_defs->devopts) {
		if (_kva2str(da_defs->devopts, buf + length, size - length,
		    KV_ASSIGN, KV_DELIMITER) != 0)
			return (-1);
		length = strlen(buf);
	}
	if (length >= size)
		return (-1);

	return (0);
}

/*
 * _def2strentry
 *	calls _def2str to break given da_defs_t into printable entry.
 *	returns pointer decoded entry, NULL on error.
 */
static strentry_t *
_def2strentry(da_defs_t *da_defs)
{
	strentry_t	*sep;

	if ((sep = (strentry_t *)malloc(sizeof (strentry_t))) == NULL)
		return (NULL);
	if (_def2str(da_defs, sep->se_str, sizeof (sep->se_str),
	    KV_TOKEN_DELIMIT) != 0) {
		free(sep);
		return (NULL);
	}

	return (sep);
}

/*
 * _build_defattrs
 *	cycles through all defattr entries, stores them in memory. removes
 *	entries with the given search_key (device type).
 *	returns 0 if given entry not found, 1 if given entry removed, 2 on
 *	error.
 */
static int
_build_defattrs(da_args *dargs, strentry_t **head_defent)
{
	int		rc = 0;
	da_defs_t	*da_defs;
	strentry_t	*tail_str, *tmp_str;

	setdadefent();
	while ((da_defs = getdadefent()) != NULL) {
		rc = !(strcmp(da_defs->devtype, dargs->devinfo->devtype));
		if (rc && dargs->optflag & DA_ADD &&
		    !(dargs->optflag & DA_FORCE)) {
			/*
			 * During DA_ADD, we keep an existing entry unless
			 * we have DA_FORCE set to override that entry.
			 */
			dargs->optflag |= DA_NO_OVERRIDE;
			rc = 0;
		}
		if (rc == 0) {
			tmp_str = _def2strentry(da_defs);
			if (tmp_str == NULL) {
				freedadefent(da_defs);
				enddadefent();
				return (2);
			}
			/* retaining defattr entry: tmp_str->se_str */
			tmp_str->se_next = NULL;
			if (*head_defent == NULL) {
				*head_defent = tail_str = tmp_str;
			} else {
				tail_str->se_next = tmp_str;
				tail_str = tmp_str;
			}
		}
		freedadefent(da_defs);
	}
	enddadefent();

	return (rc);
}

/*
 * We have to handle the "standard" types in devlist differently than
 * other devices, which are not covered by our auto-naming conventions.
 *
 * buf must be a buffer of size DA_MAX_NAME + 1
 */
int
da_std_type(da_args *dargs, char *namebuf)
{
	char *type = dargs->devinfo->devtype;
	int system_labeled;

	system_labeled = is_system_labeled();

	/* check safely for sizes */
	if (strcmp(DA_AUDIO_TYPE, type) == 0) {
		(void) strlcpy(namebuf, DA_AUDIO_NAME, DA_MAXNAME);
		return (1);
	}
	if (strcmp(DA_CD_TYPE, type) == 0) {
		if (system_labeled)
			(void) strlcpy(namebuf, DA_CD_NAME, DA_MAXNAME);
		else
			(void) strlcpy(namebuf, DA_CD_TYPE, DA_MAXNAME);
		return (1);
	}
	if (strcmp(DA_FLOPPY_TYPE, type) == 0) {
		if (system_labeled)
			(void) strlcpy(namebuf, DA_FLOPPY_NAME, DA_MAXNAME);
		else
			(void) strlcpy(namebuf, DA_FLOPPY_TYPE, DA_MAXNAME);
		return (1);
	}
	if (strcmp(DA_TAPE_TYPE, type) == 0) {
		if (system_labeled)
			(void) strlcpy(namebuf, DA_TAPE_NAME, DA_MAXNAME);
		else
			(void) strlcpy(namebuf, DA_TAPE_TYPE, DA_MAXNAME);
		return (1);
	}
	if (strcmp(DA_RMDISK_TYPE, type) == 0) {
		(void) strlcpy(namebuf, DA_RMDISK_NAME, DA_MAXNAME);
		return (1);
	}
	namebuf[0] = '\0';
	return (0);
}

/*
 * allocatable: returns
 * -1 if no auths field,
 * 0 if not allocatable (marked '*')
 * 1 if not marked '*'
 */
static int
allocatable(da_args *dargs)
{

	if (!dargs->devinfo->devauths)
		return (-1);
	if (strcmp("*", dargs->devinfo->devauths) == 0)
		return (0);
	return (1);
}

/*
 * _rebuild_lists -
 *
 *	If dargs->optflag & DA_EVENT, does not assume the dargs list is
 *	complete or completely believable, since devfsadm caches
 *	ONLY what it has been exposed to via syseventd.
 *
 *	Cycles through all the entries in the /etc files, stores them
 *	in memory, takes note of device->dname numbers (e.g. rmdisk0,
 *	rmdisk12)
 *
 *	Cycles through again, adds dargs entry
 *	with the name tname%d (lowest unused number for the device type)
 *	to the list of things for the caller to write out to a file,
 *	IFF it is a new entry.
 *
 *	It is an error for it to already be there, if it is allocatable.
 *
 *	Add:
 *	    Returns 0 if successful and 2 on error.
 *	Remove:
 *	    Returns 0 if not found, 1 if found,  2 on error.
 */
static int
_rebuild_lists(da_args *dargs, strentry_t **head_devallocp,
    strentry_t **head_devmapp)
{
	int		rc = 0;
	devalloc_t	*devallocp;
	devmap_t	*devmapp;
	strentry_t	*tail_str;
	strentry_t	*tmp_str;
	uint64_t	tmp_bitmap = 0;
	uint_t		tmp = 0;
	char		*realname;
	int		suffix;
	int		found = 0;
	int		stdtype = 1;
	int		is_allocatable = 1;
	char		new_devname[DA_MAXNAME + 1];
	char		defname[DA_MAXNAME + 1]; /* default name for type */
	char		errmsg[DA_MAXNAME + 1 + (PATH_MAX * 2) + 80];

	if (dargs->optflag & (DA_MAPS_ONLY | DA_ALLOC_ONLY))
		return (2);

	if (dargs->optflag & DA_FORCE)
		return (2);

	if (dargs->optflag & DA_ADD) {
		stdtype = da_std_type(dargs, defname);
		is_allocatable = allocatable(dargs);
	}

	/* read both files, maps first so we can compare actual devices */

	/* build device_maps */
	setdmapent();
	while ((devmapp = getdmapent()) != NULL) {
		suffix = DA_MAX_DEVNO + 1;
		if ((rc = dmap_matchtype(devmapp, dargs->devinfo->devtype))
		    == 1) {
			if (dargs->optflag & DA_REMOVE) {
				if ((devmapp->dmap_devarray == NULL) ||
				    (devmapp->dmap_devarray[0] == NULL)) {
					freedmapent(devmapp);
					enddmapent();
					return (2);
				}
				realname = dmap_physname(devmapp);
				if (realname == NULL) {
					freedmapent(devmapp);
					enddmapent();
					return (2);
				}
				if (strstr(realname, dargs->devinfo->devlist)
				    != NULL) {
					/* if need to free and safe to free */
					if (dargs->devinfo->devname != NULL &&
					    (dargs->optflag & DA_EVENT) != 0)
						free(dargs->devinfo->devname);
					dargs->devinfo->devname =
					    strdup(devmapp->dmap_devname);
					found = 1;
					freedmapent(devmapp);
					continue; /* don't retain */
				}
			} else if (dargs->optflag & DA_ADD) {
				/*
				 * Need to know which suffixes are in use
				 */
				rc = (dmap_exact_dev(devmapp,
				    dargs->devinfo->devlist, &suffix));

				if (rc == 0) {
					/*
					 * Same type, different device.  Record
					 * device suffix already in use, if
					 * applicable.
					 */
					if ((suffix < DA_MAX_DEVNO &&
					    suffix != -1) && stdtype)
						tmp_bitmap |=
						    (uint64_t)(1LL << suffix);
				} else if ((rc == 1) && !is_allocatable) {
					rc = 0;
				} else {
					/*
					 * Match allocatable on add is an error
					 * or mapping attempt returned error
					 */
					(void) snprintf(errmsg, sizeof (errmsg),
					    "Cannot add %s on node %s",
					    dargs->devinfo->devtype,
					    devmapp->dmap_devname);
					syslog(LOG_ERR, "%s", errmsg);
					freedmapent(devmapp);
					enddmapent();
					return (2);
				}
			} else
				/* add other transaction types as needed */
				return (2);
		} else if ((dargs->optflag & DA_ADD) &&
		    (stdtype || is_allocatable) &&
		    dmap_exact_dev(devmapp, dargs->devinfo->devlist,
		    &suffix)) {
			/*
			 * no dups w/o DA_FORCE, even if type differs,
			 * if there is a chance this operation is
			 * machine-driven.  The 5 "standard types"
			 * can be machine-driven adds, and tend to
			 * be allocatable.
			 */
			(void) snprintf(errmsg, sizeof (errmsg),
			    "Cannot add %s on node %s type %s",
			    dargs->devinfo->devtype,
			    devmapp->dmap_devname,
			    devmapp->dmap_devtype);
			syslog(LOG_ERR, "%s", errmsg);
			freedmapent(devmapp);
			enddmapent();
			return (2);
		}

		tmp_str = _dmap2strentry(devmapp);
		if (tmp_str == NULL) {
			freedmapent(devmapp);
			enddmapent();
			return (2);
		}
		/* retaining devmap entry: tmp_str->se_str */
		tmp_str->se_next = NULL;
		if (*head_devmapp == NULL) {
			*head_devmapp = tail_str = tmp_str;
		} else {
			tail_str->se_next = tmp_str;
			tail_str = tmp_str;
		}
		freedmapent(devmapp);
	}
	enddmapent();

	/*
	 * No need to rewrite the files if the item to be removed is not
	 * in the files -- wait for another call on another darg.
	 */
	if ((dargs->optflag & DA_REMOVE) && !found)
		return (0);


	if (dargs->optflag & DA_ADD) {
		int len;
		/*
		 * If we got here from an event, or from devfsadm,
		 * we know the stored devname is a useless guess,
		 * since the files had not been read when the name
		 * was chosen, and we don't keep them anywhere else
		 * that is sufficiently definitive.
		 */

		for (tmp = 0; tmp <= DA_MAX_DEVNO; tmp++)
			if (!(tmp_bitmap & (1LL << tmp)))
				break;
		/* Future: support more than 64 hotplug devices per type? */
		if (tmp > DA_MAX_DEVNO)
			return (2);

		/*
		 * Let the caller choose the name unless BOTH the name and
		 * device type one of: cdrom, floppy, audio, rmdisk, or tape.
		 * (or sr, fd for unlabeled)
		 */
		len = strlen(defname);
		if (stdtype &&
		    (strncmp(dargs->devinfo->devname, defname, len) == 0)) {
			(void) snprintf(new_devname, DA_MAXNAME + 1, "%s%u",
			    defname, tmp);
			/* if need to free and safe to free */
			if (dargs->devinfo->devname != NULL &&
			    (dargs->optflag & DA_EVENT) != 0)
				free(dargs->devinfo->devname);
			dargs->devinfo->devname = strdup(new_devname);
		}
	}

	/*
	 * Now adjust devalloc list to match devmaps
	 * Note we now have the correct devname for da_match to use.
	 */
	setdaent();
	while ((devallocp = getdaent()) != NULL) {
		rc = da_match(devallocp, dargs);
		if (rc == 1) {
			if (dargs->optflag & DA_ADD) {
				/* logging is on if DA_EVENT is set */
				if (dargs->optflag & DA_EVENT) {
					(void) snprintf(errmsg, sizeof (errmsg),
					    "%s and %s out of sync,"
					    "%s only in %s.",
					    DEVALLOC, DEVMAP,
					    devallocp->da_devname, DEVALLOC);
					syslog(LOG_ERR, "%s", errmsg);
				}
				freedaent(devallocp);
				enddaent();
				return (2);
			} else if (dargs->optflag & DA_REMOVE) {
				/* make list w/o this entry */
				freedaent(devallocp);
				continue;
			}
		}
		tmp_str = _da2strentry(dargs, devallocp);
		if (tmp_str == NULL) {
			freedaent(devallocp);
			enddaent();
			return (2);
		}
		/* retaining devalloc entry: tmp_str->se_str */
		tmp_str->se_next = NULL;
		if (*head_devallocp == NULL) {
			*head_devallocp = tail_str = tmp_str;
		} else {
			tail_str->se_next = tmp_str;
			tail_str = tmp_str;
		}
		freedaent(devallocp);
	}
	enddaent();

	/* the caller needs to know if a remove needs to rewrite files */
	if (dargs->optflag & DA_REMOVE)
		return (1);  /* 0 and 2 cases returned earlier */

	return (0);  /* Successful DA_ADD */
}

/*
 * _build_lists -
 *	Cycles through all the entries, stores them in memory. removes entries
 *	with the given search_key (device name or type).
 *	returns 0 if given entry not found, 1 if given entry removed, 2 on
 *	error.
 */
static int
_build_lists(da_args *dargs, strentry_t **head_devallocp,
    strentry_t **head_devmapp)
{
	int		rc = 0;
	int		found = 0;
	devalloc_t	*devallocp;
	devmap_t	*devmapp;
	strentry_t	*tail_str;
	strentry_t	*tmp_str;

	if (dargs->optflag & DA_MAPS_ONLY)
		goto dmap_only;

	/* build device_allocate */
	setdaent();
	while ((devallocp = getdaent()) != NULL) {
		rc = da_match(devallocp, dargs);
		/* if in _build_lists and DA_ADD is set, so is DA_FORCE */
		if (rc == 0) {
			tmp_str = _da2strentry(dargs, devallocp);
			if (tmp_str == NULL) {
				freedaent(devallocp);
				enddaent();
				return (2);
			}
			/* retaining devalloc entry: tmp_str->se_str */
			tmp_str->se_next = NULL;
			if (*head_devallocp == NULL) {
				*head_devallocp = tail_str = tmp_str;
			} else {
				tail_str->se_next = tmp_str;
				tail_str = tmp_str;
			}
		} else if (rc == 1)
			found = 1;

		freedaent(devallocp);
	}
	enddaent();

dmap_only:
	if (dargs->optflag & DA_ALLOC_ONLY)
		return (rc);

	/* build device_maps */
	rc = 0;
	setdmapent();
	while ((devmapp = getdmapent()) != NULL) {
		rc = dm_match(devmapp, dargs);
		if (rc == 0) {
			tmp_str = _dmap2strentry(devmapp);
			if (tmp_str == NULL) {
				freedmapent(devmapp);
				enddmapent();
				return (2);
			}
			/* retaining devmap entry: tmp_str->se_str */
			tmp_str->se_next = NULL;
			if (*head_devmapp == NULL) {
				*head_devmapp = tail_str = tmp_str;
			} else {
				tail_str->se_next = tmp_str;
				tail_str = tmp_str;
			}
		}
		freedmapent(devmapp);
	}
	enddmapent();

	/* later code cleanup may cause the use of "found" in other cases */
	if (dargs->optflag & DA_REMOVE)
		return (found);
	return (rc);
}

/*
 * _write_defattrs
 *	writes current entries to devalloc_defaults.
 */
static void
_write_defattrs(FILE *fp, strentry_t *head_defent)
{
	strentry_t *tmp_str;

	for (tmp_str = head_defent; tmp_str != NULL;
	    tmp_str = tmp_str->se_next) {
		(void) fputs(tmp_str->se_str, fp);
		(void) fputs("\n", fp);
	}

}

/*
 * _write_device_allocate -
 *	writes current entries in the list to device_allocate.
 *	frees the strings
 */
static void
_write_device_allocate(char *odevalloc, FILE *dafp, strentry_t *head_devallocp)
{
	int		is_on = -1;
	strentry_t	*tmp_str, *old_str;
	struct stat	dastat;

	(void) fseek(dafp, (off_t)0, SEEK_SET);

	/*
	 * if the devalloc on/off string existed before,
	 * put it back before anything else.
	 * we need to check for the string only if the file
	 * exists.
	 */
	if (stat(odevalloc, &dastat) == 0) {
		is_on = da_is_on();
		if (is_on == 0)
			(void) fputs(DA_OFF_STR, dafp);
		else if (is_on == 1)
			(void) fputs(DA_ON_STR, dafp);
	}
	tmp_str = head_devallocp;
	while (tmp_str) {
		(void) fputs(tmp_str->se_str, dafp);
		(void) fputs("\n", dafp);
		old_str = tmp_str;
		tmp_str = tmp_str->se_next;
		free(old_str);
	}
}

/*
 * _write_device_maps -
 *	writes current entries in the list to device_maps.
 *	and frees the strings
 */
static void
_write_device_maps(FILE *dmfp, strentry_t *head_devmapp)
{
	strentry_t	*tmp_str, *old_str;

	(void) fseek(dmfp, (off_t)0, SEEK_SET);

	tmp_str = head_devmapp;
	while (tmp_str) {
		(void) fputs(tmp_str->se_str, dmfp);
		(void) fputs("\n", dmfp);
		old_str = tmp_str;
		tmp_str = tmp_str->se_next;
		free(old_str);
	}
}

/*
 * _write_new_defattrs
 *	writes the new entry to devalloc_defaults.
 *	returns 0 on success, -1 on error.
 */
static int
_write_new_defattrs(FILE *fp, da_args *dargs)
{
	int		count;
	char		*tok = NULL, *tokp = NULL;
	char		*lasts;
	devinfo_t	*devinfo = dargs->devinfo;

	if (fseek(fp, (off_t)0, SEEK_END) == (off_t)-1)
		return (-1);
	if (!devinfo->devopts)
		return (0);
	(void) fprintf(fp, "%s%s", (devinfo->devtype ? devinfo->devtype : ""),
	    KV_TOKEN_DELIMIT);
	if ((tokp = (char *)malloc(strlen(devinfo->devopts) +1)) != NULL) {
		(void) strcpy(tokp, devinfo->devopts);
		if ((tok = strtok_r(tokp, KV_DELIMITER, &lasts)) != NULL) {
			(void) fprintf(fp, "%s", tok);
			count = 1;
		}
		while ((tok = strtok_r(NULL, KV_DELIMITER, &lasts)) != NULL) {
			if (count)
				(void) fprintf(fp, "%s", KV_DELIMITER);
			(void) fprintf(fp, "%s", tok);
			count++;
		}
	} else {
		(void) fprintf(fp, "%s", devinfo->devopts);
	}

	return (0);
}

/*
 * _write_new_entry -
 *	writes the new devalloc_t to device_allocate or the new devmap_t to
 *	device_maps.
 *	returns 0 on success, -1 on error.
 */
static int
_write_new_entry(FILE *fp, da_args *dargs, int flag)
{
	int		count;
	char		*tok = NULL, *tokp = NULL;
	char		*lasts;
	devinfo_t	*devinfo = dargs->devinfo;

	if (flag & DA_MAPS_ONLY)
		goto dmap_only;

	if (fseek(fp, (off_t)0, SEEK_END) == (off_t)-1)
		return (-1);

	(void) fprintf(fp, "%s%s\\\n\t",
	    (devinfo->devname ? devinfo->devname : ""), KV_DELIMITER);
	(void) fprintf(fp, "%s%s\\\n\t",
	    (devinfo->devtype ? devinfo->devtype : ""), KV_DELIMITER);
	if (devinfo->devopts == NULL) {
		(void) fprintf(fp, "%s%s\\\n\t", DA_RESERVED,
		    KV_DELIMITER);
	} else {
		if ((tokp = (char *)malloc(strlen(devinfo->devopts) + 1))
		    != NULL) {
			(void) strcpy(tokp, devinfo->devopts);
			if ((tok = strtok_r(tokp, KV_TOKEN_DELIMIT, &lasts)) !=
			    NULL) {
				(void) fprintf(fp, "%s", tok);
				count = 1;
			}
			while ((tok = strtok_r(NULL, KV_TOKEN_DELIMIT,
			    &lasts)) != NULL) {
				if (count)
					(void) fprintf(fp, "%s",
					    KV_TOKEN_DELIMIT "\\\n\t");
				(void) fprintf(fp, "%s", tok);
				count++;
			}
			if (count)
				(void) fprintf(fp, "%s",
				    KV_DELIMITER "\\\n\t");
		} else {
			(void) fprintf(fp, "%s%s", devinfo->devopts,
			    KV_DELIMITER "\\\n\t");
		}
	}
	(void) fprintf(fp, "%s%s\\\n\t", DA_RESERVED, KV_DELIMITER);
	(void) fprintf(fp, "%s%s\\\n\t",
	    (devinfo->devauths ? devinfo->devauths : DA_ANYUSER),
	    KV_DELIMITER);
	(void) fprintf(fp, "%s\n",
	    (devinfo->devexec ? devinfo->devexec : KV_DELIMITER));

dmap_only:
	if (flag & DA_ALLOC_ONLY)
		return (0);

	if (fseek(fp, (off_t)0, SEEK_END) == (off_t)-1)
		return (-1);

	(void) fprintf(fp, "%s%s\\\n",
	    (devinfo->devname ? devinfo->devname : ""), KV_TOKEN_DELIMIT);
	(void) fprintf(fp, "\t%s%s\\\n",
	    (devinfo->devtype ? devinfo->devtype : ""), KV_TOKEN_DELIMIT);
	(void) fprintf(fp, "\t%s\n",
	    (devinfo->devlist ? devinfo->devlist : KV_TOKEN_DELIMIT));

	return (0);
}

/*
 * _da_lock_devdb -
 *	locks the database files; lock can be either broken explicitly by
 *	closing the fd of the lock file, or it expires automatically at process
 *	termination.
 * 	returns fd of the lock file or -1 on error.
 */
int
_da_lock_devdb(char *rootdir)
{
	int		lockfd = -1;
	int		ret;
	int		count = 0;
	int		retry = 10;
	int		retry_sleep;
	uint_t		seed;
	char		*lockfile;
	char		path[MAXPATHLEN];
	int		size = sizeof (path);

	if (rootdir == NULL) {
		lockfile = DA_DB_LOCK;
	} else {
		path[0] = '\0';
		if (snprintf(path, size, "%s%s", rootdir, DA_DB_LOCK) >= size)
			return (-1);
		lockfile = path;
	}

	if ((lockfd = open(lockfile, O_RDWR | O_CREAT, 0600)) == -1)
		/* cannot open lock file */
		return (-1);

	(void) fchown(lockfd, DA_UID, DA_GID);

	if (lseek(lockfd, (off_t)0, SEEK_SET) == -1) {
		/* cannot position lock file */
		(void) close(lockfd);
		return (-1);
	}
	errno = 0;
	while (retry > 0) {
		count++;
		seed = (uint_t)gethrtime();
		ret = lockf(lockfd, F_TLOCK, 0);
		if (ret == 0) {
			(void) utime(lockfile, NULL);
			return (lockfd);
		}
		if ((errno != EACCES) && (errno != EAGAIN)) {
			/* cannot set lock */
			(void) close(lockfd);
			return (-1);
		}
		retry--;
		retry_sleep = rand_r(&seed)/((RAND_MAX + 2)/3) + count;
		(void) sleep(retry_sleep);
		errno = 0;
	}

	return (-1);
}

/*
 * da_open_devdb -
 *	opens one or both database files - device_allocate, device_maps - in
 *	the specified mode.
 *	locks the database files; lock is either broken explicitly by the
 *	caller by closing the lock file fd, or it expires automatically at
 *	process termination.
 *	writes the file pointer of opened file in the input args - dafp, dmfp.
 *	returns fd of the lock file on success, -2 if database file does not
 *	exist, -1 on other errors.
 */
int
da_open_devdb(char *rootdir, FILE **dafp, FILE **dmfp, int flag)
{
	int	oflag = 0;
	int	fda = -1;
	int	fdm = -1;
	int	lockfd = -1;
	char	*fname;
	char	*fmode;
	char	path[MAXPATHLEN];
	FILE	*devfile;

	if ((dafp == NULL) && (dmfp == NULL))
		return (-1);

	if (flag & DA_RDWR) {
		oflag = DA_RDWR;
		fmode = "r+F";
	} else if (flag & DA_RDONLY) {
		oflag = DA_RDONLY;
		fmode = "rF";
	}

	if ((lockfd = _da_lock_devdb(rootdir)) == -1)
		return (-1);

	if ((dafp == NULL) || (flag & DA_MAPS_ONLY))
		goto dmap_only;

	path[0] = '\0';

	/*
	 * open the device allocation file
	 */
	if (rootdir == NULL) {
		fname = DEVALLOC;
	} else {
		if (snprintf(path, sizeof (path), "%s%s", rootdir,
		    DEVALLOC) >= sizeof (path)) {
			if (lockfd != -1)
				(void) close(lockfd);
			return (-1);
		}
		fname = path;
	}
	if ((fda = open(fname, oflag, DA_DBMODE)) == -1) {
		if (lockfd != -1)
			(void) close(lockfd);
		return ((errno == ENOENT) ? -2 : -1);
	}
	if ((devfile = fdopen(fda, fmode)) == NULL) {
		(void) close(fda);
		if (lockfd != -1)
			(void) close(lockfd);
		return (-1);
	}
	*dafp = devfile;
	(void) fchmod(fda, DA_DBMODE);

	if ((flag & DA_ALLOC_ONLY))
		goto out;

dmap_only:
	path[0] = '\0';
	/*
	 * open the device map file
	 */
	if (rootdir == NULL) {
		fname = DEVMAP;
	} else {
		if (snprintf(path, sizeof (path), "%s%s", rootdir,
		    DEVMAP) >= sizeof (path)) {
			(void) close(fda);
			if (lockfd != -1)
				(void) close(lockfd);
			return (-1);
		}
		fname = path;
	}

	if ((fdm = open(fname, oflag, DA_DBMODE)) == -1) {
		if (lockfd != -1)
			(void) close(lockfd);
		return ((errno == ENOENT) ? -2 : -1);
	}

	if ((devfile = fdopen(fdm, fmode)) == NULL) {
		(void) close(fdm);
		(void) close(fda);
		if (lockfd != -1)
			(void) close(lockfd);
		return (-1);
	}
	*dmfp = devfile;
	(void) fchmod(fdm, DA_DBMODE);

out:
	return (lockfd);
}

/*
 * _record_on_off -
 *	adds either DA_ON_STR or DA_OFF_STR to device_allocate
 *	returns 0 on success, -1 on error.
 */
static int
_record_on_off(da_args *dargs, FILE *tafp, FILE *dafp)
{
	int		dafd;
	int		nsize;
	int		nitems = 1;
	int		actionlen;
	int		str_found = 0;
	int		len = 0, nlen = 0, plen = 0;
	char		*ptr = NULL;
	char		*actionstr;
	char		*nbuf = NULL;
	char		line[MAX_CANON];
	struct stat	dastat;

	if (dargs->optflag & DA_ON)
		actionstr = DA_ON_STR;
	else
		actionstr = DA_OFF_STR;
	actionlen = strlen(actionstr);
	dafd = fileno(dafp);
	if (fstat(dafd, &dastat) == -1)
		return (-1);

	/* check the old device_allocate for on/off string */
	ptr = fgets(line, MAX_CANON, dafp);
	if (ptr != NULL) {
		if ((strcmp(line, DA_ON_STR) == 0) ||
		    (strcmp(line, DA_OFF_STR) == 0)) {
			str_found = 1;
			nsize = dastat.st_size;
		}
	}
	if (!ptr || !str_found) {
		/*
		 * the file never had either the on or the off string;
		 * make room for it.
		 */
		str_found = 0;
		nsize = dastat.st_size + actionlen + 1;
	}
	if ((nbuf = (char *)malloc(nsize + 1)) == NULL)
		return (-1);
	nbuf[0] = '\0';
	/* put the on/off string */
	(void) strcpy(nbuf, actionstr);
	nlen = strlen(nbuf);
	plen = nlen;
	if (ptr && !str_found) {
		/* now put the first line that we read in fgets */
		nlen = plen + strlen(line) + 1;
		len = snprintf(nbuf + plen, nlen - plen, "%s", line);
		if (len >= nsize) {
			free(nbuf);
			return (-1);
		}
		plen += len;
	}

	/* now get the rest of the old file */
	while (fgets(line, MAX_CANON, dafp) != NULL) {
		nlen = plen + strlen(line) + 1;
		len = snprintf(nbuf + plen, nlen - plen, "%s", line);
		if (len >= nsize) {
			free(nbuf);
			return (-1);
		}
		plen += len;
	}
	len = strlen(nbuf) + 1;
	if (len < nsize)
		nbuf[len] = '\n';

	/* write the on/off str + the old device_allocate to the temp file */
	if (fwrite(nbuf, nsize, nitems, tafp) < nitems) {
		free(nbuf);
		return (-1);
	}

	free(nbuf);

	return (0);
}

/*
 * da_update_defattrs -
 *	writes default attributes to devalloc_defaults
 *	returns 0 on success, -1 on error.
 */
int
da_update_defattrs(da_args *dargs)
{
	int		rc = 0, lockfd = 0, tmpfd = 0;
	char		*defpath = DEFATTRS;
	char		*tmpdefpath = TMPATTRS;
	FILE		*tmpfp = NULL;
	struct stat	dstat;
	strentry_t	*head_defent = NULL;

	if (dargs == NULL)
		return (0);
	if ((lockfd = _da_lock_devdb(NULL)) == -1)
		return (-1);
	if ((tmpfd = open(tmpdefpath, O_RDWR|O_CREAT, DA_DBMODE)) == -1) {
		(void) close(lockfd);
		return (-1);
	}
	(void) fchown(tmpfd, DA_UID, DA_GID);
	if ((tmpfp = fdopen(tmpfd, "r+")) == NULL) {
		(void) close(tmpfd);
		(void) unlink(tmpdefpath);
		(void) close(lockfd);
		return (-1);
	}
	/*
	 * examine all entries, remove an old one if required, check
	 * if a new one needs to be added.
	 */
	if (stat(defpath, &dstat) == 0) {
		if ((rc = _build_defattrs(dargs, &head_defent)) != 0) {
			if (rc == 1) {
				(void) close(tmpfd);
				(void) unlink(tmpdefpath);
				(void) close(lockfd);
				return (rc);
			}
		}
	}
	/*
	 * write back any existing entries.
	 */
	_write_defattrs(tmpfp, head_defent);

	if (dargs->optflag & DA_ADD && !(dargs->optflag & DA_NO_OVERRIDE)) {
		/* add new entries */
		rc = _write_new_defattrs(tmpfp, dargs);
		(void) fclose(tmpfp);
	} else {
		(void) fclose(tmpfp);
	}
	if (rename(tmpdefpath, defpath) != 0) {
		rc = -1;
		(void) unlink(tmpdefpath);
	}
	(void) close(lockfd);

	return (rc);
}

/*
 * da_update_device -
 *	Writes existing entries and the SINGLE change requested by da_args,
 *	to device_allocate and device_maps.
 *	Returns 0 on success, -1 on error.
 */
int
da_update_device(da_args *dargs)
{
	int		rc;
	int		tafd = -1, tmfd = -1;
	int		lockfd = -1;
	char		*rootdir = NULL;
	char		*apathp = NULL, *mpathp = NULL;
	char		*dapathp = NULL, *dmpathp = NULL;
	char		apath[MAXPATHLEN], mpath[MAXPATHLEN];
	char		dapath[MAXPATHLEN], dmpath[MAXPATHLEN];
	FILE		*tafp = NULL, *tmfp = NULL, *dafp = NULL;
	struct stat	dastat;
	devinfo_t	*devinfo;
	strentry_t	*head_devmapp = NULL;
	strentry_t	*head_devallocp = NULL;

	if (dargs == NULL)
		return (0);

	rootdir = dargs->rootdir;
	devinfo = dargs->devinfo;

	/*
	 * adding/removing entries should be done in both
	 * device_allocate and device_maps. updates can be
	 * done in both or either of the files.
	 */
	if (dargs->optflag & DA_ADD || dargs->optflag & DA_REMOVE) {
		if (dargs->optflag & DA_ALLOC_ONLY ||
		    dargs->optflag & DA_MAPS_ONLY)
			return (0);
	}

	/*
	 * name, type and list are required fields for adding a new
	 * device.
	 */
	if ((dargs->optflag & DA_ADD) &&
	    ((devinfo->devname == NULL) ||
	    (devinfo->devtype == NULL) ||
	    (devinfo->devlist == NULL))) {
		return (-1);
	}

	if (rootdir != NULL) {
		if (snprintf(apath, sizeof (apath), "%s%s", rootdir,
		    TMPALLOC) >= sizeof (apath))
			return (-1);
		apathp = apath;
		if (snprintf(dapath, sizeof (dapath), "%s%s", rootdir,
		    DEVALLOC) >= sizeof (dapath))
			return (-1);
		dapathp = dapath;
		if (!(dargs->optflag & DA_ALLOC_ONLY)) {
			if (snprintf(mpath, sizeof (mpath), "%s%s", rootdir,
			    TMPMAP) >= sizeof (mpath))
				return (-1);
			mpathp = mpath;
			if (snprintf(dmpath, sizeof (dmpath), "%s%s", rootdir,
			    DEVMAP) >= sizeof (dmpath))
				return (-1);
			dmpathp = dmpath;
		}
	} else {
		apathp = TMPALLOC;
		dapathp = DEVALLOC;
		mpathp = TMPMAP;
		dmpathp = DEVMAP;
	}

	if (dargs->optflag & DA_MAPS_ONLY)
		goto dmap_only;

	/*
	 * Check if we are here just to record on/off status of
	 * device_allocation.
	 */
	if (dargs->optflag & DA_ON || dargs->optflag & DA_OFF)
		lockfd = da_open_devdb(dargs->rootdir, &dafp, NULL,
		    DA_RDONLY|DA_ALLOC_ONLY);
	else
		lockfd = _da_lock_devdb(rootdir);
	if (lockfd == -1)
		return (-1);

	if ((tafd = open(apathp, O_RDWR|O_CREAT, DA_DBMODE)) == -1) {
		(void) close(lockfd);
		(void) fclose(dafp);
		return (-1);
	}
	(void) fchown(tafd, DA_UID, DA_GID);
	if ((tafp = fdopen(tafd, "r+")) == NULL) {
		(void) close(tafd);
		(void) unlink(apathp);
		(void) fclose(dafp);
		(void) close(lockfd);
		return (-1);
	}

	/*
	 * We don't need to parse the file if we are here just to record
	 * on/off status of device_allocation.
	 */
	if (dargs->optflag & DA_ON || dargs->optflag & DA_OFF) {
		if (_record_on_off(dargs, tafp, dafp) == -1) {
			(void) close(tafd);
			(void) unlink(apathp);
			(void) fclose(dafp);
			(void) close(lockfd);
			return (-1);
		}
		(void) fclose(dafp);
		goto out;
	}

	/*
	 * If reacting to a hotplug, read the file entries,
	 * figure out what dname (tname + a new number) goes to the
	 * device being added/removed, and create a good head_devallocp and
	 * head_devmapp with everything good still in it (_rebuild_lists)
	 *
	 * Else examine all the entries, remove an old one if it is
	 * a duplicate with a device being added, returning the
	 * remaining list (_build_lists.)
	 *
	 * We need to do this only if the file exists already.
	 *
	 * Once we have built these lists, we need to free the strings
	 * in the head_* arrays before returning.
	 */
	if (stat(dapathp, &dastat) == 0) {
		/* for device allocation, the /etc files are the "master" */
		if ((dargs->optflag & (DA_ADD| DA_EVENT)) &&
		    (!(dargs->optflag & DA_FORCE)))
			rc = _rebuild_lists(dargs, &head_devallocp,
			    &head_devmapp);
		else
			rc = _build_lists(dargs, &head_devallocp,
			    &head_devmapp);

		if (rc != 0 && rc != 1) {
			(void) close(tafd);
			(void) unlink(apathp);
			(void) close(lockfd);
			return (-1);
		}
	} else
		rc = 0;

	if ((dargs->optflag & DA_REMOVE) && (rc == 0)) {
		(void) close(tafd);
		(void) unlink(apathp);
		(void) close(lockfd);
		return (0);
	}
	/*
	 * TODO: clean up the workings of DA_UPDATE.
	 * Due to da_match looking at fields that are missing
	 * in dargs for DA_UPDATE, the da_match call returns no match,
	 * but due to the way _da2str combines the devalloc_t info with
	 * the *dargs info, the DA_ADD_ZONE and DA_REMOVE_ZONE work.
	 *
	 * This would not scale if any type of update was ever needed
	 * from the daemon.
	 */

	/*
	 * Write out devallocp along with the devalloc on/off string.
	 */
	_write_device_allocate(dapathp, tafp, head_devallocp);

	if (dargs->optflag & DA_ALLOC_ONLY)
		goto out;

dmap_only:
	if ((tmfd = open(mpathp, O_RDWR|O_CREAT, DA_DBMODE)) == -1) {
		(void) close(tafd);
		(void) unlink(apathp);
		(void) close(lockfd);
		return (-1);
	}
	(void) fchown(tmfd, DA_UID, DA_GID);
	if ((tmfp = fdopen(tmfd, "r+")) == NULL) {
		(void) close(tafd);
		(void) unlink(apathp);
		(void) close(tmfd);
		(void) unlink(mpathp);
		(void) close(lockfd);
		return (-1);
	}

	/*
	 * Write back any non-removed pre-existing entries.
	 */
	if (head_devmapp != NULL)
		_write_device_maps(tmfp, head_devmapp);

out:
	/*
	 * Add any new entries here.
	 */
	if (dargs->optflag & DA_ADD && !(dargs->optflag & DA_NO_OVERRIDE)) {
		/* add any new entries */
		rc = _write_new_entry(tafp, dargs, DA_ALLOC_ONLY);
		(void) fclose(tafp);

		if (rc == 0)
			rc = _write_new_entry(tmfp, dargs, DA_MAPS_ONLY);
		(void) fclose(tmfp);
	} else {
		if (tafp)
			(void) fclose(tafp);
		if (tmfp)
			(void) fclose(tmfp);
	}

	rc = 0;
	if (!(dargs->optflag & DA_MAPS_ONLY)) {
		if (rename(apathp, dapathp) != 0) {
			rc = -1;
			(void) unlink(apathp);
		}
	}
	if (!(dargs->optflag & DA_ALLOC_ONLY)) {
		if (rename(mpathp, dmpathp) != 0) {
			rc = -1;
			(void) unlink(mpathp);
		}
	}

	(void) close(lockfd);

	return (rc);
}

/*
 * da_add_list -
 *	adds new /dev link name to the linked list of devices.
 *	returns 0 if link added successfully, -1 on error.
 */
int
da_add_list(devlist_t *dlist, char *link, int new_instance, int flag)
{
	int		instance;
	int		nlen, plen;
	int		new_entry = 0;
	char		*dtype, *dexec, *tname, *kval;
	char		*minstr = NULL, *maxstr = NULL;
	char		dname[DA_MAXNAME + 1];
	kva_t		*kva;
	deventry_t	*dentry = NULL, *nentry = NULL, *pentry = NULL;
	da_defs_t	*da_defs;

	if (dlist == NULL || link == NULL)
		return (-1);

	dname[0] = '\0';
	if (flag & DA_AUDIO) {
		dentry = dlist->audio;
		tname = DA_AUDIO_NAME;
		dtype = DA_AUDIO_TYPE;
		dexec = DA_DEFAULT_AUDIO_CLEAN;
	} else if (flag & DA_CD) {
		dentry = dlist->cd;
		tname = DA_CD_NAME;
		dtype = DA_CD_TYPE;
		dexec = DA_DEFAULT_DISK_CLEAN;
	} else if (flag & DA_FLOPPY) {
		dentry = dlist->floppy;
		tname = DA_FLOPPY_NAME;
		dtype = DA_FLOPPY_TYPE;
		dexec = DA_DEFAULT_DISK_CLEAN;
	} else if (flag & DA_TAPE) {
		dentry = dlist->tape;
		tname = DA_TAPE_NAME;
		dtype = DA_TAPE_TYPE;
		dexec = DA_DEFAULT_TAPE_CLEAN;
	} else if (flag & DA_RMDISK) {
		dentry = dlist->rmdisk;
		tname = DA_RMDISK_NAME;
		dtype = DA_RMDISK_TYPE;
		dexec = DA_DEFAULT_DISK_CLEAN;
	} else {
		return (-1);
	}

	for (nentry = dentry; nentry != NULL; nentry = nentry->next) {
		pentry = nentry;
		(void) sscanf(nentry->devinfo.devname, "%*[a-z]%d", &instance);
		if (nentry->devinfo.instance == new_instance)
			/*
			 * Add the new link name to the list of links
			 * that the device 'dname' has.
			 */
			break;
	}

	if (nentry == NULL) {
		/*
		 * Either this is the first entry ever, or no matching entry
		 * was found. Create a new one and add to the list.
		 */
		if (dentry == NULL)		/* first entry ever */
			instance = 0;
		else				/* no matching entry */
			instance++;
		(void) snprintf(dname, sizeof (dname), "%s%d", tname, instance);
		if ((nentry = (deventry_t *)malloc(sizeof (deventry_t))) ==
		    NULL)
			return (-1);
		if (pentry != NULL)
			pentry->next = nentry;
		new_entry = 1;
		nentry->devinfo.devname = strdup(dname);
		nentry->devinfo.devtype = dtype;
		nentry->devinfo.devauths = DEFAULT_DEV_ALLOC_AUTH;
		nentry->devinfo.devexec = dexec;
		nentry->devinfo.instance = new_instance;
		/*
		 * Look for default label range, authorizations and cleaning
		 * program in devalloc_defaults. If label range is not
		 * specified in devalloc_defaults, assume it to be admin_low
		 * to admin_high.
		 */
		minstr = DA_DEFAULT_MIN;
		maxstr = DA_DEFAULT_MAX;
		setdadefent();
		if (da_defs = getdadeftype(nentry->devinfo.devtype)) {
			kva = da_defs->devopts;
			if ((kval = kva_match(kva, DAOPT_MINLABEL)) != NULL)
				minstr = strdup(kval);
			if ((kval = kva_match(kva, DAOPT_MAXLABEL)) != NULL)
				maxstr = strdup(kval);
			if ((kval = kva_match(kva, DAOPT_AUTHS)) != NULL)
				nentry->devinfo.devauths = strdup(kval);
			if ((kval = kva_match(kva, DAOPT_CSCRIPT)) != NULL)
				nentry->devinfo.devexec = strdup(kval);
			freedadefent(da_defs);
		}
		enddadefent();
		kval = NULL;
		nlen = strlen(DAOPT_MINLABEL) + strlen(KV_ASSIGN) +
		    strlen(minstr) + strlen(KV_TOKEN_DELIMIT) +
		    strlen(DAOPT_MAXLABEL) + strlen(KV_ASSIGN) + strlen(maxstr)
		    + 1;			/* +1 for terminator */
		if (kval = (char *)malloc(nlen))
			(void) snprintf(kval, nlen, "%s%s%s%s%s%s%s",
			    DAOPT_MINLABEL, KV_ASSIGN, minstr, KV_TOKEN_DELIMIT,
			    DAOPT_MAXLABEL, KV_ASSIGN, maxstr);
		nentry->devinfo.devopts = kval;

		nentry->devinfo.devlist = NULL;
		nentry->next = NULL;
	}

	nlen = strlen(link) + 1;		/* +1 terminator */
	if (nentry->devinfo.devlist) {
		plen = strlen(nentry->devinfo.devlist);
		nlen = nlen + plen + 1;	/* +1 for blank to separate entries */
	} else {
		plen = 0;
	}

	if ((nentry->devinfo.devlist =
	    (char *)realloc(nentry->devinfo.devlist, nlen)) == NULL) {
		if (new_entry) {
			free(nentry->devinfo.devname);
			free(nentry);
			if (pentry != NULL)
				pentry->next = NULL;
		}
		return (-1);
	}

	if (plen == 0)
		(void) snprintf(nentry->devinfo.devlist, nlen, "%s", link);
	else
		(void) snprintf(nentry->devinfo.devlist + plen, nlen - plen,
		    " %s", link);

	if (pentry == NULL) {
		/*
		 * This is the first entry of this device type.
		 */
		if (flag & DA_AUDIO)
			dlist->audio = nentry;
		else if (flag & DA_CD)
			dlist->cd = nentry;
		else if (flag & DA_FLOPPY)
			dlist->floppy = nentry;
		else if (flag & DA_TAPE)
			dlist->tape = nentry;
		else if (flag & DA_RMDISK)
			dlist->rmdisk = nentry;
	}

	return (0);
}

/*
 * da_remove_list -
 *	removes a /dev link name from the linked list of devices.
 *	returns type of device if link for that device removed
 *	successfully, else returns -1 on error.
 *	if all links for a device are removed, stores that device
 *	name in devname.
 */
int
da_remove_list(devlist_t *dlist, char *link, int type, char *devname, int size)
{
	int		flag;
	int		remove_dev = 0;
	int		nlen, plen, slen;
	char		*lasts, *lname, *oldlist;
	struct stat	rmstat;
	deventry_t	*dentry, *current, *prev;

	if (type != NULL)
		flag = type;
	else if (link == NULL)
		return (-1);
	else if (strstr(link, DA_AUDIO_NAME) || strstr(link, DA_SOUND_NAME))
		flag = DA_AUDIO;
	else if (strstr(link, "dsk") || strstr(link, "rdsk") ||
	    strstr(link, "sr") || strstr(link, "rsr"))
		flag = DA_CD;
	else if (strstr(link, "fd") || strstr(link, "rfd") ||
	    strstr(link, "diskette") || strstr(link, "rdiskette"))
		flag = DA_FLOPPY;
	else if (strstr(link, DA_TAPE_NAME))
		flag = DA_TAPE;
	else
		flag = DA_RMDISK;

	switch (type) {
	case DA_AUDIO:
		dentry = dlist->audio;
		break;
	case DA_CD:
		dentry = dlist->cd;
		break;
	case DA_FLOPPY:
		dentry = dlist->floppy;
		break;
	case DA_TAPE:
		dentry = dlist->tape;
		break;
	case DA_RMDISK:
		dentry = dlist->rmdisk;
		break;
	default:
		return (-1);
	}

	if ((type != NULL) && (link == NULL)) {
		for (current = dentry, prev = dentry; current != NULL;
		    current = current->next) {
			oldlist = strdup(current->devinfo.devlist);
			for (lname = strtok_r(oldlist, " ", &lasts);
			    lname != NULL;
			    lname = strtok_r(NULL, " ", &lasts)) {
				if (stat(lname, &rmstat) != 0) {
					remove_dev = 1;
					goto remove_dev;
				}
			}
			prev = current;
		}
		return (-1);
	}

	for (current = dentry, prev = dentry; current != NULL;
	    current = current->next) {
		plen = strlen(current->devinfo.devlist);
		nlen = strlen(link);
		if (plen == nlen) {
			if (strcmp(current->devinfo.devlist, link) == 0) {
				/* last name in the list */
				remove_dev = 1;
				break;
			}
		}
		if (strstr(current->devinfo.devlist, link)) {
			nlen = plen - nlen + 1;
			oldlist = strdup(current->devinfo.devlist);
			if ((current->devinfo.devlist =
			    (char *)realloc(current->devinfo.devlist,
			    nlen)) == NULL) {
				free(oldlist);
				return (-1);
			}
			current->devinfo.devlist[0] = '\0';
			nlen = plen = slen = 0;
			for (lname = strtok_r(oldlist, " ", &lasts);
			    lname != NULL;
			    lname = strtok_r(NULL, " ", &lasts)) {
				if (strcmp(lname, link) == 0)
					continue;
				nlen = strlen(lname) + plen + 1;
				if (plen == 0) {
					slen =
					    snprintf(current->devinfo.devlist,
					    nlen, "%s", lname);
				} else {
					slen =
					    snprintf(current->devinfo.devlist +
					    plen, nlen - plen, " %s", lname);
				}
				plen = plen + slen + 1;
			}
			free(oldlist);
			break;
		}
		prev = current;
	}

remove_dev:
	if (remove_dev == 1) {
		(void) strlcpy(devname, current->devinfo.devname, size);
		free(current->devinfo.devname);
		free(current->devinfo.devlist);
		current->devinfo.devname = current->devinfo.devlist = NULL;
		prev->next = current->next;
		free(current);
		current = NULL;
	}
	if ((remove_dev == 1) && (prev->devinfo.devname == NULL)) {
		if (prev->next) {
			/*
			 * what we removed above was the first entry
			 * in the list. make the next entry to be the
			 * first.
			 */
			current = prev->next;
		} else {
			/*
			 * the matching entry was the only entry in the list
			 * for this type.
			 */
			current = NULL;
		}
		if (flag & DA_AUDIO)
			dlist->audio = current;
		else if (flag & DA_CD)
			dlist->cd = current;
		else if (flag & DA_FLOPPY)
			dlist->floppy = current;
		else if (flag & DA_TAPE)
			dlist->tape = current;
		else if (flag & DA_RMDISK)
			dlist->rmdisk = current;
	}

	return (flag);
}

/*
 * da_rm_list_entry -
 *
 *	The adding of devnames to a devlist and the removal of a
 *	device are not symmetrical -- hot_cleanup gives a /devices
 *	name which is used to remove the dentry whose links all point to
 *	that /devices entry.
 *
 *	The link argument is present if available to make debugging
 *	easier.
 *
 *	da_rm_list_entry removes an entry from the linked list of devices.
 *
 *	Returns 1 if the devname was removed successfully,
 *	0 if not found, -1 for error.
 */
/*ARGSUSED*/
int
da_rm_list_entry(devlist_t *dlist, char *link, int type, char *devname)
{
	int		retval = 0;
	deventry_t	**dentry, *current, *prev;

	switch (type) {
	case DA_AUDIO:
		dentry = &(dlist->audio);
		break;
	case DA_CD:
		dentry = &(dlist->cd);
		break;
	case DA_FLOPPY:
		dentry = &(dlist->floppy);
		break;
	case DA_TAPE:
		dentry = &(dlist->tape);
		break;
	case DA_RMDISK:
		dentry = &(dlist->rmdisk);
		break;
	default:
		return (-1);
	}

	/* Presumably in daemon mode, no need to remove entry, list is empty */
	if (*dentry == (deventry_t *)NULL)
		return (0);

	prev = NULL;
	for (current = *dentry; current != NULL;
	    prev = current, current = current->next) {
		if (strcmp(devname, current->devinfo.devname))
			continue;
		retval = 1;
		break;
	}
	if (retval == 0)
		return (0);
	free(current->devinfo.devname);
	if (current->devinfo.devlist != NULL)
		free(current->devinfo.devlist);
	if (current->devinfo.devopts != NULL)
		free(current->devinfo.devopts);

	if (prev == NULL)
		*dentry = current->next;
	else
		prev->next = current->next;

	free(current);
	return (retval);
}

/*
 * da_is_on -
 *	checks if device allocation feature is turned on.
 *	returns 1 if on, 0 if off, -1 if status string not
 *	found in device_allocate.
 */
int
da_is_on()
{
	return (getdaon());
}

/*
 * da_print_device -
 *	debug routine to print device entries.
 */
void
da_print_device(int flag, devlist_t *devlist)
{
	deventry_t	*entry, *dentry;
	devinfo_t	*devinfo;

	if (flag & DA_AUDIO)
		dentry = devlist->audio;
	else if (flag & DA_CD)
		dentry = devlist->cd;
	else if (flag & DA_FLOPPY)
		dentry = devlist->floppy;
	else if (flag & DA_TAPE)
		dentry = devlist->tape;
	else if (flag & DA_RMDISK)
		dentry = devlist->rmdisk;
	else
		return;

	for (entry = dentry; entry != NULL; entry = entry->next) {
		devinfo = &(entry->devinfo);
		(void) fprintf(stdout, "name: %s\n", devinfo->devname);
		(void) fprintf(stdout, "type: %s\n", devinfo->devtype);
		(void) fprintf(stdout, "auth: %s\n", devinfo->devauths);
		(void) fprintf(stdout, "exec: %s\n", devinfo->devexec);
		(void) fprintf(stdout, "list: %s\n\n", devinfo->devlist);
	}
}