root/usr/src/lib/libdevinfo/devfsmap.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) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#ifdef  lint
#define _REENTRANT      /* for localtime_r */
#endif

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/types.h>
#include <strings.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stropts.h>
#include <time.h>
#include <sys/param.h>
#include <sys/vfstab.h>
#include <dirent.h>
#ifdef __sparc
#include <sys/scsi/adapters/scsi_vhci.h>
#include <sys/sunmdi.h>
#endif /* __sparc */
#include "libdevinfo.h"
#include "device_info.h"
#include <regex.h>

#define isnewline(ch)   ((ch) == '\n' || (ch) == '\r' || (ch) == '\f')
#define isnamechar(ch)  (isalpha(ch) || isdigit(ch) || (ch) == '_' ||\
        (ch) == '-')
#define MAX_TOKEN_SIZE  1024
#define BUFSIZE         1024
#define STRVAL(s)       ((s) ? (s) : "NULL")

#define SCSI_VHCI_CONF          "/kernel/drv/scsi_vhci.conf"
#define QLC_CONF                "/kernel/drv/qlc.conf"
#define FP_CONF                 "/kernel/drv/fp.conf"
#define DRIVER_CLASSES          "/etc/driver_classes"
#define FP_AT                   "fp@"
#define VHCI_CTL_NODE           "/devices/scsi_vhci:devctl"
#define SLASH_DEVICES           "/devices"
#define SLASH_DEVICES_SLASH     "/devices/"
#define SLASH_FP_AT             "/fp@"
#define SLASH_SCSI_VHCI         "/scsi_vhci"
#define META_DEV                "/dev/md/dsk/"
#define SLASH_DEV_SLASH         "/dev/"

/*
 * Macros to produce a quoted string containing the value of a
 * preprocessor macro. For example, if SIZE is defined to be 256,
 * VAL2STR(SIZE) is "256". This is used to construct format
 * strings for scanf-family functions below.
 */
#define QUOTE(x)        #x
#define VAL2STR(x)      QUOTE(x)

typedef enum {
        CLIENT_TYPE_UNKNOWN,
        CLIENT_TYPE_PHCI,
        CLIENT_TYPE_VHCI
} client_type_t;

typedef enum {
        T_EQUALS,
        T_AMPERSAND,
        T_BIT_OR,
        T_STAR,
        T_POUND,
        T_COLON,
        T_SEMICOLON,
        T_COMMA,
        T_SLASH,
        T_WHITE_SPACE,
        T_NEWLINE,
        T_EOF,
        T_STRING,
        T_HEXVAL,
        T_DECVAL,
        T_NAME
} token_t;

typedef enum {
        begin, parent, drvname, drvclass, prop,
        parent_equals, name_equals, drvclass_equals,
        parent_equals_string, name_equals_string,
        drvclass_equals_string,
        prop_equals, prop_equals_string, prop_equals_integer,
        prop_equals_string_comma, prop_equals_integer_comma
} conf_state_t;

/* structure to hold entries with mpxio-disable property in driver.conf file */
struct conf_entry {
        char *name;
        char *parent;
        char *class;
        char *unit_address;
        int port;
        int mpxio_disable;
        struct conf_entry *next;
};

struct conf_file {
        char *filename;
        FILE *fp;
        int linenum;
};

static char *tok_err = "Unexpected token '%s'\n";


/* #define      DEBUG */

#ifdef DEBUG

int devfsmap_debug = 0;
/* /var/run is not mounted at install time. Therefore use /tmp */
char *devfsmap_logfile = "/tmp/devfsmap.log";
static FILE *logfp;
#define logdmsg(args)   log_debug_msg args
static void vlog_debug_msg(char *, va_list);
static void log_debug_msg(char *, ...);
#ifdef __sparc
static void log_confent_list(char *, struct conf_entry *, int);
static void log_pathlist(char **);
#endif /* __sparc */

#else /* DEBUG */
#define logdmsg(args)   /* nothing */
#endif /* DEBUG */


/*
 * Leave NEWLINE as the next character.
 */
static void
find_eol(FILE *fp)
{
        int ch;

        while ((ch = getc(fp)) != EOF) {
                if (isnewline(ch)) {
                        (void) ungetc(ch, fp);
                        break;
                }
        }
}

/* ignore parsing errors */
/*ARGSUSED*/
static void
file_err(struct conf_file *filep, char *fmt, ...)
{
#ifdef DEBUG
        va_list ap;

        va_start(ap, fmt);
        log_debug_msg("WARNING: %s line # %d: ",
            filep->filename, filep->linenum);
        vlog_debug_msg(fmt, ap);
        va_end(ap);
#endif /* DEBUG */
}

/* return the next token from the given driver.conf file, or -1 on error */
static token_t
lex(struct conf_file *filep, char *val, size_t size)
{
        char    *cp;
        int     ch, oval, badquote;
        size_t  remain;
        token_t token;
        FILE    *fp = filep->fp;

        if (size < 2)
                return (-1);

        cp = val;
        while ((ch = getc(fp)) == ' ' || ch == '\t')
                ;

        remain = size - 1;
        *cp++ = (char)ch;
        switch (ch) {
        case '=':
                token = T_EQUALS;
                break;
        case '&':
                token = T_AMPERSAND;
                break;
        case '|':
                token = T_BIT_OR;
                break;
        case '*':
                token = T_STAR;
                break;
        case '#':
                token = T_POUND;
                break;
        case ':':
                token = T_COLON;
                break;
        case ';':
                token = T_SEMICOLON;
                break;
        case ',':
                token = T_COMMA;
                break;
        case '/':
                token = T_SLASH;
                break;
        case ' ':
        case '\t':
        case '\f':
                while ((ch = getc(fp)) == ' ' ||
                    ch == '\t' || ch == '\f') {
                        if (--remain == 0) {
                                *cp = '\0';
                                return (-1);
                        }
                        *cp++ = (char)ch;
                }
                (void) ungetc(ch, fp);
                token = T_WHITE_SPACE;
                break;
        case '\n':
        case '\r':
                token = T_NEWLINE;
                break;
        case '"':
                remain++;
                cp--;
                badquote = 0;
                while (!badquote && (ch  = getc(fp)) != '"') {
                        switch (ch) {
                        case '\n':
                        case EOF:
                                file_err(filep, "Missing \"\n");
                                remain = size - 1;
                                cp = val;
                                *cp++ = '\n';
                                badquote = 1;
                                /* since we consumed the newline/EOF */
                                (void) ungetc(ch, fp);
                                break;

                        case '\\':
                                if (--remain == 0) {
                                        *cp = '\0';
                                        return (-1);
                                }
                                ch = (char)getc(fp);
                                if (!isdigit(ch)) {
                                        /* escape the character */
                                        *cp++ = (char)ch;
                                        break;
                                }
                                oval = 0;
                                while (ch >= '0' && ch <= '7') {
                                        ch -= '0';
                                        oval = (oval << 3) + ch;
                                        ch = (char)getc(fp);
                                }
                                (void) ungetc(ch, fp);
                                /* check for character overflow? */
                                if (oval > 127) {
                                        file_err(filep,
                                            "Character "
                                            "overflow detected.\n");
                                }
                                *cp++ = (char)oval;
                                break;
                        default:
                                if (--remain == 0) {
                                        *cp = '\0';
                                        return (-1);
                                }
                                *cp++ = (char)ch;
                                break;
                        }
                }
                token = T_STRING;
                break;

        case EOF:
                token = T_EOF;
                break;

        default:
                /*
                 * detect a lone '-' (including at the end of a line), and
                 * identify it as a 'name'
                 */
                if (ch == '-') {
                        if (--remain == 0) {
                                *cp = '\0';
                                return (-1);
                        }
                        *cp++ = (char)(ch = getc(fp));
                        if (ch == ' ' || ch == '\t' || ch == '\n') {
                                (void) ungetc(ch, fp);
                                remain++;
                                cp--;
                                token = T_NAME;
                                break;
                        }
                } else if (ch == '~' || ch == '-') {
                        if (--remain == 0) {
                                *cp = '\0';
                                return (-1);
                        }
                        *cp++ = (char)(ch = getc(fp));
                }


                if (isdigit(ch)) {
                        if (ch == '0') {
                                if ((ch = getc(fp)) == 'x') {
                                        if (--remain == 0) {
                                                *cp = '\0';
                                                return (-1);
                                        }
                                        *cp++ = (char)ch;
                                        ch = getc(fp);
                                        while (isxdigit(ch)) {
                                                if (--remain == 0) {
                                                        *cp = '\0';
                                                        return (-1);
                                                }
                                                *cp++ = (char)ch;
                                                ch = getc(fp);
                                        }
                                        (void) ungetc(ch, fp);
                                        token = T_HEXVAL;
                                } else {
                                        goto digit;
                                }
                        } else {
                                ch = getc(fp);
digit:
                                while (isdigit(ch)) {
                                        if (--remain == 0) {
                                                *cp = '\0';
                                                return (-1);
                                        }
                                        *cp++ = (char)ch;
                                        ch = getc(fp);
                                }
                                (void) ungetc(ch, fp);
                                token = T_DECVAL;
                        }
                } else if (isalpha(ch) || ch == '\\') {
                        if (ch != '\\') {
                                ch = getc(fp);
                        } else {
                                /*
                                 * if the character was a backslash,
                                 * back up so we can overwrite it with
                                 * the next (i.e. escaped) character.
                                 */
                                remain++;
                                cp--;
                        }
                        while (isnamechar(ch) || ch == '\\') {
                                if (ch == '\\')
                                        ch = getc(fp);
                                if (--remain == 0) {
                                        *cp = '\0';
                                        return (-1);
                                }
                                *cp++ = (char)ch;
                                ch = getc(fp);
                        }
                        (void) ungetc(ch, fp);
                        token = T_NAME;
                } else {
                        return (-1);
                }
                break;
        }

        *cp = '\0';

        return (token);
}

#ifdef __sparc

static void
free_confent(struct conf_entry *confent)
{
        if (confent->name)
                free(confent->name);
        if (confent->parent)
                free(confent->parent);
        if (confent->class)
                free(confent->class);
        if (confent->unit_address)
                free(confent->unit_address);
        free(confent);
}

static void
free_confent_list(struct conf_entry *confent_list)
{
        struct conf_entry *confent, *next;

        for (confent = confent_list; confent != NULL; confent = next) {
                next = confent->next;
                free_confent(confent);
        }
}

/*
 * Parse the next entry from the driver.conf file and return in the form of
 * a pointer to the conf_entry.
 */
static struct conf_entry *
parse_conf_entry(struct conf_file *filep, char *tokbuf, size_t linesize)
{
        char *prop_name, *string;
        token_t token;
        struct conf_entry *confent;
        conf_state_t state;
        int failed = 1;

        if ((confent = calloc(1, sizeof (*confent))) == NULL)
                return (NULL);

        confent->port = -1;
        confent->mpxio_disable = -1;

        state = begin;
        token = T_NAME;
        prop_name = NULL;
        string = NULL;
        do {
                switch (token) {
                case T_NAME:
                        switch (state) {
                        case prop_equals_string:
                        case prop_equals_integer:
                        case begin:
                                state = prop;
                                if ((prop_name = strdup(tokbuf)) == NULL)
                                        goto bad;
                                break;
                        default:
                                file_err(filep, tok_err, tokbuf);
                        }
                        break;
                case T_EQUALS:
                        switch (state) {
                        case prop:
                                state = prop_equals;
                                break;
                        default:
                                file_err(filep, tok_err, tokbuf);
                        }
                        break;
                case T_STRING:
                        switch (state) {
                        case prop_equals:
                                if ((string = strdup(tokbuf)) == NULL)
                                        goto bad;

                                state = begin;
                                if (strcmp(prop_name, "PARENT") == 0 ||
                                    strcmp(prop_name, "parent") == 0) {
                                        if (confent->parent) {
                                                file_err(filep,
                                "'parent' property already specified\n");
                                                goto bad;
                                        }
                                        confent->parent = string;
                                } else if (strcmp(prop_name, "NAME") == 0 ||
                                    strcmp(prop_name, "name") == 0) {
                                        if (confent->name) {
                                                file_err(filep,
                                "'name' property already specified\n");
                                                goto bad;
                                        }
                                        confent->name = string;
                                } else if (strcmp(prop_name, "CLASS") == 0 ||
                                    strcmp(prop_name, "class") == 0) {
                                        if (confent->class) {
                                                file_err(filep,
                                "'class' property already specified\n");
                                                goto bad;
                                        }
                                        confent->class = string;
                                } else if (strcmp(prop_name, "unit-address")
                                    == 0) {
                                        if (confent->unit_address) {
                                                file_err(filep,
                                "'unit-address' property already specified\n");
                                                goto bad;
                                        }
                                        confent->unit_address = string;
                                } else if (strcmp(prop_name, "mpxio-disable")
                                    == 0) {
                                        if (confent->mpxio_disable != -1) {
                                                file_err(filep,
                                "'mpxio-disable' property already specified\n");
                                                goto bad;
                                        }
                                        if (strcmp(string, "yes") == 0)
                                                confent->mpxio_disable = 1;
                                        else if (strcmp(string, "no") == 0)
                                                confent->mpxio_disable = 0;
                                        else {
                                                file_err(filep,
                                "'mpxio-disable' property setting is invalid. "
                                "The value must be either \"yes\" or \"no\"\n");
                                                goto bad;
                                        }
                                        free(string);
                                } else {
                                        free(string);
                                        state = prop_equals_string;
                                }
                                string = NULL;
                                free(prop_name);
                                prop_name = NULL;
                                break;

                        case prop_equals_string_comma:
                                state = prop_equals_string;
                                break;
                        default:
                                file_err(filep, tok_err, tokbuf);
                        }
                        break;
                case T_HEXVAL:
                case T_DECVAL:
                        switch (state) {
                        case prop_equals:
                                if (strcmp(prop_name, "port") == 0) {
                                        if (confent->port != -1) {
                                                file_err(filep,
                                        "'port' property already specified\n");
                                                goto bad;
                                        }
                                        confent->port =
                                            (int)strtol(tokbuf, NULL, 0);
                                        state = begin;
                                } else
                                        state = prop_equals_integer;
                                free(prop_name);
                                prop_name = NULL;
                                break;

                        case prop_equals_integer_comma:
                                state = prop_equals_integer;
                                break;
                        default:
                                file_err(filep, tok_err, tokbuf);
                        }
                        break;
                case T_COMMA:
                        switch (state) {
                        case prop_equals_string:
                                state = prop_equals_string_comma;
                                break;
                        case prop_equals_integer:
                                state = prop_equals_integer_comma;
                                break;
                        default:
                                file_err(filep, tok_err, tokbuf);
                        }
                        break;
                case T_NEWLINE:
                        filep->linenum++;
                        break;
                case T_POUND:
                        find_eol(filep->fp);
                        break;
                case T_EOF:
                        file_err(filep, "Unexpected EOF\n");
                        goto bad;
                default:
                        file_err(filep, tok_err, tokbuf);
                        goto bad;
                }
        } while ((token = lex(filep, tokbuf, linesize)) != T_SEMICOLON);

        failed = 0;

bad:
        if (prop_name)
                free(prop_name);
        if (string)
                free(string);
        if (failed == 1) {
                free_confent(confent);
                return (NULL);
        }
        return (confent);
}

/*
 * Parse all entries with mpxio-disable property in the given driver.conf
 * file.
 *
 * fname                driver.conf file name
 * confent_list         on return *confent_list will contain the list of
 *                      driver.conf file entries with mpxio-disable property.
 * mpxio_disable        on return *mpxio_disable is set to the setting of the
 *                      driver global mpxio-dissable property as follows.
 *                      0  if driver mpxio-disable="no"
 *                      1  if driver mpxio-disable="yes"
 *                      -1 if driver mpxio-disable property isn't specified.
 */
static void
parse_conf_file(char *fname, struct conf_entry **confent_list,
    int *mpxio_disable)
{
        struct conf_entry *confent, *tail = NULL;
        token_t token;
        struct conf_file file;
        char tokval[MAX_TOKEN_SIZE];

        *confent_list = NULL;
        *mpxio_disable = -1;
        if ((file.fp = fopen(fname, "r")) == NULL)
                return;

        file.filename = fname;
        file.linenum = 1;

        while ((token = lex(&file, tokval, MAX_TOKEN_SIZE)) != T_EOF) {
                switch (token) {
                case T_POUND:
                        /*
                         * Skip comments.
                         */
                        find_eol(file.fp);
                        break;
                case T_NAME:
                        if ((confent = parse_conf_entry(&file, tokval,
                            MAX_TOKEN_SIZE)) == NULL)
                                break;
                        /*
                         * No name indicates global property.
                         * Make sure parent and class not NULL.
                         */
                        if (confent->name == NULL) {
                                if (confent->parent ||
                                    confent->class) {
                                        file_err(&file,
                                            "missing name attribute\n");
                                } else if (confent->mpxio_disable != -1) {
                                        if (*mpxio_disable == -1)
                                                *mpxio_disable =
                                                    confent->mpxio_disable;
                                        else
                                                file_err(&file,
                                "'mpxio-disable' property already specified\n");
                                }
                                free_confent(confent);
                                break;
                        }

                        /*
                         * This is a node spec, either parent or class
                         * must be specified.
                         */
                        if (confent->parent == NULL && confent->class == NULL) {
                                file_err(&file,
                                    "missing parent or class attribute\n");
                                free_confent(confent);
                                break;
                        }

                        /* only need entries with mpxio_disable property */
                        if (confent->mpxio_disable == -1) {
                                free_confent(confent);
                                break;
                        }

                        if (tail)
                                tail->next = confent;
                        else
                                *confent_list = confent;
                        tail = confent;
                        break;

                case T_NEWLINE:
                        file.linenum++;
                        break;
                default:
                        break;
                }
        }

        (void) fclose(file.fp);
}

/*
 * Return the driver class of the given driver_name.
 * The memory for the driver class is allocated by this function and the
 * caller must free it.
 */
static char *
get_driver_class(char *rootdir, char *driver_name)
{
        FILE *fp;
        char buf[BUFSIZE];
        char driver[BUFSIZE];
        char class_name[BUFSIZE];

        logdmsg(("get_driver_class: rootdir = %s, driver name = %s\n",
            rootdir, driver_name));

        (void) snprintf(buf, sizeof (buf), "%s%s", rootdir, DRIVER_CLASSES);

        if ((fp = fopen(buf, "r")) == NULL) {
                logdmsg(("get_driver_class: failed to open %s: %s\n",
                    buf, strerror(errno)));
                return (NULL);
        }

        while (fgets(buf, sizeof (buf), fp) != NULL) {
                /* LINTED - unbounded string specifier */
                if ((sscanf(buf, "%s %s", driver, class_name) == 2) &&
                    driver[0] != '#' && strcmp(driver, driver_name) == 0) {
                        logdmsg(("get_driver_class: driver class = %s\n",
                            class_name));
                        (void) fclose(fp);
                        return (strdup(class_name));
                }
        }

        (void) fclose(fp);
        return (NULL);
}

static int
lookup_in_confent_list(struct conf_entry *confent_list,
    int match_class, char *parent, char *unit_addr, int port)
{
        struct conf_entry *confent;
        char *par;

        logdmsg(("lookup_in_confent_list: %s = \"%s\", unit_addr = \"%s\", "
            "port = %d\n", (match_class) ? "class" : "parent", parent,
            STRVAL(unit_addr), port));

        for (confent = confent_list; confent != NULL; confent = confent->next) {
                par = (match_class) ? confent->class : confent->parent;
                if (unit_addr) {
                        if (confent->unit_address != NULL &&
                            strcmp(confent->unit_address, unit_addr) == 0 &&
                            par != NULL && strcmp(par, parent) == 0)
                                return (confent->mpxio_disable);
                } else {
                        if (confent->port == port &&
                            par != NULL && strcmp(par, parent) == 0)
                                return (confent->mpxio_disable);
                }
        }
        return (-1);
}

/*
 * lookup mpxio-disabled property setting for the given path in the given
 * driver.conf file. Match the entries from most specific to least specific.
 *
 * conf_file    the path name of either fp.conf, qlc.conf or scsi_vhci.conf
 * path         /devices node path without the /devices prefix.
 *              If the conf_file is fp.conf, path must be a fp node path
 *              if the conf_file is qlc.conf, path must be a qlc node path.
 *              if the conf_file is scsi_vhci.conf, path must be NULL.
 *              ex:     /pci@8,600000/SUNW,qlc@4/fp@0,0
 *                      /pci@8,600000/SUNW,qlc@4
 *
 * returns:
 *      0       if mpxio-disable="no"
 *      1       if mpxio-disable="yes"
 *      -1      if mpxio-disable property isn't specified.
 */
static int
lookup_in_conf_file(char *rootdir, char *conf_file, char *path)
{
        struct conf_entry *confent_list = NULL;
        int mpxio_disable;
        di_node_t par_node = DI_NODE_NIL;
        char *node_name = NULL, *node_addr = NULL;
        char *unit_addr = NULL;
        int port = -1;
        char *par_node_name = NULL, *par_node_addr = NULL;
        char *par_binding_name = NULL, *par_driver_name = NULL;
        char *par_driver_class = NULL, *par_node_name_addr;
        int rv = -1;
        char buf[MAXPATHLEN];

        logdmsg(("lookup_in_conf_file: rootdir = \"%s\", conf_file = \"%s\", "
            "path = \"%s\"\n", rootdir, conf_file, STRVAL(path)));

        (void) snprintf(buf, MAXPATHLEN, "%s%s", rootdir, conf_file);
        parse_conf_file(buf, &confent_list, &mpxio_disable);
#ifdef DEBUG
        log_confent_list(buf, confent_list, mpxio_disable);
#endif

        /* if path is NULL, return driver global mpxio-disable setting */
        if (path == NULL) {
                rv = mpxio_disable;
                goto done;
        }

        if ((node_name = strrchr(path, '/')) == NULL)
                goto done;

        *node_name = '\0';
        node_name++;

        if ((node_addr = strchr(node_name, '@')) == NULL)
                goto done;

        *node_addr = '\0';
        node_addr++;

        if (strcmp(node_name, "fp") == 0) {
                /* get port number; encoded in the node addr as a hex number */
                port = (int)strtol(node_addr, NULL, 16);
        } else
                unit_addr = node_addr;

        /*
         * Match from most specific to least specific;
         * first, start the lookup based on full path.
         */
        if ((rv = lookup_in_confent_list(confent_list, 0, path,
            unit_addr, port)) != -1)
                goto done;

        /* lookup nodename@address */
        if ((par_node_name_addr = strrchr(path, '/')) != NULL) {
                par_node_name_addr++;
                if ((rv = lookup_in_confent_list(confent_list, 0,
                    par_node_name_addr, unit_addr, port)) != -1)
                        goto done;
        }

        /* di_init() doesn't work when 0 is passed in flags */
        par_node = di_init(path, DINFOMINOR);
        if (par_node != DI_NODE_NIL) {
                par_node_name = di_node_name(par_node);
                par_node_addr = di_bus_addr(par_node);
                par_binding_name = di_binding_name(par_node);
                par_driver_name = di_driver_name(par_node);
        }

        logdmsg(("par_node_name = %s\n", STRVAL(par_node_name)));
        logdmsg(("par_node_addr = %s\n", STRVAL(par_node_addr)));
        logdmsg(("par_binding_name = %s\n", STRVAL(par_binding_name)));
        logdmsg(("par_driver_name = %s\n", STRVAL(par_driver_name)));

        /* lookup bindingname@address */
        if (par_binding_name != NULL && par_binding_name != par_node_name &&
            par_node_addr != NULL) {
                (void) snprintf(buf, sizeof (buf), "%s@%s", par_binding_name,
                    par_node_addr);
                if ((rv = lookup_in_confent_list(confent_list, 0,
                    buf, unit_addr, port)) != -1)
                        goto done;
        }

        /* lookup binding name */
        if (par_binding_name != NULL) {
                if ((rv = lookup_in_confent_list(confent_list, 0,
                    par_binding_name, unit_addr, port)) != -1)
                        goto done;
        }

        if (par_driver_name != NULL) {
                /* lookup driver name */
                if ((rv = lookup_in_confent_list(confent_list, 0,
                    par_driver_name, unit_addr, port)) != -1)
                        goto done;

                /* finally, lookup class name */
                par_driver_class = get_driver_class(rootdir, par_driver_name);
                if (par_driver_class != NULL) {
                        if ((rv = lookup_in_confent_list(confent_list, 1,
                            par_driver_class, unit_addr, port)) != -1)
                                goto done;
                }
        }

        /*
         * no match so far;
         * use the driver global mpxio-disable setting if exists.
         */
        rv = mpxio_disable;

done:
        if (node_name != NULL)
                *(node_name - 1) = '/';
        if (node_addr != NULL)
                *(node_addr - 1) = '@';
        if (par_driver_class != NULL)
                free(par_driver_class);
        if (confent_list != NULL)
                free_confent_list(confent_list);
        if (par_node != DI_NODE_NIL)
                di_fini(par_node);

        return (rv);
}

/*
 * Given client_name return whether it is a phci or vhci based name.
 * client_name is /devices name of a client without the /devices prefix.
 *
 * client_name                  Return value
 * .../fp@xxx/ssd@yyy           CLIENT_TYPE_PHCI
 * .../scsi_vhci/ssd@yyy        CLIENT_TYPE_VHCI
 * other                        CLIENT_TYPE_UNKNOWN
 */
static client_type_t
client_name_type(char *client_name)
{
        client_type_t client_type;
        char *p1, *p2;

        logdmsg(("client_name_type: client_name = %s\n", client_name));

        if (strncmp(client_name, SLASH_SCSI_VHCI,
            sizeof (SLASH_SCSI_VHCI) - 1) == 0)
                return (CLIENT_TYPE_VHCI);

        if (*client_name != '/')
                return (CLIENT_TYPE_UNKNOWN);

        if ((p1 = strrchr(client_name, '/')) == NULL)
                return (CLIENT_TYPE_UNKNOWN);

        *p1 = '\0';

        if ((p2 = strrchr(client_name, '/')) != NULL &&
            strncmp(p2, SLASH_FP_AT, sizeof (SLASH_FP_AT) - 1) == 0)
                client_type = CLIENT_TYPE_PHCI;
        else
                client_type = CLIENT_TYPE_UNKNOWN;

        *p1 = '/';
        return (client_type);
}

/*
 * Compare controller name portion of dev1 and dev2.
 *
 * rootdir      root directory of the target environment
 * dev1         can be either a /dev link or /devices name in the target
 *              environemnt
 * dev2         /devices name of a device without the /devices prefix
 *
 * Returns:
 *      0       if controller names match
 *      1       if controller names don't match
 *      -1      an error occurred.
 */
static int
compare_controller(char *rootdir, char *dev1, char *dev2)
{
        int linksize;
        char *p1, *p;
        char physdev1[MAXPATHLEN];
        char buf[MAXPATHLEN];

        logdmsg(("compare_controller: rootdir = %s, dev1 = %s, dev2 = %s\n",
            rootdir, dev1, dev2));

        if (strncmp(dev1, SLASH_DEV_SLASH, sizeof (SLASH_DEV_SLASH) - 1)
            == 0) {
                (void) snprintf(buf, MAXPATHLEN, "%s%s", rootdir, dev1);
                if ((linksize = readlink(buf, physdev1, MAXPATHLEN)) > 0 &&
                    linksize < (MAXPATHLEN - 1)) {
                        physdev1[linksize] = '\0';
                        logdmsg(("compare_controller: physdev1 = %s\n",
                            physdev1));
                } else
                        return (-1);
        } else
                (void) strlcpy(physdev1, dev1, MAXPATHLEN);

        if ((p1 = strstr(physdev1, SLASH_DEVICES)) == NULL)
                return (-1);

        p1 += sizeof (SLASH_DEVICES) - 1;
        /* strip the device portion */
        if ((p = strrchr(p1, '/')) == NULL)
                return (-1);
        *p = '\0';

        if ((p = strrchr(dev2, '/')) == NULL)
                return (-1);
        *p = '\0';

        logdmsg(("compare_controller: path1 = %s, path2 = %s\n",
            p1, dev2));
        if (strcmp(p1, dev2) == 0) {
                *p = '/';
                return (0);
        } else {
                *p = '/';
                return (1);
        }
}

/*
 * Check if the specified device path is on the root controller.
 *
 * rootdir      root directory of the target environment
 * path         /devices name of a device without the /devices prefix
 *
 * Returns
 *      1       if the path is on the root controller
 *      0       if the path is not on the root controller
 *      -1      if an error occurs
 */
static int
is_root_controller(char *rootdir, char *path)
{
        FILE *fp;
        char *tmpfile;
        int rv = -1;
        struct vfstab vfsent;
        char buf[MAXPATHLEN];
        char ctd[MAXNAMELEN + 1];

        logdmsg(("is_root_controller: rootdir = %s, path = %s\n", rootdir,
            path));

        (void) snprintf(buf, MAXPATHLEN, "%s%s", rootdir, VFSTAB);

        if ((fp = fopen(buf, "r")) == NULL) {
                logdmsg(("is_root_controller: failed to open %s: %s\n",
                    buf, strerror(errno)));
                return (-1);
        }

        if (getvfsfile(fp, &vfsent, "/") != 0) {
                logdmsg(("is_root_controller: getvfsfile: failed to read "
                    "vfstab entry for mount point \"/\": %s\n",
                    strerror(errno)));
                (void) fclose(fp);
                return (-1);
        }
        (void) fclose(fp);

        /* check if the root is an svm metadisk */
        if (strncmp(vfsent.vfs_special, META_DEV, sizeof (META_DEV) - 1) != 0) {
                if (compare_controller(rootdir, vfsent.vfs_special, path) == 0)
                        return (1);
                else
                        return (0);
        }

        /* Don't use /var/run as it is not mounted in miniroot */
        if ((tmpfile = tempnam("/tmp", "diirc")) == NULL) {
                logdmsg(("is_root_controller: tempnam: failed: %s\n",
                    strerror(errno)));
                return (-1);
        }

        /* get metadisk components using metastat command */
        (void) snprintf(buf, MAXPATHLEN,
            "/usr/sbin/metastat -p %s 2>/dev/null | "
            "/usr/bin/grep ' 1 1 ' | "
            "/usr/bin/sed -e 's/^.* 1 1 //' | "
            "/usr/bin/cut -f1 -d ' ' > %s",
            vfsent.vfs_special + sizeof (META_DEV) - 1, tmpfile);

        logdmsg(("is_root_controller: command = %s\n", buf));
        fp = NULL;
        if (system(buf) == 0 && (fp = fopen(tmpfile, "r")) != NULL) {
                while (fscanf(fp, "%" VAL2STR(MAXNAMELEN) "s", ctd) == 1) {
                        (void) snprintf(buf, MAXPATHLEN, "/dev/dsk/%s", ctd);
                        if (compare_controller(rootdir, buf, path) == 0) {
                                rv = 1;
                                goto out;
                        }
                }
                rv = 0;
        }

out:
        if (fp)
                (void) fclose(fp);
        (void) unlink(tmpfile);
        free(tmpfile);
        return (rv);
}

static int
file_exists(char *rootdir, char *path)
{
        struct stat stbuf;
        char fullpath[MAXPATHLEN];
        int x;

        (void) snprintf(fullpath, MAXPATHLEN, "%s%s", rootdir, path);

        x = stat(fullpath, &stbuf);
        logdmsg(("file_exists: %s: %s\n", fullpath, (x == 0) ? "yes" : "no"));
        if (x == 0)
                return (1);
        else
                return (0);
}

/*
 * Check if mpxio is enabled or disabled on the specified device path.
 * Looks through the .conf files to determine the mpxio setting.
 *
 * rootdir      root directory of the target environment
 * path         /devices name of a device without the /devices prefix and
 *              minor name component.
 *
 * Returns
 *      1       if mpxio is disabled
 *      0       if mpxio is enabled
 *      -1      if an error occurs
 */
static int
is_mpxio_disabled(char *rootdir, char *path)
{
        int mpxio_disable;
        char *p;
        int check_root_controller;

        logdmsg(("is_mpxio_disabled: rootdir = %s, path = %s\n",
            rootdir, path));

        if (file_exists(rootdir, SCSI_VHCI_CONF) == 0) {
                /*
                 * scsi_vhci.conf doesn't exist:
                 *  if upgrading from a pre solaris 9 release. or
                 *  if this function is called during fresh or flash install
                 *  prior to installing scsi_vhci.conf file.
                 */
                if (file_exists(rootdir, "/kernel/drv"))
                        /* upgrading from pre solaris 9 */
                        return (1);
                else
                        /* fresh or flash install */
                        return (0);
        }

        mpxio_disable = lookup_in_conf_file(rootdir, SCSI_VHCI_CONF, NULL);

        /*
         * scsi_vhci.conf contains mpxio-disable property only in s9 and
         * s8+sfkpatch. This property is no longer present from s10 onwards.
         */
        if (mpxio_disable == 1) {
                /* upgrading from s8 or s9 with mpxio globally disabled */
                return (1);
        } else if (mpxio_disable == 0) {
                /* upgrading from s8 or s9 with mpxio globally enabled */
                check_root_controller = 1;
        } else {
                /*
                 * We are looking at the s10 version of the file. This is
                 * the case if this function is called after installing the
                 * new scsi_vhci.conf file.
                 */
                check_root_controller = 0;
        }

        if ((mpxio_disable = lookup_in_conf_file(rootdir, FP_CONF, path))
            != -1)
                return (mpxio_disable);

        if ((p = strrchr(path, '/')) == NULL)
                return (-1);

        *p = '\0';
        if ((mpxio_disable = lookup_in_conf_file(rootdir, QLC_CONF, path))
            != -1) {
                *p = '/';
                return (mpxio_disable);
        }
        *p = '/';

        /*
         * mpxio-disable setting is not found in the .conf files.
         * The default is to enable mpxio, except if the path is on the root
         * controller.
         *
         * In s8 and s9 mpxio is not supported on the root controller.
         * NWS supplies a patch to enable root controller support in s8 and s9.
         * If the system had the patch installed, the fp.conf file would have
         * explicit "mpxio-disable=no" for the root controller. So we would
         * have found the mpxio-disable setting when we looked up this property
         * in the fp.conf file.
         */
        if (check_root_controller) {
                mpxio_disable = is_root_controller(rootdir, path);
                logdmsg(("is_mpxio_disabled: is_root_controller returned %d\n",
                    mpxio_disable));
        } else
                mpxio_disable = 0;

        return (mpxio_disable);
}

static int
vhci_ctl(sv_iocdata_t *iocp, int cmd)
{
        int fd, rv;

        if ((fd = open(VHCI_CTL_NODE, O_RDWR)) < 0)
                return (-1);
        rv = ioctl(fd, cmd, iocp);
        (void) close(fd);
        return (rv);
}

/*
 * Convert a phci client name to vhci client name.
 *
 * phci_name    phci client /devices name without the /devices prefix and
 *              minor name component.
 *              ex: /pci@8,600000/SUNW,qlc@4/fp@0,0/ssd@w2100002037cd9f72,0
 *
 * Returns      on success, vhci client name is returned. The memory for
 *              the vhci name is allocated by this function and the caller
 *              must free it.
 *              on failure, NULL is returned.
 */
static char *
phci_to_vhci(char *phci_name)
{
        sv_iocdata_t ioc;
        char *slash, *addr, *retp;
        char vhci_name_buf[MAXPATHLEN];
        char phci_name_buf[MAXPATHLEN];
        char addr_buf[MAXNAMELEN];

        logdmsg(("phci_to_vhci: pchi_name =  %s\n", phci_name));
        (void) strlcpy(phci_name_buf, phci_name, MAXPATHLEN);

        if ((slash = strrchr(phci_name_buf, '/')) == NULL ||
            (addr = strchr(slash, '@')) == NULL)
                return (NULL);

        *slash = '\0';
        addr++;
        (void) strlcpy(addr_buf, addr, MAXNAMELEN);

        bzero(&ioc, sizeof (sv_iocdata_t));
        ioc.client = vhci_name_buf;
        ioc.phci = phci_name_buf;
        ioc.addr = addr_buf;
        if (vhci_ctl(&ioc, SCSI_VHCI_GET_CLIENT_NAME) != 0) {
                logdmsg(("phci_to_vhci: vhci_ctl failed: %s\n",
                    strerror(errno)));
                return (NULL);
        }

        retp = strdup(vhci_name_buf);
        logdmsg(("phci_to_vhci: vhci name = %s\n", STRVAL(retp)));
        return (retp);
}

static int
add_to_phci_list(char **phci_list, sv_path_info_t *pi, int npaths, int state,
    char *node_name)
{
        int rv = 0;
        char name[MAXPATHLEN];

        while (npaths--) {
                if (state == pi->ret_state) {
                        (void) snprintf(name, MAXPATHLEN, "%s/%s@%s",
                            pi->device.ret_phci, node_name, pi->ret_addr);
                        if ((*phci_list = strdup(name)) == NULL)
                                return (-1);
                        phci_list++;
                        rv++;
                }
                pi++;
        }

        return (rv);
}

static void
free_pathlist(char **pathlist)
{
        char **p;

        if (pathlist != NULL) {
                for (p = pathlist; *p != NULL; p++)
                        free(*p);
                free(pathlist);
        }
}


/*
 * Convert a vhci client name to phci client names.
 *
 * vhci_name    vhci client /devices name without the /devices prefix and
 *              minor name component.
 * num_paths    On return, *num_paths is set to the number paths in the
 *              returned path list.
 *
 * Returns      NULL terminated path list containing phci client paths is
 *              returned on success. The memory for the path list is
 *              allocated by this function and the caller must free it by
 *              calling free_pathlist().
 *              NULL is returned on failure.
 */
static char **
vhci_to_phci(char *vhci_name, int *num_paths)
{
        sv_iocdata_t ioc;
        uint_t npaths;
        int n;
        char **phci_list = NULL;
        char *node_name, *at;
        char vhci_name_buf[MAXPATHLEN];

        logdmsg(("vhci_to_phci: vchi_name =  %s\n", vhci_name));

        *num_paths = 0;
        (void) strlcpy(vhci_name_buf, vhci_name, MAXPATHLEN);

        /* first get the number paths */
        bzero(&ioc, sizeof (sv_iocdata_t));
        ioc.client = vhci_name_buf;
        ioc.ret_elem = &npaths;
        if (vhci_ctl(&ioc, SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO) != 0 ||
            npaths == 0) {
                logdmsg(("vhci_to_phci: vhci_ctl failed to get npaths: %s\n",
                    strerror(errno)));
                return (NULL);
        }

        /* now allocate memory for the path information and get all paths */
        bzero(&ioc, sizeof (sv_iocdata_t));
        ioc.client = vhci_name_buf;
        ioc.buf_elem = npaths;
        ioc.ret_elem = &npaths;
        if ((ioc.ret_buf = (sv_path_info_t *)calloc(npaths,
            sizeof (sv_path_info_t))) == NULL)
                return (NULL);
        if (vhci_ctl(&ioc, SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO) != 0 ||
            npaths == 0) {
                logdmsg(("vhci_to_phci: vhci_ctl failed: %s\n",
                    strerror(errno)));
                goto out;
        }

        if (ioc.buf_elem < npaths)
                npaths = ioc.buf_elem;

        if ((node_name = strrchr(vhci_name_buf, '/')) == NULL ||
            (at = strchr(node_name, '@')) == NULL)
                goto out;

        node_name++;
        *at = '\0';

        /* allocate one more (than npaths) for the terminating NULL pointer */
        if ((phci_list = calloc(npaths + 1, sizeof (char *))) == NULL)
                goto out;

        /*
         * add only online paths as non-online paths may not be accessible
         * in the target environment.
         */
        if ((n = add_to_phci_list(phci_list, ioc.ret_buf, npaths,
            MDI_PATHINFO_STATE_ONLINE, node_name)) <= 0)
                goto out;

        free(ioc.ret_buf);
        *num_paths = n;

#ifdef DEBUG
        logdmsg(("vhci_to_phci: phci list:\n"));
        log_pathlist(phci_list);
#endif
        return (phci_list);

out:
        free(ioc.ret_buf);
        if (phci_list)
                free_pathlist(phci_list);
        return (NULL);
}

/*
 * build list of paths accessible from the target environment
 */
static int
build_pathlist(char *rootdir, char *vhcipath, char **pathlist, int npaths)
{
        int mpxio_disabled;
        int i, j;
        char *vpath = NULL;

        for (i = 0; i < npaths; i++) {
                mpxio_disabled = is_mpxio_disabled(rootdir, pathlist[i]);
                logdmsg(("build_pathlist: mpxio_disabled = %d "
                    "on path %s\n", mpxio_disabled, pathlist[i]));
                if (mpxio_disabled == -1)
                        return (-1);
                if (mpxio_disabled == 0) {
                        /*
                         * mpxio is enabled on this phci path.
                         * So use vhci path instead of phci path.
                         */
                        if (vpath == NULL) {
                                if ((vpath = strdup(vhcipath)) == NULL)
                                        return (-1);
                                free(pathlist[i]);
                                /* keep vhci path at beginning of the list */
                                for (j = i; j > 0; j--)
                                        pathlist[j] = pathlist[j - 1];
                                pathlist[0] = vpath;
                        } else {
                                free(pathlist[i]);
                                npaths--;
                                for (j = i; j < npaths; j++)
                                        pathlist[j] = pathlist[j + 1];
                                pathlist[npaths] = NULL;
                                /* compensate for i++ in the for loop */
                                i--;
                        }
                }
        }

#ifdef DEBUG
        logdmsg(("build_pathlist: returning npaths = %d, pathlist:\n", npaths));
        log_pathlist(pathlist);
#endif
        return (npaths);
}

/*
 * Check if the specified device is refenced in the vfstab file.
 * Return 1 if referenced, 0 if not.
 *
 * rootdir      root directory of the target environment
 * nodepath     /devices path of a device in the target environment without
 *              the /devices prefix and minor component.
 */
static int
is_dev_in_vfstab(char *rootdir, char *nodepath)
{
        FILE *fp;
        int linksize;
        struct vfstab vfsent;
        char *abspath, *minor;
        char physpath[MAXPATHLEN];
        char buf[MAXPATHLEN];

        logdmsg(("is_dev_in_vfstab: rootdir = %s, nodepath = %s\n",
            rootdir, nodepath));

        (void) snprintf(buf, sizeof (buf), "%s%s", rootdir, VFSTAB);

        if ((fp = fopen(buf, "r")) == NULL)
                return (0);

        /*
         * read device specials from vfstab and compare names at physical
         * node path level.
         */
        while (getvfsent(fp, &vfsent) == 0) {
                if (strncmp(vfsent.vfs_special, SLASH_DEV_SLASH,
                    sizeof (SLASH_DEV_SLASH) - 1) == 0) {
                        (void) snprintf(buf, MAXPATHLEN, "%s%s",
                            rootdir, vfsent.vfs_special);
                        if ((linksize = readlink(buf, physpath,
                            MAXPATHLEN)) > 0 && linksize < (MAXPATHLEN - 1)) {
                                physpath[linksize] = '\0';
                                if ((abspath = strstr(physpath,
                                    SLASH_DEVICES_SLASH)) == NULL)
                                        continue;
                        } else
                                continue;
                } else if (strncmp(vfsent.vfs_special, SLASH_DEVICES_SLASH,
                    sizeof (SLASH_DEVICES_SLASH) - 1) == 0) {
                        (void) strlcpy(physpath, vfsent.vfs_special,
                            MAXPATHLEN);
                        abspath = physpath;
                } else
                        continue;

                /* point to / after /devices */
                abspath += sizeof (SLASH_DEVICES_SLASH) - 2;
                /* strip minor component */
                if ((minor = strrchr(abspath, ':')) != NULL)
                        *minor = '\0';

                if (strcmp(nodepath, abspath) == 0) {
                        (void) fclose(fp);
                        logdmsg(("is_dev_in_vfstab: returning 1\n"));
                        return (1);
                }
        }

        (void) fclose(fp);
        return (0);
}

#endif /* __sparc */

static int
devlink_callback(di_devlink_t devlink, void *argp)
{
        const char *link;

        if ((link = di_devlink_path(devlink)) != NULL)
                (void) strlcpy((char *)argp, link, MAXPATHLEN);

        return (DI_WALK_CONTINUE);
}

/*
 * Get the /dev name in the install environment corresponding to physpath.
 *
 * physpath     /devices path in the install environment without the /devices
 *              prefix.
 * buf          caller supplied buffer where the /dev name is placed on return
 * bufsz        length of the buffer
 *
 * Returns      strlen of the /dev name on success, -1 on failure.
 */
static int
get_install_devlink(char *physpath, char *buf, size_t bufsz)
{
        di_devlink_handle_t devlink_hdl;
        char devname[MAXPATHLEN];
        int tries = 0;
        int sleeptime = 2; /* number of seconds to sleep between retries */
        int maxtries = 10; /* maximum number of tries */

        logdmsg(("get_install_devlink: physpath = %s\n", physpath));

        /*
         * devlink_db sync happens after MINOR_FINI_TIMEOUT_DEFAULT secs
         * after dev link creation. So wait for minimum that amout of time.
         */

retry:
        (void) sleep(sleeptime);

        if ((devlink_hdl = di_devlink_init(NULL, 0)) == NULL) {
                logdmsg(("get_install_devlink: di_devlink_init() failed: %s\n",
                    strerror(errno)));
                return (-1);
        }

        devname[0] = '\0';
        if (di_devlink_walk(devlink_hdl, NULL, physpath, DI_PRIMARY_LINK,
            devname, devlink_callback) == 0) {
                if (devname[0] == '\0' && tries < maxtries) {
                        tries++;
                        (void) di_devlink_fini(&devlink_hdl);
                        goto retry;
                } else if (devname[0] == '\0') {
                        logdmsg(("get_install_devlink: di_devlink_walk"
                            " failed: %s\n", strerror(errno)));
                        (void) di_devlink_fini(&devlink_hdl);
                        return (-1);
                }
        } else {
                logdmsg(("get_install_devlink: di_devlink_walk failed: %s\n",
                    strerror(errno)));
                (void) di_devlink_fini(&devlink_hdl);
                return (-1);
        }

        (void) di_devlink_fini(&devlink_hdl);

        logdmsg(("get_install_devlink: devlink = %s\n", devname));
        return (strlcpy(buf, devname, bufsz));
}

/*
 * Get the /dev name in the target environment corresponding to physpath.
 *
 * rootdir      root directory of the target environment
 * physpath     /devices path in the target environment without the /devices
 *              prefix.
 * buf          caller supplied buffer where the /dev name is placed on return
 * bufsz        length of the buffer
 *
 * Returns      strlen of the /dev name on success, -1 on failure.
 */
static int
get_target_devlink(char *rootdir, char *physpath, char *buf, size_t bufsz)
{
        char *p;
        int linksize;
        DIR *dirp;
        struct dirent *direntry;
        char dirpath[MAXPATHLEN];
        char devname[MAXPATHLEN];
        char physdev[MAXPATHLEN];

        logdmsg(("get_target_devlink: rootdir = %s, physpath = %s\n",
            rootdir, physpath));

        if ((p = strrchr(physpath, '/')) == NULL)
                return (-1);

        if (strstr(p, ",raw") != NULL) {
                (void) snprintf(dirpath, MAXPATHLEN, "%s/dev/rdsk", rootdir);
        } else {
                (void) snprintf(dirpath, MAXPATHLEN, "%s/dev/dsk", rootdir);
        }

        if ((dirp = opendir(dirpath)) == NULL)
                return (-1);

        while ((direntry = readdir(dirp)) != NULL) {
                if (strcmp(direntry->d_name, ".") == 0 ||
                    strcmp(direntry->d_name, "..") == 0)
                        continue;

                (void) snprintf(devname, MAXPATHLEN, "%s/%s",
                    dirpath, direntry->d_name);

                if ((linksize = readlink(devname, physdev, MAXPATHLEN)) > 0 &&
                    linksize < (MAXPATHLEN - 1)) {
                        physdev[linksize] = '\0';
                        if ((p = strstr(physdev, SLASH_DEVICES_SLASH)) !=
                            NULL && strcmp(p + sizeof (SLASH_DEVICES) - 1,
                            physpath) == 0) {
                                (void) closedir(dirp);
                                logdmsg(("get_target_devlink: devlink = %s\n",
                                    devname + strlen(rootdir)));
                                return (strlcpy(buf, devname + strlen(rootdir),
                                    bufsz));
                        }
                }
        }

        (void) closedir(dirp);
        return (-1);
}

/*
 * Convert device name to physpath.
 *
 * rootdir      root directory
 * devname      a /dev name or /devices name under rootdir
 * physpath     caller supplied buffer where the /devices path will be placed
 *              on return (without the /devices prefix).
 * physpathlen  length of the physpath buffer
 *
 * Returns 0 on success, -1 on failure.
 */
static int
devname2physpath(char *rootdir, char *devname, char *physpath, int physpathlen)
{
        int linksize;
        char *p;
        char devlink[MAXPATHLEN];
        char tmpphyspath[MAXPATHLEN];

        logdmsg(("devname2physpath: rootdir = %s, devname = %s\n",
            rootdir, devname));

        if (strncmp(devname, SLASH_DEVICES_SLASH,
            sizeof (SLASH_DEVICES_SLASH) - 1) != 0) {
                if (*rootdir == '\0')
                        linksize = readlink(devname, tmpphyspath, MAXPATHLEN);
                else {
                        (void) snprintf(devlink, MAXPATHLEN, "%s%s",
                            rootdir, devname);
                        linksize = readlink(devlink, tmpphyspath, MAXPATHLEN);
                }
                if (linksize > 0 && linksize < (MAXPATHLEN - 1)) {
                        tmpphyspath[linksize] = '\0';
                        if ((p = strstr(tmpphyspath, SLASH_DEVICES_SLASH))
                            == NULL)
                                return (-1);
                } else
                        return (-1);
        } else
                p = devname;

        (void) strlcpy(physpath, p + sizeof (SLASH_DEVICES) - 1, physpathlen);
        logdmsg(("devname2physpath: physpath = %s\n", physpath));
        return (0);
}

/*
 * Map a device name (devname) from the target environment to the
 * install environment.
 *
 * rootdir      root directory of the target environment
 * devname      /dev or /devices name under the target environment
 * buf          caller supplied buffer where the mapped /dev name is placed
 *              on return
 * bufsz        length of the buffer
 *
 * Returns      strlen of the mapped /dev name on success, -1 on failure.
 */
int
devfs_target2install(const char *rootdir, const char *devname, char *buf,
    size_t bufsz)
{
        char physpath[MAXPATHLEN];

        logdmsg(("devfs_target2install: rootdir = %s, devname = %s\n",
            STRVAL(rootdir), STRVAL(devname)));

        if (rootdir == NULL || devname == NULL || buf == NULL || bufsz == 0)
                return (-1);

        if (strcmp(rootdir, "/") == 0)
                rootdir = "";

        if (devname2physpath((char *)rootdir, (char *)devname, physpath,
            MAXPATHLEN) != 0)
                return (-1);

#ifdef __sparc
        if (client_name_type(physpath) == CLIENT_TYPE_PHCI) {
                char *mapped_node_path, *minor;
                char minorbuf[MAXNAMELEN];

                /* strip minor component if present */
                if ((minor = strrchr(physpath, ':')) != NULL) {
                        *minor = '\0';
                        minor++;
                        (void) strlcpy(minorbuf, minor, MAXNAMELEN);
                }
                if ((mapped_node_path = phci_to_vhci(physpath)) != NULL) {
                        if (minor)
                                (void) snprintf(physpath, MAXPATHLEN,
                                    "%s:%s", mapped_node_path, minorbuf);
                        else
                                (void) strlcpy(physpath, mapped_node_path,
                                    MAXPATHLEN);
                        free(mapped_node_path);
                        logdmsg(("devfs_target2install: mapped physpath: %s\n",
                            physpath));

                } else if (minor)
                        *(minor - 1) = ':';
        }
#endif /* __sparc */

        return (get_install_devlink(physpath, buf, bufsz));
}

/*
 * Map a device name (devname) from the install environment to the target
 * environment.
 *
 * rootdir      root directory of the target environment
 * devname      /dev or /devices name under the install environment
 * buf          caller supplied buffer where the mapped /dev name is placed
 *              on return
 * bufsz        length of the buffer
 *
 * Returns      strlen of the mapped /dev name on success, -1 on failure.
 */
int
devfs_install2target(const char *rootdir, const char *devname, char *buf,
    size_t bufsz)
{
        char physpath[MAXPATHLEN];

        logdmsg(("devfs_install2target: rootdir = %s, devname = %s\n",
            STRVAL(rootdir), STRVAL(devname)));

        if (rootdir == NULL || devname == NULL || buf == NULL || bufsz == 0)
                return (-1);

        if (strcmp(rootdir, "/") == 0)
                rootdir = "";

        if (devname2physpath("", (char *)devname, physpath, MAXPATHLEN) != 0)
                return (-1);

#ifdef __sparc
        if (client_name_type(physpath) == CLIENT_TYPE_VHCI) {
                char **pathlist;
                int npaths, i, j;
                char *minor;
                char minorbuf[MAXNAMELEN];

                /* strip minor component if present */
                if ((minor = strrchr(physpath, ':')) != NULL) {
                        *minor = '\0';
                        minor++;
                        (void) strlcpy(minorbuf, minor, MAXNAMELEN);
                }

                if ((pathlist = vhci_to_phci(physpath, &npaths)) == NULL)
                        return (-1);

                if ((npaths = build_pathlist((char *)rootdir, physpath,
                    pathlist, npaths)) <= 0) {
                        free_pathlist(pathlist);
                        return (-1);
                }

                /*
                 * in case of more than one path, try to use the path
                 * referenced in the vfstab file, otherwise use the first path.
                 */
                j = 0;
                if (npaths > 1) {
                        for (i = 0; i < npaths; i++) {
                                if (is_dev_in_vfstab((char *)rootdir,
                                    pathlist[i])) {
                                        j = i;
                                        break;
                                }
                        }
                }

                if (minor)
                        (void) snprintf(physpath, MAXPATHLEN,
                            "%s:%s", pathlist[j], minorbuf);
                else
                        (void) strlcpy(physpath, pathlist[j], MAXPATHLEN);
                free_pathlist(pathlist);
        }
#endif /* __sparc */

        return (get_target_devlink((char *)rootdir, physpath, buf, bufsz));
}

/*
 * A parser for /etc/path_to_inst.
 * The user-supplied callback is called once for each entry in the file.
 * Returns 0 on success, ENOMEM/ENOENT/EINVAL on error.
 * Callback may return DI_WALK_TERMINATE to terminate the walk,
 * otherwise DI_WALK_CONTINUE.
 */
int
devfs_parse_binding_file(const char *binding_file,
        int (*callback)(void *, const char *, int,
            const char *), void *cb_arg)
{
        token_t token;
        struct conf_file file;
        char tokval[MAX_TOKEN_SIZE];
        enum { STATE_RESET, STATE_DEVPATH, STATE_INSTVAL } state;
        char *devpath;
        char *bindname;
        int instval = 0;
        int rv;

        if ((devpath = calloc(1, MAXPATHLEN)) == NULL)
                return (ENOMEM);
        if ((bindname = calloc(1, MAX_TOKEN_SIZE)) == NULL) {
                free(devpath);
                return (ENOMEM);
        }

        if ((file.fp = fopen(binding_file, "r")) == NULL) {
                free(devpath);
                free(bindname);
                return (errno);
        }

        file.filename = (char *)binding_file;
        file.linenum = 1;

        state = STATE_RESET;
        while ((token = lex(&file, tokval, MAX_TOKEN_SIZE)) != T_EOF) {
                switch (token) {
                case T_POUND:
                        /*
                         * Skip comments.
                         */
                        find_eol(file.fp);
                        break;
                case T_NAME:
                case T_STRING:
                        switch (state) {
                        case STATE_RESET:
                                if (strlcpy(devpath, tokval,
                                    MAXPATHLEN) >= MAXPATHLEN)
                                        goto err;
                                state = STATE_DEVPATH;
                                break;
                        case STATE_INSTVAL:
                                if (strlcpy(bindname, tokval,
                                    MAX_TOKEN_SIZE) >= MAX_TOKEN_SIZE)
                                        goto err;
                                rv = callback(cb_arg,
                                    devpath, instval, bindname);
                                if (rv == DI_WALK_TERMINATE)
                                        goto done;
                                if (rv != DI_WALK_CONTINUE)
                                        goto err;
                                state = STATE_RESET;
                                break;
                        default:
                                file_err(&file, tok_err, tokval);
                                state = STATE_RESET;
                                break;
                        }
                        break;
                case T_DECVAL:
                case T_HEXVAL:
                        switch (state) {
                        case STATE_DEVPATH:
                                instval = (int)strtol(tokval, NULL, 0);
                                state = STATE_INSTVAL;
                                break;
                        default:
                                file_err(&file, tok_err, tokval);
                                state = STATE_RESET;
                                break;
                        }
                        break;
                case T_NEWLINE:
                        file.linenum++;
                        state = STATE_RESET;
                        break;
                default:
                        file_err(&file, tok_err, tokval);
                        state = STATE_RESET;
                        break;
                }
        }

done:
        (void) fclose(file.fp);
        free(devpath);
        free(bindname);
        return (0);

err:
        (void) fclose(file.fp);
        free(devpath);
        free(bindname);
        return (EINVAL);
}

/*
 * Walk the minor nodes of all children below the specified device
 * by calling the provided callback with the path to each minor.
 */
static int
devfs_walk_children_minors(const char *device_path, struct stat *st,
    int (*callback)(void *, const char *), void *cb_arg, int *terminate)
{
        DIR *dir;
        struct dirent *dp;
        char *minor_path = NULL;
        int need_close = 0;
        int rv;

        if ((minor_path = calloc(1, MAXPATHLEN)) == NULL)
                return (ENOMEM);

        if ((dir = opendir(device_path)) == NULL) {
                rv = ENOENT;
                goto err;
        }
        need_close = 1;

        while ((dp = readdir(dir)) != NULL) {
                if ((strcmp(dp->d_name, ".") == 0) ||
                    (strcmp(dp->d_name, "..") == 0))
                        continue;
                (void) snprintf(minor_path, MAXPATHLEN,
                    "%s/%s", device_path, dp->d_name);
                if (stat(minor_path, st) == -1)
                        continue;
                if (S_ISDIR(st->st_mode)) {
                        rv = devfs_walk_children_minors(
                            (const char *)minor_path, st,
                            callback, cb_arg, terminate);
                        if (rv != 0)
                                goto err;
                        if (*terminate)
                                break;
                } else {
                        rv = callback(cb_arg, minor_path);
                        if (rv == DI_WALK_TERMINATE) {
                                *terminate = 1;
                                break;
                        }
                        if (rv != DI_WALK_CONTINUE) {
                                rv = EINVAL;
                                goto err;
                        }
                }
        }

        rv = 0;
err:
        if (need_close)
                (void) closedir(dir);
        if (minor_path)
                free(minor_path);
        return (rv);
}

/*
 * Return the path to each minor node for a device by
 * calling the provided callback.
 */
static int
devfs_walk_device_minors(const char *device_path, struct stat *st,
    int (*callback)(void *, const char *), void *cb_arg, int *terminate)
{
        char *minor_path;
        char *devpath;
        char *expr;
        regex_t regex;
        int need_regfree = 0;
        int need_close = 0;
        DIR *dir;
        struct dirent *dp;
        int rv;
        char *p;

        minor_path = calloc(1, MAXPATHLEN);
        devpath = calloc(1, MAXPATHLEN);
        expr = calloc(1, MAXNAMELEN);
        if (devpath == NULL || expr == NULL || minor_path == NULL) {
                rv = ENOMEM;
                goto err;
        }

        rv = EINVAL;
        if (strlcpy(devpath, device_path, MAXPATHLEN) >= MAXPATHLEN)
                goto err;
        if ((p = strrchr(devpath, '/')) == NULL)
                goto err;
        *p++ = 0;
        if (strlen(p) == 0)
                goto err;
        if (snprintf(expr, MAXNAMELEN, "%s:.*", p) >= MAXNAMELEN)
                goto err;
        if (regcomp(&regex, expr, REG_EXTENDED) != 0)
                goto err;
        need_regfree = 1;

        if ((dir = opendir(devpath)) == NULL) {
                rv = ENOENT;
                goto err;
        }
        need_close = 1;

        while ((dp = readdir(dir)) != NULL) {
                if ((strcmp(dp->d_name, ".") == 0) ||
                    (strcmp(dp->d_name, "..") == 0))
                        continue;
                (void) snprintf(minor_path, MAXPATHLEN,
                    "%s/%s", devpath, dp->d_name);
                if (stat(minor_path, st) == -1)
                        continue;
                if ((S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) &&
                    regexec(&regex, dp->d_name, 0, NULL, 0) == 0) {
                        rv = callback(cb_arg, minor_path);
                        if (rv == DI_WALK_TERMINATE) {
                                *terminate = 1;
                                break;
                        }
                        if (rv != DI_WALK_CONTINUE) {
                                rv = EINVAL;
                                goto err;
                        }
                }
        }

        rv = 0;
err:
        if (need_close)
                (void) closedir(dir);
        if (need_regfree)
                regfree(&regex);
        if (devpath)
                free(devpath);
        if (minor_path)
                free(minor_path);
        if (expr)
                free(expr);
        return (rv);
}

/*
 * Perform a walk of all minor nodes for the specified device,
 * and minor nodes below the device.
 */
int
devfs_walk_minor_nodes(const char *device_path,
        int (*callback)(void *, const char *), void *cb_arg)
{
        struct stat stbuf;
        int rv;
        int terminate = 0;

        rv = devfs_walk_device_minors(device_path,
            &stbuf, callback, cb_arg, &terminate);
        if (rv == 0 && terminate == 0) {
                rv = devfs_walk_children_minors(device_path,
                    &stbuf, callback, cb_arg, &terminate);
        }
        return (rv);
}

#ifdef DEBUG

static void
vlog_debug_msg(char *fmt, va_list ap)
{
        time_t clock;
        struct tm t;

        if (!devfsmap_debug)
                return;

        if (logfp == NULL) {
                if (*devfsmap_logfile != '\0') {
                        logfp = fopen(devfsmap_logfile, "a");
                        if (logfp)
                                (void) fprintf(logfp, "\nNew Log:\n");
                }

                if (logfp == NULL)
                        logfp = stdout;
        }

        clock = time(NULL);
        (void) localtime_r(&clock, &t);
        (void) fprintf(logfp, "%02d:%02d:%02d ", t.tm_hour, t.tm_min,
            t.tm_sec);
        (void) vfprintf(logfp, fmt, ap);
        (void) fflush(logfp);
}

static void
log_debug_msg(char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        vlog_debug_msg(fmt, ap);
        va_end(ap);
}

#ifdef __sparc

static char *
mpxio_disable_string(int mpxio_disable)
{
        if (mpxio_disable == 0)
                return ("no");
        else if (mpxio_disable == 1)
                return ("yes");
        else
                return ("not specified");
}

static void
log_confent_list(char *filename, struct conf_entry *confent_list,
    int global_mpxio_disable)
{
        struct conf_entry *confent;

        log_debug_msg("log_confent_list: filename = %s:\n", filename);
        if (global_mpxio_disable != -1)
                log_debug_msg("\tdriver global mpxio_disable = \"%s\"\n\n",
                    mpxio_disable_string(global_mpxio_disable));

        for (confent = confent_list; confent != NULL; confent = confent->next) {
                if (confent->name)
                        log_debug_msg("\tname = %s\n", confent->name);
                if (confent->parent)
                        log_debug_msg("\tparent = %s\n", confent->parent);
                if (confent->class)
                        log_debug_msg("\tclass = %s\n", confent->class);
                if (confent->unit_address)
                        log_debug_msg("\tunit_address = %s\n",
                            confent->unit_address);
                if (confent->port != -1)
                        log_debug_msg("\tport = %d\n", confent->port);
                log_debug_msg("\tmpxio_disable = \"%s\"\n\n",
                    mpxio_disable_string(confent->mpxio_disable));
        }
}

static void
log_pathlist(char **pathlist)
{
        char **p;

        for (p = pathlist; *p != NULL; p++)
                log_debug_msg("\t%s\n", *p);
}

#endif /* __sparc */

#endif /* DEBUG */