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

/*LINTLIBRARY*/

/*
 * devtab.c
 *
 *  Contains functions that deal with the device table and are not for
 *  consumption by the general user population.
 *
 *  Functions defined:
 *      _opendevtab()           Opens the device table for commands
 *      _setdevtab()            Rewinds the open device table
 *      _enddevtab()            Closes the open device table
 *      _getdevtabent()         Gets the next entry in the device table
 *      _freedevtabent()        Frees memory allocated to a device-table entry
 *      _getdevrec()            Gets a specific record from the device table
 *      _devtabpath()           Get the pathname of the device table file
 *      _validalias()           Is a value a valid alias?
 */

/*
 *  Header files
 *
 *      <sys/sysmacros.h>       System macro definitions
 *      <sys/types.h>           System data types
 *      <sys/mkdev.h>           Device Macros
 *      <unistd.h>              System Symbolic Constants
 *      <stdio.h>               Standard I/O definitions
 *      <string.h>              String handling definitions
 *      <ctype.h>               Character types and macros
 *      <errno.h>               Error codes
 *      <sys/stat.h>            File status information
 *      <devmgmt.h>             Global Device Management definitions
 *      "devtab.h"              Local Device Management definitions
 */

#include        <sys/sysmacros.h>
#include        <sys/types.h>
#ifndef SUNOS41
#include        <sys/mkdev.h>
#endif
#include        <unistd.h>
#include        <stdio.h>
#include        <string.h>
#include        <ctype.h>
#include        <errno.h>
#include        <sys/stat.h>
#include        <devmgmt.h>
#include        "devtab.h"
#include        <stdlib.h>

/*
 *  Static data definitions:
 *      dtabrecnum      Record number of the current record (0 to n-1)
 *      leftoff         Addr of char to begin next parse using
 *                      getfld(), getattrval(), getquoted()
 *      recbufsz        The size of the buffer used for reading records
 *      recbuf          Addr of malloc() buffer for reading records
 *      xtndcnt         Number of malloc()/realloc() calls on record buffer
 */

static  int             xtndcnt = 0;
static  char            *recbuf = NULL;
static  int             recbufsz = 0;

static  char            *leftoff = NULL;
static  int             dtabrecnum = 0;

/*
 * int samedev(x, y)
 *      struct stat     x, y
 *
 *      Compares pertinent information in a stat() structure
 *      to see if the two structures describe the same device.
 *      If the file modes are the same and they have the same
 *      file system and i-node (i.e. they're links) or they
 *      are block or character devices and have the same major
 *      and minor device numbers (i.e. "mknod"s for the same
 *      device), it's the same device.
 *
 *   Returns:  int
 *      TRUE if the two structures describe the same device
 *      FALSE otherwise
 */

static int
samedev(struct stat64 x, struct stat64 y)
{
        int     same;


        /* If the devices are of the same type ... */
        if ((x.st_mode & 0170000) == (y.st_mode & 0170000)) {

                /*
                 * If they are described by the same inode on the same device,
                 * the two devices are the same.  Otherwise, if the devices are
                 * character-special or block-special devices, try to match by
                 * device type and major and minor device numbers.
                 */

            if ((x.st_dev == y.st_dev) && (x.st_ino == y.st_ino)) same = TRUE;
            else
                if (((x.st_mode & 0170000) == 0020000) ||
                    ((x.st_mode & 0170000) == 0060000)) {
                    if ((major(x.st_rdev) == major(y.st_rdev)) &&
                        (minor(x.st_rdev) == minor(y.st_rdev))) same = TRUE;
                    else same = FALSE;
                } else same = FALSE;

        } else same = FALSE;

        return (same);
}

/*
 *  void _setdevtab()
 *
 *      This function rewinds the open device table so that the next
 *      _getdevtabent() returns the first record in the device table.
 *
 *  Arguments:  None
 *
 *  Returns:  Void
 */

void
_setdevtab(void)
{
        /*  If the device table file is open, rewind the file  */
        if (oam_devtab != NULL) {
            rewind(oam_devtab);
            dtabrecnum = 0;
        }
}

/*
 *  void _enddevtab()
 *
 *      This function closes the open device table.  It resets the
 *      open device table external variable to NULL.
 *
 *  Arguments:  None
 *
 *  Returns:  Void
 */

void
_enddevtab(void)
{
        /*  If the device table file is open, close it  */
        if (oam_devtab != NULL) {
            (void) fclose(oam_devtab);
            oam_devtab = NULL;
            dtabrecnum = 0;
        }
}

/*
 *  char *getfld(ptr, delims)
 *      char   *ptr
 *      char   *delims
 *
 *  Notes:
 *    - Can't use "strtok()" because of its use of static data.  The caller
 *      may be using strtok() and we'll really mess them up.
 *    - The function returns NULL if it didn't find any token -- '\0' can't
 *      be a delimiter using this algorithm.
 */

static char *
getfld(
        char   *ptr,            /* String to parse */
        char   *delims)         /* List of delimiters */
{
        int     done;           /* TRUE if we're finished */
        char   *p, *q;          /* Temp pointers */

        /*
         *  Figure out where to start.
         *  If given a pointer, use that.
         *  Otherwise, use where we left off.
         */

        p = ptr ? ptr : leftoff;


        /*
         *  If there's anything to parse, search the string for the first
         *  occurrence of any of the delimiters.  If one is found, change it
         *  to '\0' and remember the place to start for next time.  If not
         *  found, forget the restart address and prepare to return NULL.
         *  Don't terminate on "escaped" characters.
         */

        if (p) {                                    /* Anything to do ?? */
            q = p;                                  /* Where to begin */
            done = FALSE;                           /* We're not done yet */
            while (*q && !done) {                   /* Any more chars */
                if (*q == '\\') {                   /* Escaped ? */
                    if (*(++q)) q++;                /* Skip escaped char */
                } else                              /* Not escaped */
                    if (!strchr(delims, *q)) q++;   /* Skip non-delim */
                    else done = TRUE;               /* Otherwise, done */
            }
            if (*q) {                               /* Terminator found? */
                *q++ = '\0';                        /* Null-terminate token */
                leftoff = q;                        /* Remember restart pt. */
            } else
                leftoff = p = NULL;                 /* Nothin found or left */
        }

        /*  Finished  */
        return (p);                                 /* Return ptr to token */
}

/*
 *  char *getquoted(ptr)
 *      char   *ptr;
 *
 *      This function extracts a quoted string from the string pointed
 *      to by <ptr>, or, if <ptr> is NULL, wherever we left off
 *      last time.
 *
 *  Arguments:
 *      char *ptr       Pointer to the character-string to parse, or
 *                      (char *) NULL if we're to pick up where we
 *                      [getquoted(), getfld(), and getattrval()] left off.
 *
 *  Returns:  char *
 *      The address of malloc()ed space that contains the possibly quoted
 *      string.
 *
 *  Notes:
 *    - This code only works if it can assume that the last character in
 *      the string it's parsing is a '\n', something that is guarenteed
 *      by the getnextrec() function.
 */

static char *
getquoted(char *ptr)
{
        /*  Automatic data  */
        char   *rtn;            /* Value to return */
        char   *p, *q;          /* Temps */

        /* Figure out where to start off */
        p = ptr ? ptr : leftoff;

        /* If there's anything to parse and it's a quoted string ... */
        if ((p) && (*p == '"') && (p = getfld(p+1, "\""))) {

            /* Copy string for the caller */
            if (rtn = malloc(strlen(p)+1)) {    /* Malloc() space */
                q = rtn;                        /* Set up temp ptr */
                do {
                    if (*p == '\\') p++;        /* Skip escape */
                    *q++ = *p;                  /* Copy char */
                } while (*p++);                 /* While there's chars */
            } else leftoff = rtn = NULL;
        } else leftoff = rtn = NULL;

        /* Fini */
        return (rtn);
}

/*
 *  struct attrval *getattrval(ptr)
 *      char   *ptr
 *
 *      This function extracts the next attr=val pair from <ptr> or wherever
 *      getfld() left off...
 *
 *  Arguments:
 *      char *ptr       The string to parse, or (char *) NULL if we're to
 *                      begin wherever we left off last time.
 *
 *  Returns:  struct attrval *
 *      The address of a malloc()ed structure containing the attribute and the
 *      value of the attr=val pair extracted.
 */

static struct attrval *
getattrval(char *ptr)
{
        /*  Automatic data  */
        struct attrval *rtn;            /* Ptr to struct to return */
        char            *p, *q;         /* Temp pointers */


        /*  Use what's given to us or wherever we left off  */
        p = ptr ? ptr : leftoff;

        /*  If there's anything to parse, extract the next attr=val pair  */
        if (p) {

            /*  Eat white space  */
            while (*p && isspace((unsigned char)*p)) p++;

            /*  Extract the attribute name, if any  */
            if (*p && getfld(p, "=")) {

                /*  Allocate space for the structure we're building  */
                if (rtn = malloc(sizeof (struct attrval))) {

                    /*  Allocate space for the attribute name  */
                    if (rtn->attr = malloc(strlen(p)+1)) {

                        /*  Copy the attribute name into alloc'd space  */
                        q = rtn->attr;                  /* Set up temp ptr */
                        do {
                            if (*p == '\\') p++;        /* Skip escape */
                            *q++ = *p;                  /* Copy char */
                        } while (*p++);                 /* While more */

                        /*  Extract the value  */
                        if (!(rtn->val = getquoted(NULL))) {
                            /* Error getting value, free resources */
                            free(rtn->attr);
                            free(rtn);
                            leftoff = NULL;
                            rtn = NULL;
                        }
                    } else {
                        /* Error getting space for attribute, free resources */
                        free(rtn);
                        leftoff = NULL;
                        rtn = NULL;
                    }

                } else {
                    /* No space for attr struct */
                    leftoff = NULL;
                    rtn = NULL;
                }

            } else {
                /* No attribute name */
                leftoff = NULL;
                rtn = NULL;
            }

        } else {
            /* Nothing to parse */
            leftoff = NULL;
            rtn = NULL;
        }

        /* Done */
        return (rtn);
}

/*
 *  char *getnextrec()
 *
 *      This function gets the next record from the input stream "oam_devtab"
 *      and puts it in the device-table record buffer (whose address is in
 *      "recbuf").  If the buffer is not allocated or is too small to
 *      accommodate the record, the function allocates more space to the
 *      buffer.
 *
 *  Arguments:  None
 *
 *  Returns:  char *
 *      The address of the buffer containing the record.
 *
 *  Static Data Referenced:
 *      recbuf          Address of the buffer containing records read from the
 *                      device table file
 *      recbufsz        Current size of the record buffer
 *      xtndcnt         Number of times the record buffer has been extended
 *      oam_devtab      Device table stream, expected to be open for (at
 *                      least) reading
 *
 *  Notes:
 *    - The string returned in the buffer <buf> ALWAYS end in a '\n' (newline)
 *      character followed by a '\0' (null).
 */

static char *
getnextrec(void)
{
        /* Automatic data */
        char            *recp;          /* Value to return */
        char            *p;             /* Temp pointer */
        int             done;           /* TRUE if we're finished */
        int             reclen;         /* Number of chars in record */


        /* If there's no buffer for records, try to get one */
        if (!recbuf) {
            if (recbuf = malloc(DTAB_BUFSIZ)) {
                recbufsz = DTAB_BUFSIZ;
                xtndcnt = 0;
            } else return (NULL);
        }


        /* Get the next record */
        recp = fgets(recbuf, recbufsz, oam_devtab);
        done = FALSE;

        /* While we've something to return and we're not finished ... */
        while (recp && !done) {

            /* If our return string isn't a null-string ... */
            if ((reclen = (int)strlen(recp)) != 0) {

                /* If we have a complete record, we're finished */
                if ((*(recp+reclen-1) == '\n') &&
                    ((reclen == 1) || (*(recp+reclen-2) != '\\'))) done = TRUE;
                else while (!done) {

                        /*
                         * Need to complete the record.  A complete record is
                         * one which is terminated by an unescaped new-line
                         * character.
                         */

                    /* If the buffer is full, expand it and continue reading */
                    if (reclen == recbufsz-1) {

                        /* Have we reached our maximum extension count? */
                        if (xtndcnt < XTND_MAXCNT) {

                            /* Expand the record buffer */
                            if (p = realloc(recbuf,
                                (size_t)recbufsz+DTAB_BUFINC)) {

                                /* Update buffer information */
                                xtndcnt++;
                                recbuf = p;
                                recbufsz += DTAB_BUFINC;

                            } else {

                                /* Expansion failed */
                                recp = NULL;
                                done = TRUE;
                            }

                        } else {

                            /* Maximum extend count exceeded.  Insane table */
                            recp = NULL;
                            done = TRUE;
                        }

                    }

                    /* Complete the record */
                    if (!done) {

                        /* Read stuff into the expanded space */
                        if (fgets(recbuf+reclen, recbufsz-reclen, oam_devtab)) {
                            reclen = (int)strlen(recbuf);
                            recp = recbuf;
                            if ((*(recp+reclen-1) == '\n') &&
                                ((reclen == 1) || (*(recp+reclen-2) != '\\')))
                                    done = TRUE;
                        } else {
                            /* Read failed, corrupt record? */
                            recp = NULL;
                            done = TRUE;
                        }
                    }

                }   /* End incomplete record handling */

            } else {

                /* Read a null string?  (corrupt table) */
                recp = NULL;
                done = TRUE;
            }

        }   /* while (recp && !done) */

        /* Return what we've got (if anything) */
        return (recp);
}

/*
 *  char *_devtabpath()
 *
 *      Get the pathname of the device table
 *
 *  Arguments:  None
 *
 *  Returns:  char *
 *      Returns the pathname to the device table of NULL if
 *      there was a problem getting the memory needed to contain the
 *      pathname.
 *
 *  Algorithm:
 *      1.  If OAM_DEVTAB is defined in the environment and is not
 *          defined as "", it returns the value of that environment
 *          variable.
 *      2.  Otherwise, use the value of the environment variable DTAB_PATH.
 */


char *
_devtabpath(void)
{

        /* Automatic data */
#ifdef  DEBUG
        char            *path;          /* Ptr to path in environment */
#endif
        char            *rtnval;                /* Ptr to value to return */


        /*
         * If compiled with -DDEBUG=1,
         * look for the pathname in the environment
         */

#ifdef  DEBUG
        if (((path = getenv(OAM_DEVTAB)) != NULL) && (*path)) {
            if (rtnval = malloc(strlen(path)+1))
                (void) strcpy(rtnval, path);
        } else {
#endif
                /*
                 * Use the standard device table.
                 */

            if (rtnval = malloc(strlen(DTAB_PATH)+1))
                (void) strcpy(rtnval, DTAB_PATH);

#ifdef  DEBUG
        }
#endif

        /* Finished */
        return (rtnval);
}

/*
 *  int _opendevtab(mode)
 *      char   *mode
 *
 *      The _opendevtab() function opens a device table for a command.
 *
 *  Arguments:
 *      mode    The open mode to use to open the file.  (i.e. "r" for
 *              reading, "w" for writing.  See FOPEN(BA_OS) in SVID.)
 *
 *  Returns:  int
 *      TRUE if it successfully opens the device table file, FALSE otherwise
 */

int
_opendevtab(char *mode)
{
        /*
         *  Automatic data
         */

        char   *devtabname;             /* Ptr to the device table name */
        int     rtnval;                 /* Value to return */


        rtnval = TRUE;
        if (devtabname = _devtabpath()) {
            if (oam_devtab) (void) fclose(oam_devtab);
            if (oam_devtab = fopen(devtabname, mode))
                dtabrecnum = 0;  /* :-) */
            else rtnval = FALSE; /* :-( */
        } else rtnval = FALSE;   /* :-( */
        return (rtnval);
}

/*
 *  int _validalias(alias)
 *      char   *alias
 *
 *      Determine if <alias> is a valid alias.  Returns TRUE if it is
 *      a valid alias, FALSE otherwise.
 *
 *  Arguments:
 *      alias           Value to check out
 *
 *  Returns:  int
 *      TRUE if <alias> is a valid alias, FALSE otherwise.
 */

int
_validalias(char   *alias)                      /* Alias to validate */
{
        /* Automatic data */
        char            *p;             /* Temp pointer */
        size_t          len;            /* Length of <alias> */
        int             rtn;            /* Value to return */


        /* Assume the worst */
        rtn = FALSE;

        /*
         * A valid alias contains 0 < i <= 14 characters.  The first
         * must be alphanumeric or "@$_." and the rest must be alphanumeric
         * or "@#$_+-."
         */

        /* Check length */
        if ((alias != NULL) && ((len = strlen(alias)) > 0) && (len <= 14)) {

            /* Check the first character */
            p = alias;
            if (isalnum((unsigned char)*p) || strchr("@$_.", *p)) {

                /* Check the rest of the characters */
                for (p++; *p && (isalnum((unsigned char)*p) ||
                        strchr("@#$_-+.", *p)); p++)
                        ;
                if (!(*p)) rtn = TRUE;
            }
        }

        /* Return indicator... */
        return (rtn);

}   /* int _validalias() */

/*
 *  struct devtabent *_getdevtabent()
 *
 *      This function returns the next entry in the device table.
 *      If no device table is open, it opens the standard device table
 *      and returns the first record in the table.
 *
 *  Arguments:  None.
 *
 *  Returns:  struct devtabent *
 *      Pointer to the next record in the device table, or
 *      (struct devtabent *) NULL if it was unable to open the file or there
 *      are no more records to read.  "errno" reflects the situation.  If
 *      errno is not changed and the function returns NULL, there are no more
 *      records to read.  If errno is set, it indicates the error.
 *
 *  Notes:
 *    - The caller should set "errno" to 0 before calling this function.
 */

struct devtabent *
_getdevtabent(void)
{
        /*  Automatic data  */
        struct devtabent        *ent;   /* Ptr to dev table entry structure */
        struct attrval          *attr;  /* Ptr to struct for attr/val pair */
        struct attrval          *t;     /* Tmp ptr to attr/val struct */
        char                    *record; /* Ptr to the record just read */
        char                    *p, *q; /* Tmp char ptrs */
        int                     done;   /* TRUE if we've built an entry */


        /*  Open the device table if it's not already open */
        if (oam_devtab == NULL) {
            if (!_opendevtab("r"))
                return (NULL);
        }

        /*  Get space for the structure we're returning  */
        if (!(ent = malloc(sizeof (struct devtabent)))) {
            return (NULL);
        }

        done = FALSE;
        while (!done && (record = getnextrec())) {

            /* Save record number in structure */
            ent->entryno = dtabrecnum++;

            /* Comment record?  If so, just save the value and we're through */
            if (strchr("#\n", *record) || isspace((unsigned char)*record)) {
                ent->comment = TRUE;
                done = TRUE;
                if (ent->attrstr = malloc(strlen(record)+1)) {
                    q = ent->attrstr;
                    p = record;
                    do {
                        if (*p == '\\') p++;
                        *q++ = *p;
                    } while (*p++);
                } else {
                    free(ent);
                    ent = NULL;
                }
            }

            else {

                /* Record is a data record.   Parse it. */
                ent->comment = FALSE;
                ent->attrstr = NULL;  /* For now */

                /* Extract the device alias */
                if (p = getfld(record, ":")) {
                    if (*p) {
                        if (ent->alias = malloc(strlen(p)+1)) {
                            q = ent->alias;
                            do {
                                if (*p == '\\') p++;
                                *q++ = *p;
                            } while (*p++);
                        }
                    } else ent->alias = NULL;

                    /* Extract the character-device name */
                    if ((p = getfld(NULL, ":")) == NULL) {
                        if (ent->alias)
                            free(ent->alias);
                    } else {
                        if (*p) {
                            if (ent->cdevice = malloc(strlen(p)+1)) {
                                q = ent->cdevice;
                                do {
                                    if (*p == '\\') p++;
                                    *q++ = *p;
                                } while (*p++);
                            }
                        } else ent->cdevice = NULL;

                        /* Extract the block-device name */
                        if (!(p = getfld(NULL, ":"))) {
                            if (ent->alias) free(ent->alias);
                            if (ent->cdevice) free(ent->cdevice);
                        } else {
                            if (*p) {
                                if (ent->bdevice = malloc(strlen(p)+1)) {
                                    q = ent->bdevice;
                                    do {
                                        if (*p == '\\') p++;
                                        *q++ = *p;
                                    } while (*p++);
                                }
                            } else
                                ent->bdevice = NULL;

                            /* Extract the pathname */
                            if ((p = getfld(NULL, ":\n")) == NULL) {
                                if (ent->alias) free(ent->alias);
                                if (ent->cdevice) free(ent->cdevice);
                                if (ent->bdevice) free(ent->bdevice);
                            } else {
                                if (*p) {
                                    if (ent->pathname = malloc(strlen(p)+1)) {
                                        q = ent->pathname;
                                        do {
                                            if (*p == '\\') p++;
                                            *q++ = *p;
                                        } while (*p++);
                                    }
                                } else
                                    ent->pathname = NULL;

                                /* Found a valid record */
                                done = TRUE;

                                /*
                                 * Extract attributes, build a linked list of
                                 * 'em (may be none)
                                 */
                                if (attr = getattrval(NULL)) {
                                    ent->attrlist = attr;
                                    t = attr;
                                    while (attr = getattrval(NULL)) {
                                        t->next = attr;
                                        t = attr;
                                    }
                                    t->next = NULL;
                                } else
                                    ent->attrlist = NULL;

                            } /* pathname extracted */
                        } /* bdevice extracted */
                    } /* cdevice extracted */
                } /* alias extracted */
            }
        } /* !done && record read */

        /*  If no entry was read, free space allocated to the structure  */
        if (!done) {
            free(ent);
            ent = NULL;
        }

        return (ent);
}

/*
 *  void _freedevtabent(devtabent)
 *      struct devtabent       *devtabent;
 *
 *      This function frees space allocated to a device table entry.
 *
 *  Arguments:
 *      struct devtabent *devtabent     The structure whose space is to be
 *                                      freed.
 *
 *  Returns:  void
 */

void
_freedevtabent(struct devtabent *ent)
{
        /*
         * Automatic data
         */

        struct attrval *p;              /* Structure being freed */
        struct attrval *q;              /* Next structure to free */

        if (!ent->comment) {

                /*
                 *  Free the attribute list.  For each item in the attribute
                 *  list,
                 *    1.  Free the attribute name (always defined),
                 *    2.  Free the value (if any -- it's not defined if we're
                 *              changing an existing attribute),
                 *    3.  Free the space allocated to the structure.
                 */

            q = ent->attrlist;
            if (q)
                do {
                    p = q;
                    q = p->next;
                    free(p->attr);
                    if (p->val) free(p->val);
                    free(p);
                } while (q);

            /* Free the standard fields (alias, cdevice, bdevice, pathname) */
            if (ent->alias) free(ent->alias);
            if (ent->cdevice) free(ent->cdevice);
            if (ent->bdevice) free(ent->bdevice);
            if (ent->pathname) free(ent->pathname);
        }

        /* Free the attribute string */
        if (ent->attrstr) free(ent->attrstr);

        /* Free the space allocated to the structure */
        free(ent);
}

/*
 *  struct devtabent *_getdevrec(device)
 *      char *device
 *
 *      Thie _getdevrec() function returns a pointer to a structure that
 *      contains the information in the device-table entry that describes
 *      the device <device>.
 *
 *      The device <device> can be a device alias, a pathname contained in
 *      the entry as the "cdevice", "bdevice", or "pathname" attribute,
 *      or a pathname to a device described using the "cdevice", "bdevice",
 *      or "pathname" attribute (depending on whether the pathname references
 *      a character-special file, block-special file, or something else,
 *      respectively.
 *
 *  Arguments:
 *      char *device    A character-string describing the device whose record
 *                      is to be retrieved from the device table.
 *
 *  Returns:  struct devtabent *
 *      A pointer to a structure describing the device.
 *
 *  Notes:
 *    - Someday, add a cache so that repeated requests for the same record
 *      don't require going to the filesystem.  (Maybe -- this might belong
 *      in devattr()...)
 */

struct devtabent *
_getdevrec(char *device)                        /* The device to search for */
{
        /*
         *  Automatic data
         */

        struct stat64           devstatbuf;     /* Stat struct, <device> */
        struct stat64           tblstatbuf;     /* Stat struct, tbl entry */
        struct devtabent        *devrec;        /* Pointer to current record */
        int                     found;          /* TRUE if record found */
        int                     olderrno;       /* Old value of errno */


        /*
         *  Search the device table looking for the requested device
         */

        _setdevtab();
        olderrno = errno;
        found = FALSE;
        if ((device != NULL) && !_validalias(device)) {
            while (!found && (devrec = _getdevtabent())) {
                if (!devrec->comment) {
                    if (devrec->cdevice)
                        if (strcmp(device, devrec->cdevice) == 0) found = TRUE;
                    if (devrec->bdevice)
                        if (strcmp(device, devrec->bdevice) == 0) found = TRUE;
                    if (devrec->pathname)
                        if (strcmp(device, devrec->pathname) == 0) found = TRUE;
                } else _freedevtabent(devrec);
            }

                /*
                 *  If the device <device> wasn't named explicitly in the device
                 *  table, compare it against like entries by comparing file-
                 *  system, major device number, and minor device number
                 */

            if (!found) {
                _setdevtab();

                /*  Status the file <device>.  If fails, invalid device */
                if (stat64(device, &devstatbuf) != 0) errno = ENODEV;
                else {

                        /*
                         *  If <device> is a block-special device.  See if it is
                         *  in the table by matching its file-system indicator
                         * and major/minor device numbers against the
                         * file-system and major/minor device numbers of the
                         * "bdevice" entries.
                         */

                    if ((devstatbuf.st_mode & 0170000) == 0020000) {
                        while (!found && (devrec = _getdevtabent())) {
                            if (!devrec->comment &&
                                (devrec->cdevice != NULL))
                                if (stat64(devrec->cdevice, &tblstatbuf) == 0) {
                                    if (samedev(tblstatbuf, devstatbuf))
                                        found = TRUE;
                                } else {
                                        /* Ignore stat() errs */
                                        errno = olderrno;
                                }
                            if (!found) _freedevtabent(devrec);
                        }
                    }

                        /*
                         * If <device> is a block-special device.  See if it is
                         * in the table by matching its file-system indicator
                         * and major/minor device numbers against the
                         * file-system and major/minor device numbers of the
                         * "bdevice" entries.
                         */

                    else if ((devstatbuf.st_mode & 0170000) == 0060000) {
                        while (!found && (devrec = _getdevtabent())) {
                            if (!devrec->comment &&
                                (devrec->bdevice != NULL))
                                if (stat64(devrec->bdevice, &tblstatbuf) == 0) {
                                    if (samedev(tblstatbuf, devstatbuf))
                                        found = TRUE;
                                } else {
                                        /* Ignore stat() errs */
                                        errno = olderrno;
                                }
                            if (!found) _freedevtabent(devrec);
                        }
                    }

                        /*
                         * If <device> is neither a block-special or character-
                         * special device.  See if it is in the table by
                         * matching its file-system indicator and major/minor
                         * device numbers against the file-system and
                         * major/minor device numbers of the "pathname" entries.
                         */

                    else {
                        while (!found && (devrec = _getdevtabent())) {
                            if (!devrec->comment &&
                                (devrec->pathname != NULL))
                                if (stat64(devrec->pathname,
                                    &tblstatbuf) == 0) {
                                    if (samedev(tblstatbuf, devstatbuf))
                                        found = TRUE;
                                } else {
                                        /* Ignore stat() errs */
                                        errno = olderrno;
                                }
                            if (!found) _freedevtabent(devrec);
                        }
                    }

                    if (!found) {
                        devrec = NULL;
                        errno = ENODEV;
                    }

                } /* End case where stat() on the <device> succeeded */

            } /* End case handling pathname not explicitly in device table */

        } /* End case handling <device> as a fully-qualified pathname */


        /*
         *  Otherwise the device <device> is an alias.
         *  Search the table for a record that has as the "alias" attribute
         *  the value <device>.
         */

        else {
            while (!found && (devrec = _getdevtabent())) {
                if (!devrec->comment && (device != NULL) &&
                    strcmp(device, devrec->alias) == 0)
                    found = TRUE;
                else _freedevtabent(devrec);
            }
            if (!found) {
                devrec = NULL;
                errno = ENODEV;
            }
        }

        /* Fini */
        return (devrec);
}