OpenSolaris_b135/lib/cfgadm_plugins/scsi/common/cfga_utils.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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.
 */

#include "cfga_scsi.h"
#include <libgen.h>
#include <limits.h>

/*
 * This file contains helper routines for the SCSI plugin
 */

#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN	"SYS_TEST"
#endif

typedef struct strlist {
	const char *str;
	struct strlist *next;
} strlist_t;

typedef	struct {
	scfga_ret_t scsi_err;
	cfga_err_t  cfga_err;
} errcvt_t;

typedef struct {
	scfga_cmd_t cmd;
	int type;
	int (*fcn)(const devctl_hdl_t);
} set_state_cmd_t;

typedef struct {
	scfga_cmd_t cmd;
	int type;
	int (*state_fcn)(const devctl_hdl_t, uint_t *);
} get_state_cmd_t;

/* Function prototypes */
static char *pathdup(const char *path, int *l_errnop);
static void msg_common(char **err_msgpp, int append_newline, int l_errno,
    va_list ap);

/*
 * The string table contains most of the strings used by the scsi cfgadm plugin.
 * All strings which are to be internationalized must be in this table.
 * Some strings which are not internationalized are also included here.
 * Arguments to messages are NOT internationalized.
 */
msgcvt_t str_tbl[] = {

/*
 * The first element (ERR_UNKNOWN) MUST always be present in the array.
 */
#define	UNKNOWN_ERR_IDX		0	/* Keep the index in sync */


/* msg_code	num_args, I18N	msg_string				*/

/* ERRORS */
{ERR_UNKNOWN,		0, 1,	"unknown error"},
{ERR_OP_FAILED,		0, 1,	"operation failed"},
{ERR_CMD_INVAL,		0, 1,	"invalid command"},
{ERR_NOT_BUSAPID,	0, 1,	"not a SCSI bus apid"},
{ERR_APID_INVAL,	0, 1,	"invalid SCSI ap_id"},
{ERR_NOT_BUSOP,		0, 1,	"operation not supported for SCSI bus"},
{ERR_NOT_DEVOP,		0, 1,	"operation not supported for SCSI device"},
{ERR_UNAVAILABLE,	0, 1,	"unavailable"},
{ERR_CTRLR_CRIT,	0, 1,	"critical partition controlled by SCSI HBA"},
{ERR_BUS_GETSTATE,	0, 1,	"failed to get state for SCSI bus"},
{ERR_BUS_NOTCONNECTED,	0, 1,	"SCSI bus not connected"},
{ERR_BUS_CONNECTED,	0, 1,	"SCSI bus not disconnected"},
{ERR_BUS_QUIESCE,	0, 1,	"SCSI bus quiesce failed"},
{ERR_BUS_UNQUIESCE,	0, 1,	"SCSI bus unquiesce failed"},
{ERR_BUS_CONFIGURE,	0, 1,	"failed to configure devices on SCSI bus"},
{ERR_BUS_UNCONFIGURE,	0, 1,	"failed to unconfigure SCSI bus"},
{ERR_DEV_CONFIGURE,	0, 1,	"failed to configure SCSI device"},
{ERR_DEV_RECONFIGURE,	1, 1,	"failed to reconfigure device: "},
{ERR_DEV_UNCONFIGURE,	0, 1,	"failed to unconfigure SCSI device"},
{ERR_DEV_REMOVE,	0, 1,	"remove operation failed"},
{ERR_DEV_REPLACE,	0, 1,	"replace operation failed"},
{ERR_DEV_INSERT,	0, 1,	"insert operation failed"},
{ERR_DEV_GETSTATE,	0, 1,	"failed to get state for SCSI device"},
{ERR_RESET,		0, 1,	"reset failed"},
{ERR_LIST,		0, 1,	"list operation failed"},
{ERR_MAYBE_BUSY,	0, 1,	"device may be busy"},
{ERR_BUS_DEV_MISMATCH,	0, 1,	"mismatched SCSI bus and device"},
{ERR_VAR_RUN,		0, 1,	"/var/run is not mounted"},
{ERR_FORK,		0, 1,	"failed to fork cleanup handler"},

/* Errors with arguments */
{ERRARG_OPT_INVAL,	1, 1,	"invalid option: "},
{ERRARG_HWCMD_INVAL,	1, 1,	"invalid command: "},
{ERRARG_DEVINFO,	1, 1,	"libdevinfo failed on path: "},
{ERRARG_OPEN,		1, 1,	"open failed: "},
{ERRARG_LOCK,		1, 1,	"lock failed: "},
{ERRARG_QUIESCE_LOCK,	1, 1,	"cannot acquire quiesce lock: "},

/* RCM Errors */
{ERR_RCM_HANDLE,	0, 1,	"cannot get RCM handle"},
{ERRARG_RCM_SUSPEND,	0, 1,	"failed to suspend: "},
{ERRARG_RCM_RESUME,	0, 1,	"failed to resume: "},
{ERRARG_RCM_OFFLINE,	0, 1,	"failed to offline: "},
{ERRARG_RCM_CLIENT_OFFLINE,	0, 1,	"failed to offline a client device: "},
{ERRARG_RCM_ONLINE,	0, 1,	"failed to online: "},
{ERRARG_RCM_REMOVE,	0, 1,	"failed to remove: "},

/* Commands */
{CMD_INSERT_DEV,	0, 0,	"insert_device"},
{CMD_REMOVE_DEV,	0, 0,	"remove_device"},
{CMD_LED_DEV,		0, 0,	"led"},
{CMD_LOCATOR_DEV,	0, 0,	"locator"},
{CMD_REPLACE_DEV,	0, 0,	"replace_device"},
{CMD_RESET_DEV,		0, 0,	"reset_device"},
{CMD_RESET_BUS,		0, 0,	"reset_bus"},
{CMD_RESET_ALL,		0, 0,	"reset_all"},

/* help messages */
{MSG_HELP_HDR,		0, 1,	"\nSCSI specific commands and options:\n"},
{MSG_HELP_USAGE,	0, 0,	"\t-x insert_device ap_id [ap_id... ]\n"
				"\t-x remove_device ap_id [ap_id... ]\n"
				"\t-x replace_device ap_id [ap_id... ]\n"
				"\t-x locator[=on|off] ap_id [ap_id... ]\n"
				"\t-x led[=LED,mode=on|off|blink] "
				    "ap_id [ap_id... ]\n"
				"\t-x reset_device ap_id [ap_id... ]\n"
				"\t-x reset_bus ap_id [ap_id... ]\n"
				"\t-x reset_all ap_id [ap_id... ]\n"},

/* hotplug messages */
{MSG_INSDEV,		1, 1,	"Adding device to SCSI HBA: "},
{MSG_RMDEV,		1, 1,	"Removing SCSI device: "},
{MSG_REPLDEV,		1, 1,	"Replacing SCSI device: "},
{MSG_WAIT_LOCK,		0, 1,	"Waiting for quiesce lock... "},

/* Hotplugging confirmation prompts */
{CONF_QUIESCE_1,	1, 1,
	"This operation will suspend activity on SCSI bus: "},

{CONF_QUIESCE_2,	0, 1,	"\nContinue"},

{CONF_UNQUIESCE,	0, 1,
	"SCSI bus quiesced successfully.\n"
	"It is now safe to proceed with hotplug operation."
	"\nEnter y if operation is complete or n to abort"},

{CONF_NO_QUIESCE,	0, 1,
	"Proceed with hotplug operation."
	"\nEnter y if operation is complete or n to abort"},

/* Misc. */
{WARN_DISCONNECT,	0, 1,
	"WARNING: Disconnecting critical partitions may cause system hang."
	"\nContinue"},

/* LED messages */
{MSG_LED_HDR,		0, 1,	"Disk                    Led"},
{MSG_MISSING_LED_NAME,	0, 1,	"Missing LED name"},
{MSG_MISSING_LED_MODE,	0, 1,	"Missing LED mode"}
};

char *
led_strs[] = {
	"fault",
	"power",
	"attn",
	"active",
	"locator",
	NULL
};

char *
led_mode_strs[] = {
	"off",
	"on",
	"blink",
	"faulted",
	"unknown",
	NULL
};




#define	N_STRS	(sizeof (str_tbl) / sizeof (str_tbl[0]))

#define	GET_MSG_NARGS(i)	(str_tbl[msg_idx(i)].nargs)
#define	GET_MSG_INTL(i)		(str_tbl[msg_idx(i)].intl)

static errcvt_t err_cvt_tbl[] = {
	{ SCFGA_OK,		CFGA_OK			},
	{ SCFGA_LIB_ERR,	CFGA_LIB_ERROR		},
	{ SCFGA_APID_NOEXIST,	CFGA_APID_NOEXIST	},
	{ SCFGA_NACK,		CFGA_NACK		},
	{ SCFGA_BUSY,		CFGA_BUSY		},
	{ SCFGA_SYSTEM_BUSY,	CFGA_SYSTEM_BUSY	},
	{ SCFGA_OPNOTSUPP,	CFGA_OPNOTSUPP		},
	{ SCFGA_PRIV,		CFGA_PRIV		},
	{ SCFGA_UNKNOWN_ERR,	CFGA_ERROR		},
	{ SCFGA_ERR,		CFGA_ERROR		}
};

#define	N_ERR_CVT_TBL	(sizeof (err_cvt_tbl)/sizeof (err_cvt_tbl[0]))

#define	DEV_OP	0
#define	BUS_OP	1
static set_state_cmd_t set_state_cmds[] = {

{ SCFGA_BUS_QUIESCE,		BUS_OP,		devctl_bus_quiesce	},
{ SCFGA_BUS_UNQUIESCE,		BUS_OP,		devctl_bus_unquiesce	},
{ SCFGA_BUS_CONFIGURE,		BUS_OP,		devctl_bus_configure	},
{ SCFGA_BUS_UNCONFIGURE, 	BUS_OP,		devctl_bus_unconfigure	},
{ SCFGA_RESET_BUS,		BUS_OP,		devctl_bus_reset	},
{ SCFGA_RESET_ALL, 		BUS_OP,		devctl_bus_resetall	},
{ SCFGA_DEV_CONFIGURE,		DEV_OP,		devctl_device_online	},
{ SCFGA_DEV_UNCONFIGURE,	DEV_OP,		devctl_device_offline	},
{ SCFGA_DEV_REMOVE,		DEV_OP,		devctl_device_remove	},
{ SCFGA_RESET_DEV,		DEV_OP,		devctl_device_reset	}

};

#define	N_SET_STATE_CMDS (sizeof (set_state_cmds)/sizeof (set_state_cmds[0]))

static get_state_cmd_t get_state_cmds[] = {
{ SCFGA_BUS_GETSTATE,		BUS_OP,		devctl_bus_getstate	},
{ SCFGA_DEV_GETSTATE,		DEV_OP,		devctl_device_getstate	}
};

#define	N_GET_STATE_CMDS (sizeof (get_state_cmds)/sizeof (get_state_cmds[0]))

/*
 * SCSI hardware specific commands
 */
static hw_cmd_t hw_cmds[] = {
	/* Command string	Command ID		Function	*/

	{ CMD_INSERT_DEV,	SCFGA_INSERT_DEV,	dev_insert	},
	{ CMD_REMOVE_DEV,	SCFGA_REMOVE_DEV,	dev_remove	},
	{ CMD_REPLACE_DEV,	SCFGA_REPLACE_DEV,	dev_replace	},
	{ CMD_LED_DEV,		SCFGA_LED_DEV,		dev_led		},
	{ CMD_LOCATOR_DEV,	SCFGA_LOCATOR_DEV,	dev_led		},
	{ CMD_RESET_DEV,	SCFGA_RESET_DEV,	reset_common	},
	{ CMD_RESET_BUS,	SCFGA_RESET_BUS,	reset_common	},
	{ CMD_RESET_ALL,	SCFGA_RESET_ALL,	reset_common	},
};
#define	N_HW_CMDS (sizeof (hw_cmds) / sizeof (hw_cmds[0]))


cfga_err_t
err_cvt(scfga_ret_t s_err)
{
	int i;

	for (i = 0; i < N_ERR_CVT_TBL; i++) {
		if (err_cvt_tbl[i].scsi_err == s_err) {
			return (err_cvt_tbl[i].cfga_err);
		}
	}

	return (CFGA_ERROR);
}

/*
 * Removes duplicate slashes from a pathname and any trailing slashes.
 * Returns "/" if input is "/"
 */
static char *
pathdup(const char *path, int *l_errnop)
{
	int prev_was_slash = 0;
	char c, *dp = NULL, *dup = NULL;
	const char *sp = NULL;

	*l_errnop = 0;

	if (path == NULL) {
		return (NULL);
	}

	if ((dup = calloc(1, strlen(path) + 1)) == NULL) {
		*l_errnop = errno;
		return (NULL);
	}

	prev_was_slash = 0;
	for (sp = path, dp = dup; (c = *sp) != '\0'; sp++) {
		if (!prev_was_slash || c != '/') {
			*dp++ = c;
		}
		if (c == '/') {
			prev_was_slash = 1;
		} else {
			prev_was_slash = 0;
		}
	}

	/* Remove trailing slash except if it is the first char */
	if (prev_was_slash && dp != dup && dp - 1 != dup) {
		*(--dp) = '\0';
	} else {
		*dp = '\0';
	}

	return (dup);
}


scfga_ret_t
apidt_create(const char *ap_id, apid_t *apidp, char **errstring)
{
	char *hba_phys = NULL, *dyn = NULL;
	char *dyncomp = NULL, *path = NULL;
	int l_errno = 0;
	size_t len = 0;
	scfga_ret_t ret;

	if ((hba_phys = pathdup(ap_id, &l_errno)) == NULL) {
		cfga_err(errstring, l_errno, ERR_OP_FAILED, 0);
		return (SCFGA_LIB_ERR);
	}

	/* Extract the base(hba) and dynamic(device) component if any */
	dyncomp = NULL;
	if ((dyn = GET_DYN(hba_phys)) != NULL) {
		len = strlen(DYN_TO_DYNCOMP(dyn)) + 1;
		dyncomp = calloc(1, len);
		if (dyncomp == NULL) {
			cfga_err(errstring, errno, ERR_OP_FAILED, 0);
			ret = SCFGA_LIB_ERR;
			goto err;
		}
		(void) strcpy(dyncomp, DYN_TO_DYNCOMP(dyn));

		/* Remove the dynamic component from the base */
		*dyn = '\0';
	} else {
		apidp->dyntype = NODYNCOMP;
	}

	/* get dyn comp type */
	if (dyncomp != NULL) {
		if (strstr(dyncomp, PATH_APID_DYN_SEP) != NULL) {
			apidp->dyntype = PATH_APID;
		} else {
			apidp->dyntype = DEV_APID;
		}
	}

	/* Create the path */
	if ((ret = apid_to_path(hba_phys, dyncomp, &path,
	    &l_errno)) != SCFGA_OK) {
		cfga_err(errstring, l_errno, ERR_OP_FAILED, 0);
		goto err;
	}

	assert(path != NULL);
	assert(hba_phys != NULL);

	apidp->hba_phys = hba_phys;
	apidp->dyncomp = dyncomp;
	apidp->path = path;
	apidp->flags = 0;

	return (SCFGA_OK);

err:
	S_FREE(hba_phys);
	S_FREE(dyncomp);
	S_FREE(path);
	return (ret);
}

void
apidt_free(apid_t *apidp)
{
	if (apidp == NULL)
		return;

	S_FREE(apidp->hba_phys);
	S_FREE(apidp->dyncomp);
	S_FREE(apidp->path);
}

scfga_ret_t
walk_tree(
	const char	*physpath,
	void		*arg,
	uint_t		init_flags,
	walkarg_t	*up,
	scfga_cmd_t	cmd,
	int		*l_errnop)
{
	int rv;
	di_node_t root, walk_root;
	char *root_path, *cp = NULL, *init_path;
	size_t len;
	scfga_ret_t ret;

	*l_errnop = 0;

	if ((root_path = strdup(physpath)) == NULL) {
		*l_errnop = errno;
		return (SCFGA_LIB_ERR);
	}

	/* Fix up path for di_init() */
	len = strlen(DEVICES_DIR);
	if (strncmp(root_path, DEVICES_DIR SLASH,
	    len + strlen(SLASH)) == 0) {
		cp = root_path + len;
		(void) memmove(root_path, cp, strlen(cp) + 1);
	} else if (*root_path != '/') {
		*l_errnop = 0;
		ret = SCFGA_ERR;
		goto out;
	}

	/* Remove dynamic component if any */
	if ((cp = GET_DYN(root_path)) != NULL) {
		*cp = '\0';
	}

	/* Remove minor name if any */
	if ((cp = strrchr(root_path, ':')) != NULL) {
		*cp = '\0';
	}

	/*
	 * Cached snapshots are always rooted at "/"
	 */
	init_path = root_path;
	if ((init_flags & DINFOCACHE) == DINFOCACHE) {
		init_path = "/";
	}

	/* Get a snapshot */
	if ((root = di_init(init_path, init_flags)) == DI_NODE_NIL) {
		*l_errnop = errno;
		ret = SCFGA_LIB_ERR;
		goto out;
	}

	/*
	 * Lookup the subtree of interest
	 */
	walk_root = root;
	if ((init_flags & DINFOCACHE) == DINFOCACHE) {
		walk_root = di_lookup_node(root, root_path);
	}

	if (walk_root == DI_NODE_NIL) {
		*l_errnop = errno;
		di_fini(root);
		ret = SCFGA_LIB_ERR;
		goto out;
	}

	/* Walk the tree */
	errno = 0;
	if (cmd == SCFGA_WALK_NODE) {
		rv = di_walk_node(walk_root, up->node_args.flags, arg,
		    up->node_args.fcn);
	} else if (cmd == SCFGA_WALK_PATH) {
		rv = stat_path_info(walk_root, arg, l_errnop);
	} else {
		assert(cmd == SCFGA_WALK_MINOR);
		rv = di_walk_minor(walk_root, up->minor_args.nodetype, 0, arg,
		    up->minor_args.fcn);
	}

	if (rv != 0) {
		*l_errnop = errno;
		ret = SCFGA_LIB_ERR;
	} else {
		*l_errnop = 0;
		ret = SCFGA_OK;
	}

	di_fini(root);

	/*FALLTHRU*/
out:
	S_FREE(root_path);
	return (ret);
}

scfga_ret_t
invoke_cmd(
	const char *func,
	apid_t *apidtp,
	prompt_t *prp,
	cfga_flags_t flags,
	char **errstring)
{
	int i;
	int len;


	/*
	 * Determine if the func has an equal sign; only compare up to
	 * the equals
	 */
	for (len = 0; func[len] != 0 && func[len] != '='; len++) {
	};

	for (i = 0; i < N_HW_CMDS; i++) {
		const char *s = GET_MSG_STR(hw_cmds[i].str_id);
		if (strncmp(func, s, len) == 0 && s[len] == 0) {
			return (hw_cmds[i].fcn(func, hw_cmds[i].cmd, apidtp,
			    prp, flags, errstring));
		}
	}

	cfga_err(errstring, 0, ERRARG_HWCMD_INVAL, func, 0);
	return (SCFGA_ERR);
}

int
msg_idx(msgid_t msgid)
{
	int idx = 0;

	/* The string table index and the error id may or may not be same */
	if (msgid >= 0 && msgid <= N_STRS - 1 &&
	    str_tbl[msgid].msgid == msgid) {
		idx = msgid;
	} else {
		for (idx = 0; idx < N_STRS; idx++) {
			if (str_tbl[idx].msgid == msgid)
				break;
		}
		if (idx >= N_STRS) {
			idx =  UNKNOWN_ERR_IDX;
		}
	}

	return (idx);
}

/*
 * cfga_err() accepts a variable number of message IDs and constructs
 * a corresponding error string which is returned via the errstring argument.
 * cfga_err() calls dgettext() to internationalize proper messages.
 * May be called with a NULL argument.
 */
void
cfga_err(char **errstring, int l_errno, ...)
{
	va_list ap;
	int append_newline = 0;

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

	/*
	 * Don't append a newline, the application (for example cfgadm)
	 * should do that.
	 */
	append_newline = 0;

	va_start(ap, l_errno);
	msg_common(errstring, append_newline, l_errno, ap);
	va_end(ap);
}

/*
 * This routine accepts a variable number of message IDs and constructs
 * a corresponding message string which is printed via the message print
 * routine argument.
 */
void
cfga_msg(struct cfga_msg *msgp, ...)
{
	char *p = NULL;
	int append_newline = 0, l_errno = 0;
	va_list ap;

	if (msgp == NULL || msgp->message_routine == NULL) {
		return;
	}

	/* Append a newline after message */
	append_newline = 1;
	l_errno = 0;

	va_start(ap, msgp);
	msg_common(&p, append_newline, l_errno, ap);
	va_end(ap);

	(void) (*msgp->message_routine)(msgp->appdata_ptr, p);

	S_FREE(p);
}


/*
 * This routine prints the value of an led for a disk.
 */
void
cfga_led_msg(struct cfga_msg *msgp, apid_t *apidp, led_strid_t led,
    led_modeid_t mode)
{
	char led_msg[MAX_INPUT];	/* 512 bytes */

	if ((msgp == NULL) || (msgp->message_routine == NULL)) {
		return;
	}
	if ((apidp == NULL) || (apidp->dyncomp == NULL)) {
		return;
	}
	(void) snprintf(led_msg, sizeof (led_msg), "%-23s\t%s=%s\n",
	    basename(apidp->dyncomp),
	    dgettext(TEXT_DOMAIN, led_strs[led]),
	    dgettext(TEXT_DOMAIN, led_mode_strs[mode]));
	(void) (*msgp->message_routine)(msgp->appdata_ptr, led_msg);
}

/*
 * Get internationalized string corresponding to message id
 * Caller must free the memory allocated.
 */
char *
cfga_str(int append_newline, ...)
{
	char *p = NULL;
	int l_errno = 0;
	va_list ap;

	va_start(ap, append_newline);
	msg_common(&p, append_newline, l_errno, ap);
	va_end(ap);

	return (p);
}

static void
msg_common(char **msgpp, int append_newline, int l_errno, va_list ap)
{
	int a = 0;
	size_t len = 0;
	int i = 0, n = 0;
	char *s = NULL, *t = NULL;
	strlist_t dummy;
	strlist_t *savep = NULL, *sp = NULL, *tailp = NULL;

	if (*msgpp != NULL) {
		return;
	}

	dummy.next = NULL;
	tailp = &dummy;
	for (len = 0; (a = va_arg(ap, int)) != 0; ) {
		n = GET_MSG_NARGS(a); /* 0 implies no additional args */
		for (i = 0; i <= n; i++) {
			sp = calloc(1, sizeof (*sp));
			if (sp == NULL) {
				goto out;
			}
			if (i == 0 && GET_MSG_INTL(a)) {
				sp->str = dgettext(TEXT_DOMAIN, GET_MSG_STR(a));
			} else if (i == 0) {
				sp->str = GET_MSG_STR(a);
			} else {
				sp->str = va_arg(ap, char *);
			}
			len += (strlen(sp->str));
			sp->next = NULL;
			tailp->next = sp;
			tailp = sp;
		}
	}

	len += 1;	/* terminating NULL */

	s = t = NULL;
	if (l_errno) {
		s = dgettext(TEXT_DOMAIN, ": ");
		t = S_STR(strerror(l_errno));
		if (s != NULL && t != NULL) {
			len += strlen(s) + strlen(t);
		}
	}

	if (append_newline) {
		len++;
	}

	if ((*msgpp = calloc(1, len)) == NULL) {
		goto out;
	}

	**msgpp = '\0';
	for (sp = dummy.next; sp != NULL; sp = sp->next) {
		(void) strcat(*msgpp, sp->str);
	}

	if (s != NULL && t != NULL) {
		(void) strcat(*msgpp, s);
		(void) strcat(*msgpp, t);
	}

	if (append_newline) {
		(void) strcat(*msgpp, dgettext(TEXT_DOMAIN, "\n"));
	}

	/* FALLTHROUGH */
out:
	sp = dummy.next;
	while (sp != NULL) {
		savep = sp->next;
		S_FREE(sp);
		sp = savep;
	}
}

/*
 * Check to see if the given pi_node is the last path to the client device.
 *
 * Return:
 *	0: if there is another path avialable.
 *	-1: if no other paths available.
 */
static int
check_available_path(
	di_node_t client_node,
	di_path_t pi_node)
{
	di_path_state_t pi_state;
	di_path_t   next_pi = DI_PATH_NIL;

	if (((pi_state = di_path_state(pi_node)) != DI_PATH_STATE_ONLINE) &&
	    (pi_state != DI_PATH_STATE_STANDBY)) {
		/* it is not last available path */
		return (0);
	}

	while (next_pi = di_path_client_next_path(client_node, next_pi)) {
		/* if anohter pi node is avaialble, return 0 */
		if ((next_pi != pi_node) &&
		    (((pi_state = di_path_state(next_pi)) ==
		    DI_PATH_STATE_ONLINE) ||
		    pi_state == DI_PATH_STATE_STANDBY)) {
			return (0);
		}
	}
	return (-1);
}

scfga_ret_t
path_apid_state_change(
	apid_t		*apidp,
	scfga_cmd_t	cmd,
	cfga_flags_t	flags,
	char		**errstring,
	int		*l_errnop,
	msgid_t		errid)
{
	di_node_t   root, walk_root, client_node;
	di_path_t   pi_node = DI_PATH_NIL;
	char	    *root_path, *cp, *client_path, devpath[MAXPATHLEN];
	int	    len, found = 0;
	scfga_ret_t ret;
	char *dev_list[2] = {NULL};

	*l_errnop = 0;

	/* Make sure apid is pathinfo associated apid. */
	if ((apidp->dyntype != PATH_APID) || (apidp->dyncomp == NULL)) {
		return (SCFGA_LIB_ERR);
	}

	if ((cmd != SCFGA_DEV_CONFIGURE) && (cmd != SCFGA_DEV_UNCONFIGURE)) {
		return (SCFGA_LIB_ERR);
	}

	if ((root_path = strdup(apidp->hba_phys)) == NULL) {
		*l_errnop = errno;
		return (SCFGA_LIB_ERR);
	}

	/* Fix up path for di_init() */
	len = strlen(DEVICES_DIR);
	if (strncmp(root_path, DEVICES_DIR SLASH,
	    len + strlen(SLASH)) == 0) {
		cp = root_path + len;
		(void) memmove(root_path, cp, strlen(cp) + 1);
	} else if (*root_path != '/') {
		*l_errnop = 0;
		S_FREE(root_path);
		return (SCFGA_ERR);
	}

	/* Remove dynamic component if any */
	if ((cp = GET_DYN(root_path)) != NULL) {
		*cp = '\0';
	}

	/* Remove minor name if any */
	if ((cp = strrchr(root_path, ':')) != NULL) {
		*cp = '\0';
	}

	/*
	 * Cached snapshots are always rooted at "/"
	 */

	/* Get a snapshot */
	if ((root = di_init("/", DINFOCACHE)) == DI_NODE_NIL) {
		*l_errnop = errno;
		S_FREE(root_path);
		return (SCFGA_ERR);
	}

	/*
	 * Lookup the subtree of interest
	 */
	walk_root = di_lookup_node(root, root_path);

	if (walk_root == DI_NODE_NIL) {
		*l_errnop = errno;
		di_fini(root);
		S_FREE(root_path);
		return (SCFGA_LIB_ERR);
	}


	if ((pi_node = di_path_next_client(walk_root, pi_node)) ==
	    DI_PATH_NIL) {
		/* the path apid not found */
		di_fini(root);
		S_FREE(root_path);
		return (SCFGA_APID_NOEXIST);
	}

	do {
		/* check the length first. */
		if (strlen(di_path_bus_addr(pi_node)) !=
		    strlen(apidp->dyncomp)) {
			continue;
		}

		/* compare bus addr. */
		if (strcmp(di_path_bus_addr(pi_node), apidp->dyncomp) == 0) {
			found = 1;
			break;
		}
		pi_node = di_path_next_client(root, pi_node);
	} while (pi_node != DI_PATH_NIL);

	if (!found) {
		di_fini(root);
		S_FREE(root_path);
		return (SCFGA_APID_NOEXIST);
	}

	/* Get client node path. */
	client_node = di_path_client_node(pi_node);
	if (client_node == DI_NODE_NIL) {
		di_fini(root);
		S_FREE(root_path);
		return (SCFGA_ERR);
	} else {
		client_path = di_devfs_path(client_node);
		if (client_path == NULL) {
			di_fini(root);
			S_FREE(root_path);
			return (SCFGA_ERR);
		}

		if ((apidp->flags & FLAG_DISABLE_RCM) == 0) {
			if (cmd == SCFGA_DEV_UNCONFIGURE) {
				if (check_available_path(client_node,
				    pi_node) != 0) {
					/*
					 * last path. check if unconfiguring
					 * is okay.
					 */
					(void) snprintf(devpath,
					    strlen(DEVICES_DIR) +
					    strlen(client_path) + 1, "%s%s",
					    DEVICES_DIR, client_path);
					dev_list[0] = devpath;
					flags |= FLAG_CLIENT_DEV;
					ret = scsi_rcm_offline(dev_list,
					    errstring, flags);
					if (ret != SCFGA_OK) {
						di_fini(root);
						di_devfs_path_free(client_path);
						S_FREE(root_path);
						return (ret);
					}
				}
			}
		}
	}

	ret = devctl_cmd(apidp->path, cmd, NULL, l_errnop);
	if (ret != SCFGA_OK) {
		cfga_err(errstring, *l_errnop, errid, 0);

		/*
		 * If an unconfigure fails, cancel the RCM offline.
		 * Discard any RCM failures so that the devctl
		 * failure will still be reported.
		 */
		if ((apidp->flags & FLAG_DISABLE_RCM) == 0) {
			if (cmd == SCFGA_DEV_UNCONFIGURE)
				(void) scsi_rcm_online(dev_list,
				    errstring, flags);
		}
	}

	di_devfs_path_free(client_path);
	di_fini(root);
	S_FREE(root_path);

	return (ret);
}


scfga_ret_t
devctl_cmd(
	const char	*physpath,
	scfga_cmd_t	cmd,
	uint_t		*statep,
	int		*l_errnop)
{
	int rv = -1, i, type;
	devctl_hdl_t hdl = NULL;
	char *cp = NULL, *path = NULL;
	int (*func)(const devctl_hdl_t);
	int (*state_func)(const devctl_hdl_t, uint_t *);

	*l_errnop = 0;

	if (statep != NULL) *statep = 0;

	func = NULL;
	state_func = NULL;
	type = 0;

	for (i = 0; i < N_GET_STATE_CMDS; i++) {
		if (get_state_cmds[i].cmd == cmd) {
			state_func = get_state_cmds[i].state_fcn;
			type = get_state_cmds[i].type;
			assert(statep != NULL);
			break;
		}
	}

	if (state_func == NULL) {
		for (i = 0; i < N_SET_STATE_CMDS; i++) {
			if (set_state_cmds[i].cmd == cmd) {
				func = set_state_cmds[i].fcn;
				type = set_state_cmds[i].type;
				assert(statep == NULL);
				break;
			}
		}
	}

	assert(type == BUS_OP || type == DEV_OP);

	if (func == NULL && state_func == NULL) {
		return (SCFGA_ERR);
	}

	/*
	 * Fix up path for calling devctl.
	 */
	if ((path = strdup(physpath)) == NULL) {
		*l_errnop = errno;
		return (SCFGA_LIB_ERR);
	}

	/* Remove dynamic component if any */
	if ((cp = GET_DYN(path)) != NULL) {
		*cp = '\0';
	}

	/* Remove minor name */
	if ((cp = strrchr(path, ':')) != NULL) {
		*cp = '\0';
	}

	errno = 0;

	if (type == BUS_OP) {
		hdl = devctl_bus_acquire(path, 0);
	} else {
		hdl = devctl_device_acquire(path, 0);
	}
	*l_errnop = errno;

	S_FREE(path);

	if (hdl == NULL) {
		return (SCFGA_ERR);
	}

	errno = 0;
	/* Only getstate functions require a second argument */
	if (func != NULL && statep == NULL) {
		rv = func(hdl);
		*l_errnop = errno;
	} else if (state_func != NULL && statep != NULL) {
		rv = state_func(hdl, statep);
		*l_errnop = errno;
	} else {
		rv = -1;
		*l_errnop = 0;
	}

	devctl_release(hdl);

	return ((rv == -1) ? SCFGA_ERR : SCFGA_OK);
}

/*
 * Is device in a known state ? (One of BUSY, ONLINE, OFFLINE)
 *	BUSY --> One or more device special files are open. Implies online
 *	ONLINE --> driver attached
 *	OFFLINE --> CF1 with offline flag set.
 *	UNKNOWN --> None of the above
 */
int
known_state(di_node_t node)
{
	uint_t state;

	state = di_state(node);

	/*
	 * CF1 without offline flag set is considered unknown state.
	 * We are in a known state if either CF2 (driver attached) or
	 * offline.
	 */
	if ((state & DI_DEVICE_OFFLINE) == DI_DEVICE_OFFLINE ||
	    (state & DI_DRIVER_DETACHED) != DI_DRIVER_DETACHED) {
		return (1);
	}

	return (0);
}

void
list_free(ldata_list_t **llpp)
{
	ldata_list_t *lp, *olp;

	lp = *llpp;
	while (lp != NULL) {
		olp = lp;
		lp = olp->next;
		S_FREE(olp);
	}

	*llpp = NULL;
}

/*
 * Obtain the devlink from a /devices path
 */
typedef struct walk_link {
	char *path;
	char len;
	char **linkpp;
} walk_link_t;

static int
get_link(di_devlink_t devlink, void *arg)
{
	walk_link_t *larg = (walk_link_t *)arg;

	/*
	 * When path is specified, it's the node path without minor
	 * name. Therefore, the ../.. prefixes needs to be stripped.
	 */
	if (larg->path) {
		char *content = (char *)di_devlink_content(devlink);
		char *start = strstr(content, "/devices/");

		/* line content must have minor node */
		if (start == NULL ||
		    strncmp(start, larg->path, larg->len) != 0 ||
		    start[larg->len] != ':')
			return (DI_WALK_CONTINUE);
	}

	*(larg->linkpp) = strdup(di_devlink_path(devlink));
	return (DI_WALK_TERMINATE);
}

scfga_ret_t
physpath_to_devlink(
	char *node_path,
	char **logpp,
	int *l_errnop,
	int match_minor)
{
	walk_link_t larg;
	di_devlink_handle_t hdl;
	char *minor_path;

	if ((hdl = di_devlink_init(NULL, 0)) == NULL) {
		*l_errnop = errno;
		return (SCFGA_LIB_ERR);
	}

	*logpp = NULL;
	larg.linkpp = logpp;
	if (match_minor) {
		minor_path = node_path + strlen(DEVICES_DIR);
		larg.path = NULL;
	} else {
		minor_path = NULL;
		larg.len = strlen(node_path);
		larg.path = node_path;
	}

	(void) di_devlink_walk(hdl, NULL, minor_path, DI_PRIMARY_LINK,
	    (void *)&larg, get_link);

	(void) di_devlink_fini(&hdl);

	if (*logpp == NULL)
		return (SCFGA_LIB_ERR);

	return (SCFGA_OK);
}

int
hba_dev_cmp(const char *hba, const char *devpath)
{
	char *cp = NULL;
	int rv;
	size_t hba_len, dev_len;
	char l_hba[MAXPATHLEN], l_dev[MAXPATHLEN];

	(void) snprintf(l_hba, sizeof (l_hba), "%s", hba);
	(void) snprintf(l_dev, sizeof (l_dev), "%s", devpath);

	/* Remove dynamic component if any */
	if ((cp = GET_DYN(l_hba)) != NULL) {
		*cp = '\0';
	}

	if ((cp = GET_DYN(l_dev)) != NULL) {
		*cp = '\0';
	}


	/* Remove minor names */
	if ((cp = strrchr(l_hba, ':')) != NULL) {
		*cp = '\0';
	}

	if ((cp = strrchr(l_dev, ':')) != NULL) {
		*cp = '\0';
	}

	hba_len = strlen(l_hba);
	dev_len = strlen(l_dev);

	/* Check if HBA path is component of device path */
	if (rv = strncmp(l_hba, l_dev, hba_len)) {
		return (rv);
	}

	/* devpath must have '/' and 1 char in addition to hba path */
	if (dev_len >= hba_len + 2 && l_dev[hba_len] == '/') {
		return (0);
	} else {
		return (-1);
	}
}

int
dev_cmp(const char *dev1, const char *dev2, int match_minor)
{
	char l_dev1[MAXPATHLEN], l_dev2[MAXPATHLEN];
	char *mn1, *mn2;
	int rv;

	(void) snprintf(l_dev1, sizeof (l_dev1), "%s", dev1);
	(void) snprintf(l_dev2, sizeof (l_dev2), "%s", dev2);

	if ((mn1 = GET_DYN(l_dev1)) != NULL) {
		*mn1 = '\0';
	}

	if ((mn2 = GET_DYN(l_dev2)) != NULL) {
		*mn2 = '\0';
	}

	/* Separate out the minor names */
	if ((mn1 = strrchr(l_dev1, ':')) != NULL) {
		*mn1++ = '\0';
	}

	if ((mn2 = strrchr(l_dev2, ':')) != NULL) {
		*mn2++ = '\0';
	}

	if ((rv = strcmp(l_dev1, l_dev2)) != 0 || !match_minor) {
		return (rv);
	}

	/*
	 * Compare minor names
	 */
	if (mn1 == NULL && mn2 == NULL) {
		return (0);
	} else if (mn1 == NULL) {
		return (-1);
	} else if (mn2 == NULL) {
		return (1);
	} else {
		return (strcmp(mn1, mn2));
	}
}