root/usr/src/lib/libbsm/common/devalloc.c
/*
 * 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 != 0)
                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 != 0) && (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);
        }
}