root/usr/src/lib/cfgadm_plugins/sata/common/cfga_sata.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
 * Copyright (c) 2018, Joyent, Inc.
 */

#include <sys/param.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include "cfga_sata.h"

/*
 * This file contains the entry points to the plug-in as defined in the
 * config_admin(3CFGADM) man page.
 */

/*
 * Set the version number for the cfgadm library's use.
 */
int cfga_version = CFGA_HSL_V2;

enum {
        HELP_HEADER = 1,
        HELP_CONFIG,
        HELP_RESET_PORT,
        HELP_RESET_DEVICE,
        HELP_RESET_ALL,
        HELP_PORT_DEACTIVATE,
        HELP_PORT_ACTIVATE,
        HELP_PORT_SELF_TEST,
        HELP_CNTRL_SELF_TEST,
        HELP_UNKNOWN
};

/* SATA specific help messages */
static char *sata_help[] = {
        NULL,
        "SATA specific commands:\n",
        " cfgadm -c [configure|unconfigure|disconnect|connect] ap_id "
            "[ap_id...]\n",
        " cfgadm -x sata_reset_port ap_id  [ap_id...]\n",
        " cfgadm -x sata_reset_device ap_id [ap_id...]\n",
        " cfgadm -x sata_reset_all ap_id\n",
        " cfgadm -x sata_port_deactivate ap_id [ap_id...]\n",
        " cfgadm -x sata_port_activate ap_id [ap_id...]\n",
        " cfgadm -x sata_port_self_test ap_id [ap_id...]\n",
        " cfgadm -t ap_id\n",
        "\tunknown command or option:\n",
        NULL
};      /* End help messages */


/*
 * Messages.
 */
static msgcvt_t sata_msgs[] = {
        /* CFGA_SATA_OK */
        { CVT, CFGA_OK, "" },

        /* CFGA_SATA_NACK */
        { CVT, CFGA_NACK, "" },

        /* CFGA_SATA_DEVICE_UNCONFIGURED */
        { CVT, CFGA_OK, "Device unconfigured prior to disconnect" },

        /* CFGA_SATA_UNKNOWN / CFGA_LIB_ERROR -> "Library error" */
        { CVT, CFGA_LIB_ERROR, "Unknown message; internal error" },

        /* CFGA_SATA_INTERNAL_ERROR / CFGA_LIB_ERROR -> "Library error" */
        { CVT, CFGA_LIB_ERROR, "Internal error" },

        /* CFGA_SATA_DATA_ERROR / CFGA_DATA_ERROR -> "Data error" */
        { CVT, CFGA_DATA_ERROR, "cfgadm data error" },

        /* CFGA_SATA_OPTIONS / CFGA_ERROR -> "Hardware specific failure" */
        { CVT, CFGA_ERROR, "Hardware specific option not supported" },

        /* CFGA_SATA_HWOPNOTSUPP / CFGA_ERROR -> "Hardware specific failure" */
        { CVT, CFGA_ERROR, "Hardware specific operation not supported" },

        /*
         * CFGA_SATA_DYNAMIC_AP /
         * CFGA_LIB_ERROR -> "Configuration operation invalid"
         */
        { CVT, CFGA_INVAL, "Cannot identify attached device" },

        /* CFGA_SATA_AP / CFGA_APID_NOEXIST -> "Attachment point not found" */
        { CVT, CFGA_APID_NOEXIST, "" },

        /* CFGA_SATA_PORT / CFGA_LIB_ERROR -> "Library error" */
        { CVT, CFGA_LIB_ERROR, "Cannot determine sata port number for " },

        /* CFGA_SATA_DEVCTL / CFGA_LIB_ERROR -> "Library error" */
        { CVT, CFGA_LIB_ERROR, "Internal error: "
                "Cannot allocate devctl handle " },

        /*
         * CFGA_SATA_DEV_CONFIGURE /
         * CFGA_ERROR -> "Hardware specific failure"
         */
        { CVT, CFGA_ERROR, "Failed to config device at " },

        /*
         * CFGA_SATA_DEV_UNCONFIGURE /
         * CFGA_ERROR -> "Hardware specific failure"
         */
        { CVT, CFGA_ERROR, "Failed to unconfig device at " },

        /*
         * CFGA_SATA_DISCONNECTED
         * CFGA_INVAL -> "Configuration operation invalid"
         */
        { CVT, CFGA_INVAL, "Port already disconnected " },

        /*
         * CFGA_SATA_NOT_CONNECTED
         * CFGA_INVAL -> "Configuration operation invalid"
         */
        { CVT, CFGA_INVAL, "No device connected to " },

        /*
         * CFGA_SATA_NOT_CONFIGURED /
         * CFGA_INVAL -> "Configuration operation invalid"
         */
        { CVT, CFGA_INVAL, "No device configured at " },

        /*
         * CFGA_SATA_ALREADY_CONNECTED /
         * CFGA_INVAL -> "Configuration operation invalid"
         */
        { CVT, CFGA_INVAL, "Device already connected to " },

        /*
         * CFGA_SATA_ALREADY_CONFIGURED /
         * CFGA_INVAL -> "Configuration operation invalid"
         */
        { CVT, CFGA_INVAL, "Device already configured at " },

        /*
         * CFGA_SATA_INVALID_DEVNAME /
         * CFGA_INVAL -> "Configuration operation invalid"
         */
        { CVT, CFGA_INVAL, "Cannot specify device name" },

        /* CFGA_SATA_OPEN / CFGA_LIB_ERROR -> "Library error" */
        { CVT, CFGA_LIB_ERROR, "Cannot open " },

        /* CFGA_SATA_IOCTL / CFGA_ERROR -> "Hardware specific failure"  */
        { CVT, CFGA_ERROR, "Driver ioctl failed " },

        /*
         * CFGA_SATA_BUSY /
         * CFGA_SYSTEM_BUSY -> "System is busy, try again"
         */
        { CVT, CFGA_SYSTEM_BUSY, "" },

        /* CFGA_SATA_ALLOC_FAIL / CFGA_LIB_ERROR -> "Library error" */
        { CVT, CFGA_LIB_ERROR, "Memory allocation failure" },

        /*
         * CFGA_SATA_OPNOTSUPP /
         * CFGA_OPNOTSUPP -> "Configuration operation not supported"
         */
        { CVT, CFGA_OPNOTSUPP, "Operation not supported" },

        /* CFGA_SATA_DEVLINK / CFGA_LIB_ERROR -> "Library error" */
        { CVT, CFGA_LIB_ERROR, "Could not find /dev/cfg link for " },

        /* CFGA_SATA_STATE / CFGA_LIB_ERROR -> "Library error" */
        { CVT, CFGA_LIB_ERROR, "Internal error: Unrecognized ap state" },

        /* CFGA_SATA_PRIV / CFGA_PRIV -> "Insufficient privileges" */
        { CVT, CFGA_PRIV, "" },

        /* CFGA_SATA_NVLIST / CFGA_ERROR -> "Hardware specific failure" */
        { CVT, CFGA_ERROR, "Internal error (nvlist)" },

        /* CFGA_SATA_ZEROLEN / CFGA_ERROR -> "Hardware specific failure" */
        { CVT, CFGA_ERROR, "Internal error (zerolength string)" },

        /* CFGA_SATA_RCM_HANDLE / CFGA_ERROR -> "Hardware specific failure" */
        { CVT, CFGA_ERROR, "cannot get RCM handle"},

        /*
         * CFGA_SATA_RCM_ONLINE /
         * CFGA_SYSTEM_BUSY -> "System is busy, try again"
         */
        { CVT, CFGA_SYSTEM_BUSY, "failed to online: "},

        /*
         * CFGA_SATA_RCM_OFFLINE /
         * CFGA_SYSTEM_BUSY -> "System is busy, try again"
         */
        { CVT, CFGA_SYSTEM_BUSY, "failed to offline: "},

        /* CFGA_SATA_RCM_INFO / CFGA_ERROR -> "Hardware specific failure" */
        { CVT, CFGA_ERROR, "failed to query: "}

};      /* End error messages */

static cfga_sata_ret_t
verify_params(const char *ap_id, const char *options, char **errstring);


static cfga_sata_ret_t
setup_for_devctl_cmd(const char *ap_id, devctl_hdl_t *devctl_hdl,
    nvlist_t **user_nvlistp, uint_t oflag);

static cfga_sata_ret_t
port_state(devctl_hdl_t hdl, nvlist_t *list,
    ap_rstate_t *rstate, ap_ostate_t *ostate);

static cfga_sata_ret_t
do_control_ioctl(const char *ap_id, sata_cfga_apctl_t subcommand, uint_t arg,
    void **descrp, size_t *sizep);

static void
cleanup_after_devctl_cmd(devctl_hdl_t devctl_hdl, nvlist_t *user_nvlist);

static char *
sata_get_devicepath(const char *ap_id);

static int
sata_confirm(struct cfga_confirm *confp, char *msg);

static cfga_sata_ret_t
get_port_num(const char *ap_id, uint32_t *port);

/* Utilities */

static cfga_sata_ret_t
physpath_to_devlink(const char *basedir, const char *node_path,
    char **logpp, int *l_errnop)
{
        char *linkpath;
        char *buf;
        char *real_path;
        DIR *dp;
        struct dirent *dep, *newdep;
        int deplen;
        boolean_t found = B_FALSE;
        int err = 0;
        struct stat sb;
        char *p;
        cfga_sata_ret_t rv = CFGA_SATA_INTERNAL_ERROR;

        /*
         * Using libdevinfo for this is overkill and kills performance
         * when multiple consumers of libcfgadm are executing
         * concurrently.
         */
        if ((dp = opendir(basedir)) == NULL) {
                *l_errnop = errno;
                return (CFGA_SATA_INTERNAL_ERROR);
        }

        linkpath = malloc(PATH_MAX);
        buf = malloc(PATH_MAX);
        real_path = malloc(PATH_MAX);

        deplen = pathconf(basedir, _PC_NAME_MAX);
        deplen = (deplen <= 0 ? MAXNAMELEN : deplen) +
            sizeof (struct dirent);
        dep = (struct dirent *)malloc(deplen);

        if (dep == NULL || linkpath == NULL || buf == NULL ||
            real_path == NULL) {
                *l_errnop = ENOMEM;
                rv = CFGA_SATA_ALLOC_FAIL;
                goto pp_cleanup;
        }

        *logpp = NULL;

        while (!found && (err = readdir_r(dp, dep, &newdep)) == 0 &&
            newdep != NULL) {

                assert(newdep == dep);

                if (strcmp(dep->d_name, ".") == 0 ||
                    strcmp(dep->d_name, "..") == 0)
                        continue;

                (void) snprintf(linkpath, MAXPATHLEN,
                    "%s/%s", basedir, dep->d_name);

                if (lstat(linkpath, &sb) < 0)
                        continue;

                if (S_ISDIR(sb.st_mode)) {

                        if ((rv = physpath_to_devlink(linkpath, node_path,
                            logpp, l_errnop)) != CFGA_SATA_OK) {

                                goto pp_cleanup;
                        }

                        if (*logpp != NULL)
                                found = B_TRUE;

                } else if (S_ISLNK(sb.st_mode)) {

                        bzero(buf, PATH_MAX);
                        if (readlink(linkpath, buf, PATH_MAX) < 0)
                                continue;


                        /*
                         * realpath() is too darn slow, so fake
                         * it, by using what we know about /dev
                         * links: they are always of the form:
                         * <"../">+/devices/<path>
                         */
                        p = buf;
                        while (strncmp(p, "../", 3) == 0)
                                p += 3;

                        if (p != buf)
                                p--;    /* back up to get a slash */

                        assert (*p == '/');

                        if (strcmp(p, node_path) == 0) {
                                *logpp = strdup(linkpath);
                                if (*logpp == NULL) {

                                        rv = CFGA_SATA_ALLOC_FAIL;
                                        goto pp_cleanup;
                                }

                                found = B_TRUE;
                        }
                }
        }

        free(linkpath);
        free(buf);
        free(real_path);
        free(dep);
        (void) closedir(dp);

        if (err != 0) {
                *l_errnop = err;
                return (CFGA_SATA_INTERNAL_ERROR);
        }

        return (CFGA_SATA_OK);

pp_cleanup:

        if (dp)
                (void) closedir(dp);
        if (dep)
                free(dep);
        if (linkpath)
                free(linkpath);
        if (buf)
                free(buf);
        if (real_path)
                free(real_path);
        if (*logpp) {
                free(*logpp);
                *logpp = NULL;
        }
        return (rv);
}


/*
 * Given the index into a table (msgcvt_t) of messages, get the message
 * string, converting it to the proper locale if necessary.
 * NOTE: Indexes are defined in cfga_sata.h
 */
static const char *
get_msg(uint_t msg_index, msgcvt_t *msg_tbl, uint_t tbl_size)
{
        if (msg_index >= tbl_size) {
                msg_index = CFGA_SATA_UNKNOWN;
        }

        return ((msg_tbl[msg_index].intl) ?
            dgettext(TEXT_DOMAIN, msg_tbl[msg_index].msgstr) :
            msg_tbl[msg_index].msgstr);
}

/*
 * Allocates and creates a message string (in *ret_str),
 * by concatenating all the (char *) args together, in order.
 * Last arg MUST be NULL.
 */
static void
set_msg(char **ret_str, ...)
{
        char    *str;
        size_t  total_len;
        va_list valist;

        va_start(valist, ret_str);

        total_len = (*ret_str == NULL) ? 0 : strlen(*ret_str);

        while ((str = va_arg(valist, char *)) != NULL) {
                size_t  len = strlen(str);
                char    *old_str = *ret_str;

                *ret_str = (char *)realloc(*ret_str, total_len + len + 1);
                if (*ret_str == NULL) {
                        /* We're screwed */
                        free(old_str);
                        va_end(valist);
                        return;
                }

                (void) strcpy(*ret_str + total_len, str);
                total_len += len;
        }

        va_end(valist);
}

/*
 * Error message handling.
 * For the rv passed in, looks up the corresponding error message string(s),
 * internationalized if necessary, and concatenates it into a new
 * memory buffer, and points *errstring to it.
 * Note not all rvs will result in an error message return, as not all
 * error conditions warrant a SATA-specific error message - for those
 * conditions the cfgadm generic messages are sufficient.
 *
 * Some messages may display ap_id or errno, which is why they are passed
 * in.
 */

cfga_err_t
sata_err_msg(
    char **errstring,
    cfga_sata_ret_t rv,
    const char *ap_id,
    int l_errno)
{
        if (errstring == NULL) {
                return (sata_msgs[rv].cfga_err);
        }

        /*
         * Generate the appropriate SATA-specific error message(s) (if any).
         */
        switch (rv) {
        case CFGA_SATA_OK:
        case CFGA_NACK:
                /* Special case - do nothing.  */
                break;

        case CFGA_SATA_UNKNOWN:
        case CFGA_SATA_DYNAMIC_AP:
        case CFGA_SATA_INTERNAL_ERROR:
        case CFGA_SATA_OPTIONS:
        case CFGA_SATA_ALLOC_FAIL:
        case CFGA_SATA_STATE:
        case CFGA_SATA_PRIV:
        case CFGA_SATA_OPNOTSUPP:
        case CFGA_SATA_DATA_ERROR:
                /* These messages require no additional strings passed. */
                set_msg(errstring, ERR_STR(rv), NULL);
                break;

        case CFGA_SATA_HWOPNOTSUPP:
                /* hardware-specific help needed */
                set_msg(errstring, ERR_STR(rv), NULL);
                set_msg(errstring, "\n",
                    dgettext(TEXT_DOMAIN, sata_help[HELP_HEADER]), NULL);
                set_msg(errstring, sata_help[HELP_RESET_PORT], NULL);
                set_msg(errstring, sata_help[HELP_RESET_DEVICE], NULL);
                set_msg(errstring, sata_help[HELP_RESET_ALL],  NULL);
                set_msg(errstring, sata_help[HELP_PORT_ACTIVATE], NULL);
                set_msg(errstring, sata_help[HELP_PORT_DEACTIVATE], NULL);
                set_msg(errstring, sata_help[HELP_PORT_SELF_TEST], NULL);
                set_msg(errstring, sata_help[HELP_CNTRL_SELF_TEST], NULL);
                break;

        case CFGA_SATA_AP:
        case CFGA_SATA_PORT:
        case CFGA_SATA_NOT_CONNECTED:
        case CFGA_SATA_NOT_CONFIGURED:
        case CFGA_SATA_ALREADY_CONNECTED:
        case CFGA_SATA_ALREADY_CONFIGURED:
        case CFGA_SATA_BUSY:
        case CFGA_SATA_DEVLINK:
        case CFGA_SATA_RCM_HANDLE:
        case CFGA_SATA_RCM_ONLINE:
        case CFGA_SATA_RCM_OFFLINE:
        case CFGA_SATA_RCM_INFO:
        case CFGA_SATA_DEV_CONFIGURE:
        case CFGA_SATA_DEV_UNCONFIGURE:
        case CFGA_SATA_DISCONNECTED:
                /* These messages also print ap_id.  */
                set_msg(errstring, ERR_STR(rv), "ap_id: ", ap_id, "", NULL);
                break;


        case CFGA_SATA_IOCTL:
        case CFGA_SATA_NVLIST:
                /* These messages also print errno.  */
                {
                        char *errno_str = l_errno ? strerror(l_errno) : "";

                        set_msg(errstring, ERR_STR(rv), errno_str,
                            l_errno ? "\n" : "", NULL);
                        break;
                }

        case CFGA_SATA_OPEN:
                /* These messages also apid and errno.  */
                {
                        char *errno_str = l_errno ? strerror(l_errno) : "";

                        set_msg(errstring, ERR_STR(rv), "ap_id: ", ap_id, "\n",
                            errno_str, l_errno ? "\n" : "", NULL);
                        break;
                }

        default:
                set_msg(errstring, ERR_STR(CFGA_SATA_INTERNAL_ERROR), NULL);

        } /* end switch */


        /*
         * Determine the proper error code to send back to the cfgadm library.
         */
        return (sata_msgs[rv].cfga_err);
}


/*
 * Entry points
 */
/* cfgadm entry point */
/*ARGSUSED*/
cfga_err_t
cfga_change_state(
    cfga_cmd_t state_change_cmd,
    const char *ap_id,
    const char *options,
    struct cfga_confirm *confp,
    struct cfga_msg *msgp,
    char **errstring,
    cfga_flags_t flags)
{
        int             ret;
        int             len;
        char            *msg;
        char            *devpath;
        nvlist_t        *nvl = NULL;
        ap_rstate_t     rstate;
        ap_ostate_t     ostate;
        devctl_hdl_t    hdl = NULL;
        cfga_sata_ret_t rv = CFGA_SATA_OK;
        char            *pdyn;
        char            *str_type;
        size_t          size;
        boolean_t       pmult = B_FALSE;

        /*
         * All sub-commands which can change state of device require
         * root privileges.
         */
        if (geteuid() != 0) {
                rv = CFGA_SATA_PRIV;
                goto bailout;
        }

        if ((rv = verify_params(ap_id, options, errstring)) != CFGA_SATA_OK) {
                (void) cfga_help(msgp, options, flags);
                goto bailout;
        }

        if ((rv = setup_for_devctl_cmd(ap_id, &hdl, &nvl,
            DC_RDONLY)) != CFGA_SATA_OK) {
                goto bailout;
        }

        /*
         * Checking device type. A port multiplier is not configurable - it is
         * already configured as soon as it is connected.
         */
        if ((rv = do_control_ioctl(ap_id, SATA_CFGA_GET_AP_TYPE, 0,
            (void **)&str_type, &size)) != CFGA_SATA_OK) {
                /* no such deivce */
                goto bailout;
        }
        if (strncmp(str_type, "sata-pmult", sizeof ("sata-pmult")) == 0) {
                pmult = B_TRUE;
        }

        switch (state_change_cmd) {
        case CFGA_CMD_CONFIGURE:
                if (pmult == B_TRUE) {
                        rv = CFGA_SATA_HWOPNOTSUPP;
                        goto bailout;
                }

                if ((rv = port_state(hdl, nvl, &rstate, &ostate)) !=
                    CFGA_SATA_OK)
                        goto bailout;

                if (ostate == AP_OSTATE_CONFIGURED) {
                        rv = CFGA_SATA_ALREADY_CONFIGURED;
                        goto bailout;
                }
                /* Disallow dynamic AP name component */
                if (GET_DYN(ap_id) != NULL) {
                        rv = CFGA_SATA_INVALID_DEVNAME;
                        goto bailout;
                }

                if (rstate == AP_RSTATE_EMPTY) {
                        rv = CFGA_SATA_NOT_CONNECTED;
                        goto bailout;
                }
                rv = CFGA_SATA_OK;

                if (devctl_ap_configure(hdl, nvl) != 0) {
                        rv = CFGA_SATA_DEV_CONFIGURE;
                        goto bailout;
                }

                devpath = sata_get_devicepath(ap_id);
                if (devpath == NULL) {
                        int i;
                        /*
                         * Try for some time as SATA hotplug thread
                         * takes a while to create the path then
                         * eventually give up.
                         */
                        for (i = 0; i < 12 && (devpath == NULL); i++) {
                                (void) sleep(6);
                                devpath = sata_get_devicepath(ap_id);
                        }

                        if (devpath == NULL) {
                                rv = CFGA_SATA_DEV_CONFIGURE;
                                break;
                        }
                }

                S_FREE(devpath);
                break;

        case CFGA_CMD_UNCONFIGURE:
                if (pmult == B_TRUE) {
                        rv = CFGA_SATA_HWOPNOTSUPP;
                        goto bailout;
                }

                if ((rv = port_state(hdl, nvl, &rstate, &ostate)) !=
                    CFGA_SATA_OK)
                        goto bailout;

                if (rstate != AP_RSTATE_CONNECTED) {
                        rv = CFGA_SATA_NOT_CONNECTED;
                        goto bailout;
                }

                if (ostate != AP_OSTATE_CONFIGURED) {
                        rv = CFGA_SATA_NOT_CONFIGURED;
                        goto bailout;
                }
                /* Strip off AP name dynamic component, if present */
                if ((pdyn = GET_DYN(ap_id)) != NULL) {
                        *pdyn = '\0';
                }

                rv = CFGA_SATA_OK;

                len = strlen(SATA_CONFIRM_DEVICE) +
                    strlen(SATA_CONFIRM_DEVICE_SUSPEND) +
                    strlen("Unconfigure") + strlen(ap_id);
                if ((msg = (char *)calloc(len +3, 1)) != NULL) {
                        (void) snprintf(msg, len + 3, "Unconfigure"
                            " %s%s\n%s",
                            SATA_CONFIRM_DEVICE, ap_id,
                            SATA_CONFIRM_DEVICE_SUSPEND);
                }

                if (!sata_confirm(confp, msg)) {
                        free(msg);
                        rv = CFGA_SATA_NACK;
                        break;
                }
                free(msg);

                devpath = sata_get_devicepath(ap_id);
                if (devpath == NULL) {
                        (void) printf(
                            "cfga_change_state: get device path failed\n");
                        rv = CFGA_SATA_DEV_UNCONFIGURE;
                        break;
                }

                if ((rv = sata_rcm_offline(ap_id, errstring, devpath, flags))
                    != CFGA_SATA_OK) {
                        break;
                }

                ret = devctl_ap_unconfigure(hdl, nvl);

                if (ret != 0) {
                        rv = CFGA_SATA_DEV_UNCONFIGURE;
                        if (errno == EBUSY) {
                                rv = CFGA_SATA_BUSY;
                        }
                        (void) sata_rcm_online(ap_id, errstring, devpath,
                            flags);
                } else {
                        (void) sata_rcm_remove(ap_id, errstring, devpath,
                            flags);

                }
                S_FREE(devpath);

                break;

        case CFGA_CMD_DISCONNECT:
                if ((rv = port_state(hdl, nvl, &rstate, &ostate)) !=
                    CFGA_SATA_OK)
                        goto bailout;

                if (rstate == AP_RSTATE_DISCONNECTED) {
                        rv = CFGA_SATA_DISCONNECTED;
                        goto bailout;
                }

                /* Strip off AP name dynamic component, if present */
                if ((pdyn = GET_DYN(ap_id)) != NULL) {
                        *pdyn = '\0';
                }


                rv = CFGA_SATA_OK; /* other statuses don't matter */

                /*
                 * If the port originally with device attached and was
                 * unconfigured already, the devicepath for the sd will be
                 * removed. sata_get_devicepath in this case is not necessary.
                 */
                /* only call rcm_offline if the state was CONFIGURED */
                if (ostate == AP_OSTATE_CONFIGURED &&
                    pmult == B_FALSE) {
                        devpath = sata_get_devicepath(ap_id);
                        if (devpath == NULL) {
                                (void) printf(
                                    "cfga_change_state: get path failed\n");
                                rv = CFGA_SATA_DEV_UNCONFIGURE;
                                break;
                        }

                        len = strlen(SATA_CONFIRM_DEVICE) +
                            strlen(SATA_CONFIRM_DEVICE_SUSPEND) +
                            strlen("Disconnect") + strlen(ap_id);
                        if ((msg = (char *)calloc(len +3, 1)) != NULL) {
                                (void) snprintf(msg, len + 3,
                                    "Disconnect"
                                    " %s%s\n%s",
                                    SATA_CONFIRM_DEVICE, ap_id,
                                    SATA_CONFIRM_DEVICE_SUSPEND);
                        }
                        if (!sata_confirm(confp, msg)) {
                                free(msg);
                                rv = CFGA_SATA_NACK;
                                break;
                        }
                        free(msg);

                        if ((rv = sata_rcm_offline(ap_id, errstring,
                            devpath, flags)) != CFGA_SATA_OK) {
                                break;
                        }

                        ret = devctl_ap_unconfigure(hdl, nvl);
                        if (ret != 0) {
                                (void) printf(
                                    "devctl_ap_unconfigure failed\n");
                                rv = CFGA_SATA_DEV_UNCONFIGURE;
                                if (errno == EBUSY)
                                        rv = CFGA_SATA_BUSY;
                                (void) sata_rcm_online(ap_id, errstring,
                                    devpath, flags);
                                S_FREE(devpath);

                                /*
                                 * The current policy is that if unconfigure
                                 * failed, do not continue with disconnect.
                                 * If the port needs to be forced into the
                                 * disconnect (shutdown) state,
                                 * the -x sata_port_poweroff command should be
                                 * used instead of -c disconnect
                                 */
                                break;
                        } else {
                                (void) printf("%s\n",
                                    ERR_STR(CFGA_SATA_DEVICE_UNCONFIGURED));
                                (void) sata_rcm_remove(ap_id, errstring,
                                    devpath, flags);
                        }
                        S_FREE(devpath);
                } else if (rstate == AP_RSTATE_CONNECTED ||
                    rstate == AP_RSTATE_EMPTY) {
                        len = strlen(SATA_CONFIRM_PORT) +
                            strlen(SATA_CONFIRM_PORT_DISABLE) +
                            strlen("Deactivate Port") + strlen(ap_id);
                        if ((msg = (char *)calloc(len +3, 1)) != NULL) {
                                (void) snprintf(msg, len +3,
                                    "Disconnect"
                                    " %s%s\n%s",
                                    SATA_CONFIRM_PORT, ap_id,
                                    SATA_CONFIRM_PORT_DISABLE);
                        }
                        if (!sata_confirm(confp, msg)) {
                                free(msg);
                                rv = CFGA_SATA_NACK;
                                break;
                        }
                }
                ret = devctl_ap_disconnect(hdl, nvl);
                if (ret != 0) {
                        rv = CFGA_SATA_IOCTL;
                        if (errno == EBUSY) {
                                rv = CFGA_SATA_BUSY;
                        }
                }
                break;

        case CFGA_CMD_CONNECT:
                if ((rv = port_state(hdl, nvl, &rstate, &ostate)) !=
                    CFGA_SATA_OK)
                        goto bailout;

                if (rstate == AP_RSTATE_CONNECTED) {
                        rv = CFGA_SATA_ALREADY_CONNECTED;
                        goto bailout;
                }

                len = strlen(SATA_CONFIRM_PORT) +
                    strlen(SATA_CONFIRM_PORT_ENABLE) +
                    strlen("Activate Port") + strlen(ap_id);
                if ((msg = (char *)calloc(len +3, 1)) != NULL) {
                        (void) snprintf(msg, len +3, "Activate"
                            " %s%s\n%s",
                            SATA_CONFIRM_PORT, ap_id,
                            SATA_CONFIRM_PORT_ENABLE);
                }
                if (!sata_confirm(confp, msg)) {
                        rv = CFGA_SATA_NACK;
                        break;
                }

                /* Disallow dynamic AP name component */
                if (GET_DYN(ap_id) != NULL) {
                        rv = CFGA_SATA_INVALID_DEVNAME;
                        goto bailout;
                }

                ret = devctl_ap_connect(hdl, nvl);
                if (ret != 0) {
                        rv = CFGA_SATA_IOCTL;
                } else {
                        rv = CFGA_SATA_OK;
                }

                break;

        case CFGA_CMD_LOAD:
        case CFGA_CMD_UNLOAD:
                (void) cfga_help(msgp, options, flags);
                rv = CFGA_SATA_OPNOTSUPP;
                break;

        case CFGA_CMD_NONE:
        default:
                (void) cfga_help(msgp, options, flags);
                rv = CFGA_SATA_INTERNAL_ERROR;
        }

bailout:
        cleanup_after_devctl_cmd(hdl, nvl);

        return (sata_err_msg(errstring, rv, ap_id, errno));
}

/* cfgadm entry point */
cfga_err_t
cfga_private_func(
    const char *func,
    const char *ap_id,
    const char *options,
    struct cfga_confirm *confp,
    struct cfga_msg *msgp,
    char **errstring,
    cfga_flags_t flags)
{
        int                     len;
        char                    *msg;
        nvlist_t                *list = NULL;
        ap_ostate_t             ostate;
        ap_rstate_t             rstate;
        devctl_hdl_t            hdl = NULL;
        cfga_sata_ret_t         rv;
        char                    *str_p;
        size_t                  size;

        if ((rv = verify_params(ap_id, NULL, errstring)) != CFGA_SATA_OK) {
                (void) cfga_help(msgp, options, flags);
                return (sata_err_msg(errstring, rv, ap_id, errno));
        }

        /*
         * All subcommands which can change state of device require
         * root privileges.
         */
        if (geteuid() != 0) {
                rv = CFGA_SATA_PRIV;
                goto bailout;
        }

        if (func == NULL) {
                (void) printf("No valid option specified\n");
                rv = CFGA_SATA_OPTIONS;
                goto bailout;
        }

        if ((rv = setup_for_devctl_cmd(ap_id, &hdl, &list, 0)) !=
            CFGA_SATA_OK) {
                goto bailout;
        }

        /* We do not care here about dynamic AP name component */
        if ((str_p = GET_DYN(ap_id)) != NULL) {
                *str_p = '\0';
        }

        rv = CFGA_SATA_OK;

        if (strcmp(func, SATA_RESET_PORT) == 0) {
                len = strlen(SATA_CONFIRM_PORT) +
                    strlen(SATA_CONFIRM_DEVICE_ABORT) +
                    strlen("Reset Port") + strlen(ap_id);

                if ((msg = (char *)calloc(len +3, 1)) != NULL) {
                        (void) snprintf(msg, len +3, "Reset"
                            " %s%s\n%s",
                            SATA_CONFIRM_PORT, ap_id,
                            SATA_CONFIRM_DEVICE_ABORT);
                } else {
                        rv = CFGA_SATA_NACK;
                        goto bailout;
                }

                if (!sata_confirm(confp, msg)) {
                        rv = CFGA_SATA_NACK;
                        goto bailout;
                }

                rv = do_control_ioctl(ap_id, SATA_CFGA_RESET_PORT, 0,
                    (void **)&str_p, &size);

        } else if (strcmp(func, SATA_RESET_DEVICE) == 0) {
                if ((rv = port_state(hdl, list, &rstate, &ostate)) !=
                    CFGA_SATA_OK)
                        goto bailout;
                /*
                 * Reset device function requires device to be connected
                 */
                if (rstate != AP_RSTATE_CONNECTED) {
                        rv = CFGA_SATA_NOT_CONNECTED;
                        goto bailout;
                }

                len = strlen(SATA_CONFIRM_DEVICE) +
                    strlen(SATA_CONFIRM_DEVICE_ABORT) +
                    strlen("Reset Device") + strlen(ap_id);

                if ((msg = (char *)calloc(len +3, 1)) != NULL) {
                        (void) snprintf(msg, len +3, "Reset"
                            " %s%s\n%s",
                            SATA_CONFIRM_DEVICE, ap_id,
                            SATA_CONFIRM_DEVICE_ABORT);
                } else {
                        rv = CFGA_SATA_NACK;
                        goto bailout;
                }

                if (!sata_confirm(confp, msg)) {
                        rv = CFGA_SATA_NACK;
                        goto bailout;
                }

                rv = do_control_ioctl(ap_id, SATA_CFGA_RESET_DEVICE, 0,
                    (void **)&str_p, &size);

        } else if (strcmp(func, SATA_RESET_ALL) == 0) {
                len = strlen(SATA_CONFIRM_CONTROLLER) +
                    strlen(SATA_CONFIRM_CONTROLLER_ABORT) +
                    strlen("Reset All") + strlen(ap_id);

                if ((msg = (char *)calloc(len +3, 1)) != NULL) {
                        (void) snprintf(msg, len +3, "Reset"
                            " %s%s\n%s",
                            SATA_CONFIRM_CONTROLLER, ap_id,
                            SATA_CONFIRM_CONTROLLER_ABORT);
                } else {
                        rv = CFGA_SATA_NACK;
                        goto bailout;
                }

                if (!sata_confirm(confp, msg)) {
                        rv = CFGA_SATA_NACK;
                        goto bailout;
                }
                rv = do_control_ioctl(ap_id, SATA_CFGA_RESET_ALL, 0,
                    (void **)&str_p, &size);

        } else if (strcmp(func, SATA_PORT_DEACTIVATE) == 0) {
                len = strlen(SATA_CONFIRM_PORT) +
                    strlen(SATA_CONFIRM_PORT_DISABLE) +
                    strlen("Deactivate Port") + strlen(ap_id);

                if ((msg = (char *)calloc(len +3, 1)) != NULL) {
                        (void) snprintf(msg, len +3, "Deactivate"
                            " %s%s\n%s",
                            SATA_CONFIRM_PORT, ap_id,
                            SATA_CONFIRM_PORT_DISABLE);
                } else {
                        rv = CFGA_SATA_NACK;
                        goto bailout;
                }
                if (!sata_confirm(confp, msg)) {
                        rv = CFGA_SATA_NACK;
                        goto bailout;
                }

                rv = do_control_ioctl(ap_id, SATA_CFGA_PORT_DEACTIVATE, 0,
                    (void **)&str_p, &size);

        } else if (strcmp(func, SATA_PORT_ACTIVATE) == 0) {
                len = strlen(SATA_CONFIRM_PORT) +
                    strlen(SATA_CONFIRM_PORT_ENABLE) +
                    strlen("Activate Port") + strlen(ap_id);

                if ((msg = (char *)calloc(len +3, 1)) != NULL) {
                        (void) snprintf(msg, len +3, "Activate"
                            " %s%s\n%s",
                            SATA_CONFIRM_PORT, ap_id,
                            SATA_CONFIRM_PORT_ENABLE);
                } else {
                        rv = CFGA_SATA_NACK;
                        goto bailout;
                }
                if (!sata_confirm(confp, msg)) {
                        rv = CFGA_SATA_NACK;
                        goto bailout;
                }

                rv = do_control_ioctl(ap_id, SATA_CFGA_PORT_ACTIVATE,
                    0, (void **)&str_p, &size);
                goto bailout;

        } else if (strcmp(func, SATA_PORT_SELF_TEST) == 0) {
                len = strlen(SATA_CONFIRM_PORT) +
                    strlen(SATA_CONFIRM_DEVICE_SUSPEND) +
                    strlen("Self Test Port") + strlen(ap_id);

                if ((msg = calloc(len +3, 1)) != NULL) {
                        (void) snprintf(msg, len +3, "Self Test"
                            " %s%s\n%s",
                            SATA_CONFIRM_PORT, ap_id,
                            SATA_CONFIRM_DEVICE_SUSPEND);
                } else {
                        rv = CFGA_SATA_NACK;
                        goto bailout;
                }
                if (!sata_confirm(confp, msg)) {
                        rv = CFGA_SATA_NACK;
                        goto bailout;
                }

                rv = do_control_ioctl(ap_id, SATA_CFGA_PORT_SELF_TEST,
                    0, (void **)&str_p, &size);
        } else {
                /* Unrecognized operation request */
                rv = CFGA_SATA_HWOPNOTSUPP;
        }

bailout:
        cleanup_after_devctl_cmd(hdl, list);

        return (sata_err_msg(errstring, rv, ap_id, errno));

}

/* cfgadm entry point */
/*ARGSUSED*/
cfga_err_t
cfga_test(
    const char *ap_id,
    const char *options,
    struct cfga_msg *msgp,
    char **errstring,
    cfga_flags_t flags)
{
        /* Should call ioctl for self test - phase 2 */
        return (CFGA_OPNOTSUPP);
}


int
sata_check_target_node(di_node_t node, void *arg)
{
        char *minorpath;
        char *cp;

        minorpath = di_devfs_minor_path(di_minor_next(node, DI_MINOR_NIL));
        if (minorpath != NULL) {
                if (strstr(minorpath, arg) != NULL) {
                        cp = strrchr(minorpath, (int)*MINOR_SEP);
                        if (cp != NULL) {
                                (void) strcpy(arg, cp);
                        }
                        free(minorpath);
                        return (DI_WALK_TERMINATE);
                }
                free(minorpath);
        }
        return (DI_WALK_CONTINUE);
}

struct chk_dev {
        int c_isblk;
        char *c_minor;
};

/*ARGSUSED*/
static int
chk_dev_fcn(di_node_t node, di_minor_t minor, void *arg)
{
        char    *mn;
        struct chk_dev *chkp = (struct chk_dev *)arg;

        mn = di_minor_name(minor);
        if (mn == NULL)
                return (DI_WALK_CONTINUE);

        if (strcmp(mn, chkp->c_minor) != 0)
                return (DI_WALK_CONTINUE);

        chkp->c_isblk = di_minor_spectype(minor) == S_IFBLK ? 1 : 0;

        return (DI_WALK_TERMINATE);
}

/*
 * Don't use devfs if stat() in /devices fails. Use libdevinfo instead.
 * Retired devices don't show up in devfs.
 *
 *      Returns:
 *              1 - minor exists and is of type BLK
 *              0 - minor does not exist or is not of type BLK.
 */
static int
is_devinfo_blk(char *minor_path)
{
        char    *minor_portion;
        struct chk_dev chk_dev;
        di_node_t node;
        int     rv;

        /*
         * prune minor path for di_init() - no /devices prefix and no minor name
         */
        if (strncmp(minor_path, "/devices/", strlen("/devices/")) != 0)
                return (0);

        minor_portion = strrchr(minor_path, *MINOR_SEP);
        if (minor_portion == NULL)
                return (0);

        *minor_portion = 0;

        node = di_init(minor_path + strlen("/devices"), DINFOMINOR);

        *minor_portion = *MINOR_SEP;

        if (node == DI_NODE_NIL)
                return (0);

        chk_dev.c_isblk = 0;
        chk_dev.c_minor = minor_portion + 1;

        rv = di_walk_minor(node, NULL, 0, &chk_dev, chk_dev_fcn);

        di_fini(node);

        if (rv == 0 && chk_dev.c_isblk)
                return (1);
        else
                return (0);
}

/*
 * The dynamic component buffer returned by this function has to be freed!
 */
int
sata_make_dyncomp(const char *ap_id, char **dyncomp, const char *type)
{
        char    *devpath = NULL;
        char    *cp = NULL;
        int     l_errno;
        char    minor_path[MAXPATHLEN];
        char    name_part[MAXNAMELEN];
        char    *devlink = NULL;
        char    *minor_portion = NULL;
        int     deplen;
        int     err;
        DIR     *dp = NULL;
        struct stat sb;
        struct dirent *dep = NULL;
        struct dirent *newdep = NULL;
        char    *p;

        assert(dyncomp != NULL);

        /*
         * Get target node path
         */
        devpath = sata_get_devicepath(ap_id);
        if (devpath == NULL) {

                (void) printf("cfga_list_ext: cannot locate target device\n");
                return (CFGA_SATA_DYNAMIC_AP);

        } else {

                cp = strrchr(devpath, *PATH_SEP);
                assert(cp != NULL);
                *cp = 0;        /* terminate path for opendir() */

                (void) strncpy(name_part, cp + 1, MAXNAMELEN);

                /*
                 * Using libdevinfo for this is overkill and kills
                 * performance when many consumers are using libcfgadm
                 * concurrently.
                 */
                if ((dp = opendir(devpath)) == NULL) {
                        goto bailout;
                }

                /*
                 * deplen is large enough to fit the largest path-
                 * struct dirent includes one byte (the terminator)
                 * so we don't add 1 to the calculation here.
                 */
                deplen = pathconf(devpath, _PC_NAME_MAX);
                deplen = ((deplen <= 0) ? MAXNAMELEN : deplen) +
                    sizeof (struct dirent);
                dep = (struct dirent *)malloc(deplen);
                if (dep == NULL)
                        goto bailout;

                while ((err = readdir_r(dp, dep, &newdep)) == 0 &&
                    newdep != NULL) {

                        assert(newdep == dep);

                        if (strcmp(dep->d_name, ".") == 0 ||
                            strcmp(dep->d_name, "..") == 0 ||
                            (minor_portion = strchr(dep->d_name,
                            *MINOR_SEP)) == NULL)
                                continue;

                        *minor_portion = 0;
                        if (strcmp(dep->d_name, name_part) != 0)
                                continue;
                        *minor_portion = *MINOR_SEP;

                        (void) snprintf(minor_path, MAXPATHLEN,
                            "%s/%s", devpath, dep->d_name);

                        /*
                         * Break directly for tape device
                         */
                        if (strcmp(type, "tape") == 0)
                                break;

                        /*
                         * If stat() fails, the device *may* be retired.
                         * Check via libdevinfo if the device has a BLK minor.
                         * We don't use libdevinfo all the time, since taking
                         * a snapshot is slower than a stat().
                         */
                        if (stat(minor_path, &sb) < 0) {
                                if (is_devinfo_blk(minor_path)) {
                                        break;
                                } else {
                                        continue;
                                }
                        }

                        if (S_ISBLK(sb.st_mode))
                                break;

                }

                (void) closedir(dp);
                free(dep);
                free(devpath);

                dp = NULL;
                dep = NULL;
                devpath = NULL;

                /*
                 * If there was an error, or we didn't exit the loop
                 * by finding a block or character device, bail out.
                 */
                if (err != 0 || newdep == NULL)
                        goto bailout;

                /*
                 * Look for links to the physical path in /dev/dsk
                 * and /dev/rmt. So far, sata modue supports disk,
                 * dvd and tape devices, so we will first look for
                 * BLOCK devices, and then look for tape devices.
                 */
                (void) physpath_to_devlink("/dev/dsk",
                    minor_path, &devlink, &l_errno);

                /* postprocess and copy logical name here */
                if (devlink != NULL) {
                        /*
                         * For disks, remove partition/slice info
                         */
                        if ((cp = strstr(devlink, "dsk/")) != NULL) {
                                /* cXtYdZ[(s[0..15])|(p[0..X])] */
                                if ((p = strchr(cp + 4, 'd')) != NULL) {
                                        p++;    /* Skip the 'd' */
                                        while (*p != 0 && isdigit(*p))
                                                p++;
                                        *p = 0;
                                }
                                *dyncomp = strdup(cp);
                        }

                        free(devlink);
                } else if (strcmp(type, "tape") == 0) {

                        /*
                         * For tape device, logical name looks like
                         * rmt/X
                         */
                        (void) physpath_to_devlink("/dev/rmt",
                            minor_path, &devlink, &l_errno);

                        if (devlink != NULL) {
                                if ((cp = strstr(devlink, "rmt/")) != NULL) {
                                        *dyncomp = strdup(cp);
                                }

                                free(devlink);
                        }
                }

                return (SATA_CFGA_OK);
        }

bailout:
        if (dp)
                (void) closedir(dp);
        if (devpath)
                free(devpath);
        if (dep)
                free(dep);
        return (CFGA_SATA_DYNAMIC_AP);
}

/* cfgadm entry point */
/*ARGSUSED*/
cfga_err_t
cfga_list_ext(
    const char *ap_id,
    cfga_list_data_t **ap_id_list,
    int *nlistp,
    const char *options,
    const char *listopts,
    char **errstring,
    cfga_flags_t flags)
{
        int                     l_errno;
        char                    *ap_id_log = NULL;
        size_t                  size;
        nvlist_t                *user_nvlist = NULL;
        devctl_hdl_t            devctl_hdl = NULL;
        cfga_sata_ret_t         rv = CFGA_SATA_OK;
        devctl_ap_state_t       devctl_ap_state;
        char                    *pdyn;
        boolean_t               pmult = B_FALSE;
        uint32_t                port;


        if ((rv = verify_params(ap_id, options, errstring)) != CFGA_SATA_OK) {
                goto bailout;
        }
        /* We do not care here about dynamic AP name component */
        if ((pdyn = GET_DYN(ap_id)) != NULL) {
                *pdyn = '\0';
        }

        if (ap_id_list == NULL || nlistp == NULL) {
                rv = CFGA_SATA_DATA_ERROR;
                goto bailout;
        }

        /* Get ap status */
        if ((rv = setup_for_devctl_cmd(ap_id, &devctl_hdl, &user_nvlist,
            DC_RDONLY)) != CFGA_SATA_OK) {
                goto bailout;
        }

        /* will call dc_cmd to send IOCTL to kernel */
        if (devctl_ap_getstate(devctl_hdl, user_nvlist,
            &devctl_ap_state) == -1) {
                cleanup_after_devctl_cmd(devctl_hdl, user_nvlist);
                rv = CFGA_SATA_IOCTL;
                goto bailout;
        }

        cleanup_after_devctl_cmd(devctl_hdl, user_nvlist);

        /*
         * Create cfga_list_data_t struct.
         */
        if ((*ap_id_list =
            (cfga_list_data_t *)malloc(sizeof (**ap_id_list))) == NULL) {
                rv = CFGA_SATA_ALLOC_FAIL;
                goto bailout;
        }
        *nlistp = 1;

        /*
         * Rest of the code fills in the cfga_list_data_t struct.
         */

        /* Get /dev/cfg path to corresponding to the physical ap_id */
        /* Remember ap_id_log must be freed */
        rv = physpath_to_devlink(CFGA_DEV_DIR, (char *)ap_id,
            &ap_id_log, &l_errno);

        if (rv != 0) {
                rv = CFGA_SATA_DEVLINK;
                goto bailout;
        }

        /* Get logical ap_id corresponding to the physical */
        if (ap_id_log == NULL || strstr(ap_id_log, CFGA_DEV_DIR) == NULL) {
                rv = CFGA_SATA_DEVLINK;
                goto bailout;
        }

        (void) strlcpy((*ap_id_list)->ap_log_id,
            /* Strip off /dev/cfg/ */ ap_id_log + strlen(CFGA_DEV_DIR)+ 1,
            sizeof ((*ap_id_list)->ap_log_id));

        free(ap_id_log);
        ap_id_log = NULL;

        (void) strlcpy((*ap_id_list)->ap_phys_id, ap_id,
            sizeof ((*ap_id_list)->ap_phys_id));

        switch (devctl_ap_state.ap_rstate) {
        case AP_RSTATE_EMPTY:
                (*ap_id_list)->ap_r_state = CFGA_STAT_EMPTY;
                break;

        case AP_RSTATE_DISCONNECTED:
                (*ap_id_list)->ap_r_state = CFGA_STAT_DISCONNECTED;
                break;

        case AP_RSTATE_CONNECTED:
                (*ap_id_list)->ap_r_state = CFGA_STAT_CONNECTED;
                break;

        default:
                rv = CFGA_SATA_STATE;
                goto bailout;
        }

        switch (devctl_ap_state.ap_ostate) {
        case AP_OSTATE_CONFIGURED:
                (*ap_id_list)->ap_o_state = CFGA_STAT_CONFIGURED;
                break;

        case AP_OSTATE_UNCONFIGURED:
                (*ap_id_list)->ap_o_state = CFGA_STAT_UNCONFIGURED;
                break;

        default:
                rv = CFGA_SATA_STATE;
                goto bailout;
        }

        switch (devctl_ap_state.ap_condition) {
        case AP_COND_OK:
                (*ap_id_list)->ap_cond = CFGA_COND_OK;
                break;

        case AP_COND_FAILING:
                (*ap_id_list)->ap_cond = CFGA_COND_FAILING;
                break;

        case AP_COND_FAILED:
                (*ap_id_list)->ap_cond = CFGA_COND_FAILED;
                break;

        case AP_COND_UNUSABLE:
                (*ap_id_list)->ap_cond = CFGA_COND_UNUSABLE;
                break;

        case AP_COND_UNKNOWN:
                (*ap_id_list)->ap_cond = CFGA_COND_UNKNOWN;
                break;

        default:
                rv = CFGA_SATA_STATE;
                goto bailout;
        }

        (*ap_id_list)->ap_class[0] = '\0';      /* Filled by libcfgadm */
        (*ap_id_list)->ap_busy = devctl_ap_state.ap_in_transition;
        (*ap_id_list)->ap_status_time = devctl_ap_state.ap_last_change;
        (*ap_id_list)->ap_info[0] = '\0';

        if ((*ap_id_list)->ap_r_state == CFGA_STAT_CONNECTED) {
                char *str_p;
                int skip, i;

                /*
                 * Fill in the 'Information' field for the -v option
                 * Model (MOD:)
                 */
                if ((rv = do_control_ioctl(ap_id, SATA_CFGA_GET_MODEL_INFO,
                    0, (void **)&str_p, &size)) != CFGA_SATA_OK) {
                        (void) printf(
                            "SATA_CFGA_GET_MODULE_INFO ioctl failed\n");
                        goto bailout;
                }
                /* drop leading and trailing spaces */
                skip = strspn(str_p, " ");
                for (i = size - 1; i >= 0; i--) {
                        if (str_p[i] == '\040')
                                str_p[i] = '\0';
                        else if (str_p[i] != '\0')
                                break;
                }

                (void) strlcpy((*ap_id_list)->ap_info, "Mod: ",
                    sizeof ((*ap_id_list)->ap_info));
                (void) strlcat((*ap_id_list)->ap_info, str_p + skip,
                    sizeof ((*ap_id_list)->ap_info));

                free(str_p);

                /*
                 * Fill in the 'Information' field for the -v option
                 * Firmware revision (FREV:)
                 */
                if ((rv = do_control_ioctl(ap_id,
                    SATA_CFGA_GET_REVFIRMWARE_INFO,
                    0, (void **)&str_p, &size)) != CFGA_SATA_OK) {
                        (void) printf(
                            "SATA_CFGA_GET_REVFIRMWARE_INFO ioctl failed\n");
                        goto bailout;
                }
                /* drop leading and trailing spaces */
                skip = strspn(str_p, " ");
                for (i = size - 1; i >= 0; i--) {
                        if (str_p[i] == '\040')
                                str_p[i] = '\0';
                        else if (str_p[i] != '\0')
                                break;
                }
                (void) strlcat((*ap_id_list)->ap_info, " FRev: ",
                    sizeof ((*ap_id_list)->ap_info));
                (void) strlcat((*ap_id_list)->ap_info, str_p + skip,
                    sizeof ((*ap_id_list)->ap_info));

                free(str_p);


                /*
                 * Fill in the 'Information' field for the -v option
                 * Serial Number (SN:)
                 */
                if ((rv = do_control_ioctl(ap_id,
                    SATA_CFGA_GET_SERIALNUMBER_INFO,
                    0, (void **)&str_p, &size)) != CFGA_SATA_OK) {
                        (void) printf(
                            "SATA_CFGA_GET_SERIALNUMBER_INFO ioctl failed\n");
                        goto bailout;
                }
                /* drop leading and trailing spaces */
                skip = strspn(str_p, " ");
                for (i = size - 1; i >= 0; i--) {
                        if (str_p[i] == '\040')
                                str_p[i] = '\0';
                        else if (str_p[i] != '\0')
                                break;
                }
                (void) strlcat((*ap_id_list)->ap_info, " SN: ",
                    sizeof ((*ap_id_list)->ap_info));
                (void) strlcat((*ap_id_list)->ap_info, str_p + skip,
                    sizeof ((*ap_id_list)->ap_info));

                free(str_p);



                /* Fill in ap_type which is collected from HBA driver */
                /* call do_control_ioctl TBD */
                if ((rv = do_control_ioctl(ap_id, SATA_CFGA_GET_AP_TYPE, 0,
                    (void **)&str_p, &size)) != CFGA_SATA_OK) {
                        (void) printf(
                            "SATA_CFGA_GET_AP_TYPE ioctl failed\n");
                        goto bailout;
                }

                (void) strlcpy((*ap_id_list)->ap_type, str_p,
                    sizeof ((*ap_id_list)->ap_type));

                free(str_p);

                /*
                 * Checking device type. Port multiplier has no dynamic
                 * suffix.
                 */
                if (strncmp((*ap_id_list)->ap_type, "sata-pmult",
                    sizeof ("sata-pmult")) == 0)
                        pmult = B_TRUE;

                if ((*ap_id_list)->ap_o_state == CFGA_STAT_CONFIGURED &&
                    pmult == B_FALSE) {

                        char *dyncomp = NULL;

                        /*
                         * This is the case where we need to generate
                         * a dynamic component of the ap_id, i.e. device.
                         */
                        rv = sata_make_dyncomp(ap_id, &dyncomp,
                            (*ap_id_list)->ap_type);
                        if (rv != CFGA_SATA_OK)
                                goto bailout;
                        if (dyncomp != NULL) {
                                (void) strcat((*ap_id_list)->ap_log_id,
                                    DYN_SEP);
                                (void) strlcat((*ap_id_list)->ap_log_id,
                                    dyncomp,
                                    sizeof ((*ap_id_list)->ap_log_id));
                                free(dyncomp);
                        }
                }

        } else {
                /* This is an empty port */
                if (get_port_num(ap_id, &port) != CFGA_SATA_OK) {
                        goto bailout;
                }

                if (port & SATA_CFGA_PMPORT_QUAL) {
                        (void) strlcpy((*ap_id_list)->ap_type, "pmult-port",
                            sizeof ((*ap_id_list)->ap_type));
                } else {
                        (void) strlcpy((*ap_id_list)->ap_type, "sata-port",
                            sizeof ((*ap_id_list)->ap_type));
                }
        }

        return (sata_err_msg(errstring, rv, ap_id, errno));

bailout:
        if (*ap_id_list != NULL) {
                free(*ap_id_list);
        }
        if (ap_id_log != NULL) {
                free(ap_id_log);
        }

        return (sata_err_msg(errstring, rv, ap_id, errno));
}
/*
 * This routine accepts a string adn prints it using
 * the message print routine argument.
 */
static void
cfga_msg(struct cfga_msg *msgp, const char *str)
{
        int len;
        char *q;

        if (msgp == NULL || msgp->message_routine == NULL) {
                (void) printf("cfga_msg: NULL msgp\n");
                return;
        }

        if ((len = strlen(str)) == 0) {
                (void) printf("cfga_msg: null str\n");
                return;
        }

        if ((q = (char *)calloc(len + 1, 1)) == NULL) {
                perror("cfga_msg");
                return;
        }

        (void) strcpy(q, str);
        (void) (*msgp->message_routine)(msgp->appdata_ptr, q);

        free(q);
}

/* cfgadm entry point */
/* ARGSUSED */
cfga_err_t
cfga_help(struct cfga_msg *msgp, const char *options, cfga_flags_t flags)
{
        if (options != NULL) {
                cfga_msg(msgp, dgettext(TEXT_DOMAIN, sata_help[HELP_UNKNOWN]));
                cfga_msg(msgp, options);
        }
        cfga_msg(msgp, dgettext(TEXT_DOMAIN, sata_help[HELP_HEADER]));
        cfga_msg(msgp, sata_help[HELP_CONFIG]);
        cfga_msg(msgp, sata_help[HELP_RESET_PORT]);
        cfga_msg(msgp, sata_help[HELP_RESET_DEVICE]);
        cfga_msg(msgp, sata_help[HELP_RESET_ALL]);
        cfga_msg(msgp, sata_help[HELP_PORT_ACTIVATE]);
        cfga_msg(msgp, sata_help[HELP_PORT_DEACTIVATE]);
        cfga_msg(msgp, sata_help[HELP_PORT_SELF_TEST]);
        cfga_msg(msgp, sata_help[HELP_CNTRL_SELF_TEST]);

        return (CFGA_OK);
}


/*
 * Ensure the ap_id passed is in the correct (physical ap_id) form:
 *     path/device:xx[.xx]
 * where xx is a one or two-digit number.
 *
 * Note the library always calls the plugin with a physical ap_id.
 */
static int
verify_valid_apid(const char *ap_id)
{
        char    *l_ap_id;

        if (ap_id == NULL)
                return (-1);

        l_ap_id = strrchr(ap_id, (int)*MINOR_SEP);
        l_ap_id++;

        if (strspn(l_ap_id, "0123456789.") != strlen(l_ap_id)) {
                /* Bad characters in the ap_id */
                return (-1);
        }

        if (strstr(l_ap_id, "..") != NULL) {
                /* ap_id has 1..2 or more than 2 dots */
                return (-1);
        }

        return (0);
}



/*
 * Verify the params passed in are valid.
 */
static cfga_sata_ret_t
verify_params(
    const char *ap_id,
    const char *options,
    char **errstring)
{
        char *pdyn, *lap_id;
        int rv;

        if (errstring != NULL) {
                *errstring = NULL;
        }

        if (options != NULL) {
                return (CFGA_SATA_OPTIONS);
        }

        /* Strip dynamic AP name component if it is present. */
        lap_id = strdup(ap_id);
        if (lap_id == NULL) {
                return (CFGA_SATA_ALLOC_FAIL);
        }
        if ((pdyn = GET_DYN(lap_id)) != NULL) {
                *pdyn = '\0';
        }

        if (verify_valid_apid(lap_id) != 0) {
                rv = CFGA_SATA_AP;
        } else {
                rv = CFGA_SATA_OK;
        }
        free(lap_id);

        return (rv);
}

/*
 * Takes a validated ap_id and extracts the port number.
 * Port multiplier is supported now.
 */
static cfga_sata_ret_t
get_port_num(const char *ap_id, uint32_t *port)
{
        uint32_t        cport, pmport = 0, qual = 0;
        char            *cport_str, *pmport_str;

        /* Get the cport number */
        cport_str = strrchr(ap_id, (int)*MINOR_SEP) + strlen(MINOR_SEP);

        errno = 0;
        cport = strtol(cport_str, NULL, 10);
        if ((cport & ~SATA_CFGA_CPORT_MASK) != 0 || errno != 0) {
                return (CFGA_SATA_PORT);
        }

        /* Get pmport number if there is a PORT_SEPARATOR */
        errno = 0;
        if ((pmport_str = strrchr(ap_id, (int)*PORT_SEPARATOR)) != 0) {
                pmport_str += strlen(PORT_SEPARATOR);
                pmport = strtol(pmport_str, NULL, 10);
                qual = SATA_CFGA_PMPORT_QUAL;
                if ((pmport & ~SATA_CFGA_PMPORT_MASK) != 0 || errno != 0) {
                        return (CFGA_SATA_PORT);
                }
        }

        *port = cport | (pmport << SATA_CFGA_PMPORT_SHIFT) | qual;
        return (CFGA_SATA_OK);
}

/*
 * Pair of routines to set up for/clean up after a devctl_ap_* lib call.
 */
static void
cleanup_after_devctl_cmd(devctl_hdl_t devctl_hdl, nvlist_t *user_nvlist)
{
        if (user_nvlist != NULL) {
                nvlist_free(user_nvlist);
        }
        if (devctl_hdl != NULL) {
                devctl_release(devctl_hdl);
        }
}

static cfga_sata_ret_t
setup_for_devctl_cmd(
    const char *ap_id,
    devctl_hdl_t *devctl_hdl,
    nvlist_t **user_nvlistp,
    uint_t oflag)
{

        uint_t  port;
        cfga_sata_ret_t rv = CFGA_SATA_OK;
        char *lap_id, *pdyn;

        lap_id = strdup(ap_id);
        if (lap_id == NULL)
                return (CFGA_SATA_ALLOC_FAIL);
        if ((pdyn = GET_DYN(lap_id)) != NULL) {
                *pdyn = '\0';
        }

        /* Get a devctl handle to pass to the devctl_ap_XXX functions */
        if ((*devctl_hdl = devctl_ap_acquire((char *)lap_id, oflag)) == NULL) {
                (void) fprintf(stderr, "[libcfgadm:sata] "
                    "setup_for_devctl_cmd: devctl_ap_acquire failed: %s\n",
                    strerror(errno));
                rv = CFGA_SATA_DEVCTL;
                goto bailout;
        }

        /* Set up nvlist to pass the port number down to the driver */
        if (nvlist_alloc(user_nvlistp, NV_UNIQUE_NAME_TYPE, 0) != 0) {
                *user_nvlistp = NULL;
                rv = CFGA_SATA_NVLIST;
                (void) printf("nvlist_alloc failed\n");
                goto bailout;
        }

        /*
         * Get port id, for Port Multiplier port, things could be a little bit
         * complicated because of "port.port" format in ap_id, thus for
         * port multiplier port, port number should be coded as 32bit int
         * with the sig 16 bit as sata channel number, least 16 bit as
         * the port number of sata port multiplier port.
         */
        if ((rv = get_port_num(lap_id, &port)) != CFGA_SATA_OK) {
                (void) printf(
                    "setup_for_devctl_cmd: get_port_num, errno: %d\n",
                    errno);
                goto bailout;
        }

        /* Creates an int32_t entry */
        if (nvlist_add_int32(*user_nvlistp, PORT, port) == -1) {
                (void) printf("nvlist_add_int32 failed\n");
                rv = CFGA_SATA_NVLIST;
                goto bailout;
        }

        free(lap_id);
        return (rv);

bailout:
        free(lap_id);
        (void) cleanup_after_devctl_cmd(*devctl_hdl, *user_nvlistp);

        return (rv);
}


static cfga_sata_ret_t
port_state(devctl_hdl_t hdl, nvlist_t *list,
    ap_rstate_t *rstate, ap_ostate_t *ostate)
{
        devctl_ap_state_t       devctl_ap_state;

        if (devctl_ap_getstate(hdl, list, &devctl_ap_state) == -1) {
                (void) printf("devctl_ap_getstate failed, errno: %d\n", errno);
                return (CFGA_SATA_IOCTL);
        }
        *rstate = devctl_ap_state.ap_rstate;
        *ostate =  devctl_ap_state.ap_ostate;
        return (CFGA_SATA_OK);
}


/*
 * Given a subcommand to the DEVCTL_AP_CONTROL ioctl, rquest the size of
 * the data to be returned, allocate a buffer, then get the data.
 * Returns *descrp (which must be freed) and size.
 *
 * Note SATA_DESCR_TYPE_STRING returns an ASCII NULL-terminated string,
 * not a string descr.
 */
cfga_sata_ret_t
do_control_ioctl(const char *ap_id, sata_cfga_apctl_t subcommand, uint_t arg,
    void **descrp, size_t *sizep)
{
        int                     fd = -1;
        uint_t                  port;
        uint32_t                local_size;
        cfga_sata_ret_t         rv = CFGA_SATA_OK;
        struct sata_ioctl_data  ioctl_data;

        assert(descrp != NULL);
        *descrp = NULL;
        assert(sizep != NULL);

        if ((rv = get_port_num(ap_id, &port)) != CFGA_SATA_OK) {
                goto bailout;
        }

        if ((fd = open(ap_id, O_RDONLY)) == -1) {
                (void) printf("do_control_ioctl: open failed: errno:%d\n",
                    errno);
                rv = CFGA_SATA_OPEN;
                if (errno == EBUSY) {
                        rv = CFGA_SATA_BUSY;
                }
                goto bailout;
        }

        ioctl_data.cmd = subcommand;
        ioctl_data.port = port;
        ioctl_data.misc_arg = (uint_t)arg;

        /*
         * Find out how large a buf we need to get the data.
         * Note the ioctls only accept/return a 32-bit int for a get_size
         * to avoid 32/64 and BE/LE issues.
         */
        if ((subcommand == SATA_CFGA_GET_AP_TYPE) ||
            (subcommand == SATA_CFGA_GET_DEVICE_PATH) ||
            (subcommand == SATA_CFGA_GET_MODEL_INFO) ||
            (subcommand == SATA_CFGA_GET_REVFIRMWARE_INFO) ||
            (subcommand == SATA_CFGA_GET_SERIALNUMBER_INFO)) {
                ioctl_data.get_size = B_TRUE;
                ioctl_data.buf = (caddr_t)&local_size;
                ioctl_data.bufsiz = sizeof (local_size);

                if (ioctl(fd, DEVCTL_AP_CONTROL, &ioctl_data) != 0) {
                        perror("ioctl failed (size)");
                        rv = CFGA_SATA_IOCTL;
                        goto bailout;
                }
                *sizep = local_size;

                if (local_size == 0) {
                        (void) printf("zero length data\n");
                        rv = CFGA_SATA_ZEROLEN;
                        goto bailout;
                }
                if ((*descrp = malloc(*sizep)) == NULL) {
                        (void) printf("do_control_ioctl: malloc failed\n");
                        rv = CFGA_SATA_ALLOC_FAIL;
                        goto bailout;
                }
        } else {
                *sizep = 0;
        }
        ioctl_data.get_size = B_FALSE;
        ioctl_data.buf = *descrp;
        ioctl_data.bufsiz = *sizep;

        /* Execute IOCTL */

        if (ioctl(fd, DEVCTL_AP_CONTROL, &ioctl_data) != 0) {
                rv = CFGA_SATA_IOCTL;
                goto bailout;
        }

        (void) close(fd);

        return (rv);

bailout:
        if (fd != -1) {
                (void) close(fd);
        }
        if (*descrp != NULL) {
                free(*descrp);
                *descrp = NULL;
        }

        if (rv == CFGA_SATA_IOCTL && errno == EBUSY) {
                rv = CFGA_SATA_BUSY;
        }

        return (rv);
}


static int
sata_confirm(struct cfga_confirm *confp, char *msg)
{
        int rval;

        if (confp == NULL || confp->confirm == NULL) {
                return (0);
        }
        rval = (*confp->confirm)(confp->appdata_ptr, msg);

        return (rval);
}


static char *
sata_get_devicepath(const char *ap_id)
{
        char            *devpath = NULL;
        size_t          size;
        cfga_sata_ret_t rv;

        rv = do_control_ioctl(ap_id, SATA_CFGA_GET_DEVICE_PATH, 0,
            (void **)&devpath, &size);

        if (rv == CFGA_SATA_OK) {
                return (devpath);
        } else {
                return ((char *)NULL);
        }

}