OpenSolaris_b135/lib/libdevinfo/devinfo_retire.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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <libdevinfo.h>
#include <sys/modctl.h>
#include <sys/stat.h>
#include <string.h>
#include <librcm.h>
#include <dlfcn.h>

#undef	NDEBUG
#include <assert.h>

typedef struct rio_path {
	char		rpt_path[PATH_MAX];
	struct rio_path	*rpt_next;
} rio_path_t;

typedef struct rcm_arg {
	char		*rcm_root;
	di_node_t	rcm_node;
	int		rcm_supp;
	rcm_handle_t	*rcm_handle;
	int		rcm_retcode;
	di_retire_t	*rcm_dp;
	rio_path_t	*rcm_cons_nodes;
	rio_path_t	*rcm_rsrc_minors;
	int		(*rcm_offline)();
	int		(*rcm_online)();
	int		(*rcm_remove)();
} rcm_arg_t;

typedef struct selector {
	char	*sel_name;
	int	(*sel_selector)(di_node_t node, rcm_arg_t *rp);
} di_selector_t;

static void rio_assert(di_retire_t *dp, const char *EXstr, int line,
    const char *file);

#define	LIBRCM_PATH	"/usr/lib/librcm.so"
#define	RIO_ASSERT(d, x)	\
		{if (!(x)) rio_assert(d, #x, __LINE__, __FILE__); }

static int disk_select(di_node_t node, rcm_arg_t *rp);
static int nexus_select(di_node_t node, rcm_arg_t *rp);

di_selector_t supported_devices[] = {
	{"disk",	disk_select},
	{"nexus",	nexus_select},
	{NULL, 		NULL}
};

void *
s_calloc(size_t nelem, size_t elsize, int fail)
{
	if (fail) {
		errno = ENOMEM;
		return (NULL);
	} else {
		return (calloc(nelem, elsize));
	}
}

static void
rio_assert(di_retire_t *dp, const char *EXstr, int line, const char *file)
{
	char	buf[PATH_MAX];

	if (dp->rt_abort == NULL)
		assert(0);

	(void) snprintf(buf, sizeof (buf),
	    "Assertion failed: %s, file %s, line %d\n",
	    EXstr, file, line);
	dp->rt_abort(dp->rt_hdl, buf);
}

/*ARGSUSED*/
static int
disk_minor(di_node_t node, di_minor_t minor, void *arg)
{
	rcm_arg_t *rp = (rcm_arg_t *)arg;
	di_retire_t *dp = rp->rcm_dp;

	if (di_minor_spectype(minor) == S_IFBLK) {
		rp->rcm_supp = 1;
		dp->rt_debug(dp->rt_hdl, "[INFO]: disk_minor: is disk minor. "
		    "IDed this node as disk\n");
		return (DI_WALK_TERMINATE);
	}

	dp->rt_debug(dp->rt_hdl, "[INFO]: disk_minor: Not a disk minor. "
	    "Continuing minor walk\n");
	return (DI_WALK_CONTINUE);
}

static int
disk_select(di_node_t node, rcm_arg_t *rp)
{
	rcm_arg_t rarg;
	di_retire_t	*dp = rp->rcm_dp;

	rarg.rcm_dp = dp;

	/*
	 * Check if this is a disk minor. If any one minor is DDI_NT_BLOCK
	 * we assume it is a disk
	 */
	rarg.rcm_supp = 0;
	if (di_walk_minor(node, DDI_NT_BLOCK, 0, &rarg, disk_minor) != 0) {
		dp->rt_debug(dp->rt_hdl, "[INFO]: disk_select: di_walk_minor "
		    "failed. Returning NOTSUP\n");
		return (0);
	}

	return (rarg.rcm_supp);
}

static int
nexus_select(di_node_t node, rcm_arg_t *rp)
{
	int select;
	char *path;

	di_retire_t *dp = rp->rcm_dp;

	path = di_devfs_path(node);
	if (path == NULL) {
		dp->rt_debug(dp->rt_hdl, "[INFO]: nexus_select: "
		    "di_devfs_path() is NULL. Returning NOTSUP\n");
		return (0);
	}

	/*
	 * Check if it is a nexus
	 */
	if (di_driver_ops(node) & DI_BUS_OPS) {
		dp->rt_debug(dp->rt_hdl, "[INFO]: nexus_select: is nexus %s\n",
		    path);
		select = 1;
	} else {
		dp->rt_debug(dp->rt_hdl, "[INFO]: nexus_select: not nexus %s\n",
		    path);
		select = 0;
	}

	di_devfs_path_free(path);

	return (select);
}

static int
node_select(di_node_t node, void *arg)
{
	rcm_arg_t *rp = (rcm_arg_t *)arg;
	di_retire_t *dp;
	int	sel;
	int	i;
	char	*path;
	uint_t	state;

	dp = rp->rcm_dp;

	/* skip pseudo nodes - we only retire real hardware */
	path = di_devfs_path(node);
	if (strncmp(path, "/pseudo/", strlen("/pseudo/")) == 0 ||
	    strcmp(path, "/pseudo") == 0) {
		dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: "
		    "pseudo device in subtree - returning NOTSUP: %s\n",
		    path);
		rp->rcm_supp = 0;
		di_devfs_path_free(path);
		return (DI_WALK_TERMINATE);
	}
	di_devfs_path_free(path);

	/*
	 * If a device is offline/detached/down it is
	 * retireable irrespective of the type of device,
	 * presumably the system is able to function without
	 * it.
	 */
	state = di_state(node);
	if ((state & DI_DRIVER_DETACHED) || (state & DI_DEVICE_OFFLINE) ||
	    (state & DI_BUS_DOWN)) {
		dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: device "
		    "is offline/detached. Assuming retire supported\n");
		return (DI_WALK_CONTINUE);
	}

	sel = 0;
	for (i = 0; supported_devices[i].sel_name != NULL; i++) {
		sel = supported_devices[i].sel_selector(node, rp);
		if (sel == 1) {
			dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: "
			    "found supported device: %s\n",
			    supported_devices[i].sel_name);
			break;
		}
	}

	if (sel != 1) {
		/*
		 * This node is not a supported device. Retire cannot proceed
		 */
		dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: found "
		    "unsupported device. Returning NOTSUP\n");
		rp->rcm_supp = 0;
		return (DI_WALK_TERMINATE);
	}

	/*
	 * This node is supported. Check other nodes in this subtree.
	 */
	dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: This node supported. "
	    "Checking other nodes in subtree: %s\n", rp->rcm_root);
	return (DI_WALK_CONTINUE);
}



/*
 * when in doubt assume that retire is not supported for this device.
 */
static int
retire_supported(rcm_arg_t *rp)
{
	di_retire_t	*dp;
	di_node_t rnode = rp->rcm_node;

	dp = rp->rcm_dp;

	/*
	 * We should not be here if devinfo snapshot is NULL.
	 */
	RIO_ASSERT(dp, rnode != DI_NODE_NIL);

	/*
	 * Note: We initally set supported to 1, then walk the
	 * subtree rooted at devpath, allowing each node the
	 * opportunity to veto the support. We cannot do things
	 * the other way around i.e. assume "not supported" and
	 * let individual nodes indicate that they are supported.
	 * In the latter case, the supported flag would be set
	 * if any one node in the subtree was supported which is
	 * not what we want.
	 */
	rp->rcm_supp = 1;
	if (di_walk_node(rnode, DI_WALK_CLDFIRST, rp, node_select) != 0) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: retire_supported: "
		    "di_walk_node: failed. Returning NOTSUP\n");
		rp->rcm_supp = 0;
	}

	if (rp->rcm_supp) {
		dp->rt_debug(dp->rt_hdl, "[INFO]: retire IS supported\n");
	}

	return (rp->rcm_supp);
}

static void
rcm_finalize(rcm_arg_t *rp, int retcode)
{
	rio_path_t 	*p;
	rio_path_t 	*tmp;
	int		flags = RCM_RETIRE_NOTIFY;
	int		retval;
	int		error;
	di_retire_t	*dp;

	dp = rp->rcm_dp;

	RIO_ASSERT(dp, retcode == 0 || retcode == -1);

	dp->rt_debug(dp->rt_hdl, "[INFO]: rcm_finalize: retcode=%d: dev=%s\n",
	    retcode, rp->rcm_root);

	for (p = rp->rcm_cons_nodes; p; ) {
		tmp = p;
		p = tmp->rpt_next;
		free(tmp);
	}
	rp->rcm_cons_nodes = NULL;

	dp->rt_debug(dp->rt_hdl, "[INFO]: rcm_finalize: cons_nodes NULL\n");

	for (p = rp->rcm_rsrc_minors; p; ) {
		tmp = p;
		p = tmp->rpt_next;
		if (retcode == 0) {
			retval = rp->rcm_remove(rp->rcm_handle,
			    tmp->rpt_path, flags, NULL);
			error = errno;
		} else {
			RIO_ASSERT(dp, retcode == -1);
			retval = rp->rcm_online(rp->rcm_handle,
			    tmp->rpt_path, flags, NULL);
			error = errno;
		}
		if (retval != RCM_SUCCESS) {
			dp->rt_debug(dp->rt_hdl, "[ERROR]: rcm_finalize: "
			    "rcm_%s: retval=%d: error=%s: path=%s\n",
			    retcode == 0 ? "remove" : "online", retval,
			    strerror(error), tmp->rpt_path);
		} else {
			dp->rt_debug(dp->rt_hdl, "[INFO]: rcm_finalize: "
			    "rcm_%s: SUCCESS: path=%s\n",
			    retcode == 0 ? "remove" : "online", tmp->rpt_path);
		}
		free(tmp);
	}
	rp->rcm_rsrc_minors = NULL;
}
/*ARGSUSED*/
static int
call_offline(di_node_t node, di_minor_t minor, void *arg)
{
	rcm_arg_t	*rp = (rcm_arg_t *)arg;
	di_retire_t	*dp = rp->rcm_dp;
	char		*mnp;
	rio_path_t	*rpt;
	int		retval;

	mnp = di_devfs_minor_path(minor);
	if (mnp == NULL) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: di_devfs_minor_path "
		    "failed. Returning RCM FAILURE: %s\n", rp->rcm_root);
		rp->rcm_retcode = RCM_FAILURE;
		return (DI_WALK_TERMINATE);
	}

	rpt = s_calloc(1, sizeof (rio_path_t), 0);
	if (rpt == NULL) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: calloc failed. "
		    "Returning RCM FAILURE: %s\n", rp->rcm_root);
		di_devfs_path_free(mnp);
		rp->rcm_retcode = RCM_FAILURE;
		return (DI_WALK_TERMINATE);
	}

	(void) snprintf(rpt->rpt_path, sizeof (rpt->rpt_path),
	    "/devices%s", mnp);

	di_devfs_path_free(mnp);

	retval = rp->rcm_offline(rp->rcm_handle, rpt->rpt_path,
	    RCM_RETIRE_REQUEST, NULL);

	rpt->rpt_next = rp->rcm_rsrc_minors;
	rp->rcm_rsrc_minors = rpt;

	if (retval == RCM_FAILURE) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: RCM OFFLINE failed "
		    "for: %s\n", rpt->rpt_path);
		rp->rcm_retcode = RCM_FAILURE;
		return (DI_WALK_TERMINATE);
	} else if (retval == RCM_SUCCESS) {
		rp->rcm_retcode = RCM_SUCCESS;
		dp->rt_debug(dp->rt_hdl, "[INFO]: RCM OFFLINE returned "
		    "RCM_SUCCESS: %s\n", rpt->rpt_path);
	} else if (retval != RCM_NO_CONSTRAINT) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: RCM OFFLINE returned "
		    "invalid value for: %s\n", rpt->rpt_path);
		rp->rcm_retcode = RCM_FAILURE;
		return (DI_WALK_TERMINATE);
	} else {
		dp->rt_debug(dp->rt_hdl, "[INFO]: RCM OFFLINE returned "
		    "RCM_NO_CONSTRAINT: %s\n", rpt->rpt_path);
	}

	return (DI_WALK_CONTINUE);
}

static int
offline_one(di_node_t node, void *arg)
{
	rcm_arg_t 	*rp = (rcm_arg_t *)arg;
	rio_path_t	*rpt;
	di_retire_t	*dp = rp->rcm_dp;
	char		*path;

	/*
	 * We should already have terminated the walk
	 * in case of failure
	 */
	RIO_ASSERT(dp, rp->rcm_retcode == RCM_SUCCESS ||
	    rp->rcm_retcode == RCM_NO_CONSTRAINT);

	dp->rt_debug(dp->rt_hdl, "[INFO]: offline_one: entered\n");

	rp->rcm_retcode = RCM_NO_CONSTRAINT;

	rpt = s_calloc(1, sizeof (rio_path_t), 0);
	if (rpt == NULL) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: rio_path_t calloc "
		    "failed: error: %s\n", strerror(errno));
		goto fail;
	}

	path = di_devfs_path(node);
	if (path == NULL) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: di_devfs_path "
		    "failed: error: %s\n", strerror(errno));
		free(rpt);
		goto fail;
	}

	(void) strlcpy(rpt->rpt_path, path, sizeof (rpt->rpt_path));

	di_devfs_path_free(path);

	if (di_walk_minor(node, NULL, 0, rp, call_offline) != 0) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_minor "
		    "failed: error: %s: %s\n", strerror(errno), path);
		free(rpt);
		goto fail;
	}

	if (rp->rcm_retcode == RCM_FAILURE) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_minor "
		    "returned: RCM_FAILURE: %s\n", rpt->rpt_path);
		free(rpt);
		goto fail;
	} else if (rp->rcm_retcode == RCM_SUCCESS) {
		dp->rt_debug(dp->rt_hdl, "[INFO]: di_walk_minor "
		    "returned: RCM_SUCCESS: %s\n", rpt->rpt_path);
		rpt->rpt_next = rp->rcm_cons_nodes;
		rp->rcm_cons_nodes = rpt;
	} else if (rp->rcm_retcode != RCM_NO_CONSTRAINT) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_minor "
		    "returned: unknown RCM error code: %d, %s\n",
		    rp->rcm_retcode, rpt->rpt_path);
		free(rpt);
		goto fail;
	} else {
		dp->rt_debug(dp->rt_hdl, "[INFO]: di_walk_minor "
		    "returned: RCM_NO_CONSTRAINT: %s\n", rpt->rpt_path);
		free(rpt);
	}

	/*
	 * RCM_SUCCESS or RCM_NO_CONSTRAINT.
	 * RCM_SUCCESS implies we overcame a constraint, so keep walking.
	 * RCM_NO_CONSTRAINT implies no constraints applied via RCM.
	 *	Continue walking in the hope that contracts or LDI will
	 * 	apply constraints
	 * set retcode to RCM_SUCCESS to show that at least 1 node
	 * completely walked
	 */
	rp->rcm_retcode = RCM_SUCCESS;
	return (DI_WALK_CONTINUE);

fail:
	rp->rcm_retcode = RCM_FAILURE;
	return (DI_WALK_TERMINATE);
}

/*
 * Returns:
 *	RCM_SUCCESS:  RCM constraints (if any) were applied. The
 *	device paths for which constraints were applied is passed
 *	back via the pp argument
 *
 *	RCM_FAILURE: Either RCM constraints prevent a retire or
 *	an error occurred
 */
static int
rcm_notify(rcm_arg_t *rp, char **pp, size_t *clen)
{
	size_t	len;
	rio_path_t *p;
	rio_path_t *tmp;
	char *plistp;
	char *s;
	di_retire_t *dp;
	di_node_t rnode;

	dp = rp->rcm_dp;

	dp->rt_debug(dp->rt_hdl, "[INFO]: rcm_notify() entered\n");

	RIO_ASSERT(dp, rp->rcm_root);

	*pp = NULL;

	rnode = rp->rcm_node;
	if (rnode == DI_NODE_NIL) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: devinfo snapshot "
		    "NULL. Returning no RCM constraint: %s\n", rp->rcm_root);
		return (RCM_NO_CONSTRAINT);
	}

	rp->rcm_retcode = RCM_NO_CONSTRAINT;
	rp->rcm_cons_nodes = NULL;
	rp->rcm_rsrc_minors = NULL;
	if (di_walk_node(rnode, DI_WALK_CLDFIRST, rp, offline_one) != 0) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_node "
		    "failed: error: %s: %s\n", strerror(errno), rp->rcm_root);
		/* online is idempotent - safe to online non-offlined nodes */
		rcm_finalize(rp, -1);
		rp->rcm_retcode = RCM_FAILURE;
		goto out;
	}

	if (rp->rcm_retcode == RCM_FAILURE) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: walk_node "
		    "returned retcode of RCM_FAILURE: %s\n", rp->rcm_root);
		rcm_finalize(rp, -1);
		goto out;
	}

	if (rp->rcm_retcode == RCM_NO_CONSTRAINT) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_node "
		    " - no nodes walked: RCM_NO_CONSTRAINT: %s\n",
		    rp->rcm_root);
	} else {
		dp->rt_debug(dp->rt_hdl, "[INFO]: walk_node: RCM_SUCCESS\n");
	}

	/*
	 * Convert to a sequence of NUL separated strings terminated by '\0'\0'
	 */
	for (len = 0, p = rp->rcm_cons_nodes; p; p = p->rpt_next) {
		RIO_ASSERT(dp, p->rpt_path);
		RIO_ASSERT(dp, strlen(p->rpt_path) > 0);
		len += (strlen(p->rpt_path) + 1);
	}
	len++;	/* list terminating '\0' */

	dp->rt_debug(dp->rt_hdl, "[INFO]: len of constraint str = %lu\n", len);

	plistp = s_calloc(1, len, 0);
	if (plistp == NULL) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: fail to alloc "
		    "constraint list: error: %s: %s\n", strerror(errno),
		    rp->rcm_root);
		rcm_finalize(rp, -1);
		rp->rcm_retcode = RCM_FAILURE;
		goto out;
	}

	for (s = plistp, p = rp->rcm_cons_nodes; p; ) {
		tmp = p;
		p = tmp->rpt_next;
		(void) strcpy(s, tmp->rpt_path);
		s += strlen(s) + 1;
		RIO_ASSERT(dp, s - plistp < len);
		free(tmp);
	}
	rp->rcm_cons_nodes = NULL;
	RIO_ASSERT(dp, s - plistp == len - 1);
	*s = '\0';

	dp->rt_debug(dp->rt_hdl, "[INFO]: constraint str = %p\n", plistp);

	*pp = plistp;
	*clen = len;

	rp->rcm_retcode = RCM_SUCCESS;
out:
	return (rp->rcm_retcode);
}


/*ARGSUSED*/
int
di_retire_device(char *devpath, di_retire_t *dp, int flags)
{
	char path[PATH_MAX];
	struct stat sb;
	int retval = EINVAL;
	char *constraint = NULL;
	size_t clen;
	void *librcm_hdl;
	rcm_arg_t rarg = {0};
	int (*librcm_alloc_handle)();
	int (*librcm_free_handle)();

	if (dp == NULL || dp->rt_debug == NULL || dp->rt_hdl == NULL)
		return (EINVAL);

	if (devpath == NULL || devpath[0] == '\0') {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: NULL argument(s)\n");
		return (EINVAL);
	}

	if (devpath[0] != '/' || strlen(devpath) >= PATH_MAX ||
	    strncmp(devpath, "/devices/", strlen("/devices/")) == 0 ||
	    strstr(devpath, "../devices/") || strrchr(devpath, ':')) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: invalid devpath: %s\n",
		    devpath);
		return (EINVAL);
	}

	if (flags != 0) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: flags should be 0: %d\n",
		    flags);
		return (EINVAL);
	}

	/*
	 * dlopen rather than link against librcm since libdevinfo
	 * resides in / and librcm resides in /usr. The dlopen is
	 * safe to do since fmd which invokes the retire code
	 * resides on /usr and will not come here until /usr is
	 * mounted.
	 */
	librcm_hdl = dlopen(LIBRCM_PATH, RTLD_LAZY);
	if (librcm_hdl == NULL) {
		char *errstr = dlerror();
		dp->rt_debug(dp->rt_hdl, "[ERROR]: Cannot dlopen librcm: %s\n",
		    errstr ? errstr : "Unknown error");
		return (ENOSYS);
	}

	librcm_alloc_handle = (int (*)())dlsym(librcm_hdl, "rcm_alloc_handle");
	rarg.rcm_offline = (int (*)())dlsym(librcm_hdl, "rcm_request_offline");
	rarg.rcm_online = (int (*)())dlsym(librcm_hdl, "rcm_notify_online");
	rarg.rcm_remove = (int (*)())dlsym(librcm_hdl, "rcm_notify_remove");
	librcm_free_handle = (int (*)())dlsym(librcm_hdl, "rcm_free_handle");

	if (librcm_alloc_handle == NULL ||
	    rarg.rcm_offline == NULL ||
	    rarg.rcm_online == NULL ||
	    rarg.rcm_remove == NULL ||
	    librcm_free_handle == NULL) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: dlsym failed\n");
		retval = ENOSYS;
		goto out;
	}

	/*
	 * Take a libdevinfo snapshot here because we cannot do so
	 * after device is retired. If device doesn't attach, we retire
	 * anyway i.e. it is not fatal.
	 */
	rarg.rcm_node = di_init(devpath, DINFOCPYALL);
	if (rarg.rcm_node == DI_NODE_NIL) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: device doesn't attach, "
		    "retiring anyway: %s\n", devpath);
	}

	rarg.rcm_handle = NULL;
	if (librcm_alloc_handle(NULL, 0,  NULL, &rarg.rcm_handle)
	    != RCM_SUCCESS) {
		retval = errno;
		dp->rt_debug(dp->rt_hdl, "[ERROR]: failed to alloc "
		    "RCM handle. Returning RCM failure: %s\n", devpath);
		rarg.rcm_handle = NULL;
		goto out;
	}

	rarg.rcm_root = devpath;
	rarg.rcm_dp = dp;

	/*
	 * If device is already detached/nonexistent and cannot be
	 * attached, allow retire without checking device type.
	 * XXX
	 * Else, check if retire is supported for this device type.
	 */
	(void) snprintf(path, sizeof (path), "/devices%s", devpath);
	if (stat(path, &sb) == -1 || !S_ISDIR(sb.st_mode)) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: detached or nonexistent "
		    "device. Bypassing retire_supported: %s\n", devpath);
	} else if (!retire_supported(&rarg)) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: retire not supported for "
		    "device type: %s\n", devpath);
		retval = ENOTSUP;
		goto out;
	}

	clen = 0;
	constraint = NULL;
	retval = rcm_notify(&rarg, &constraint, &clen);
	if (retval == RCM_FAILURE) {
		/* retire not permitted */
		dp->rt_debug(dp->rt_hdl, "[ERROR]: RCM constraints block "
		    "retire: %s\n", devpath);
		retval = EBUSY;
		goto out;
	} else if (retval == RCM_SUCCESS) {
		dp->rt_debug(dp->rt_hdl, "[INFO]: RCM constraints applied"
		    ": %s\n", devpath);
	} else if (retval == RCM_NO_CONSTRAINT) {
		dp->rt_debug(dp->rt_hdl, "[INFO]: No RCM constraints applied"
		    ": %s\n", devpath);
	} else {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: notify returned unknown "
		    "return code: %d: %s\n", retval, devpath);
		retval = ESRCH;
		goto out;
	}

	if (modctl(MODRETIRE, devpath, constraint, clen) != 0) {
		retval = errno;
		dp->rt_debug(dp->rt_hdl, "[ERROR]: retire modctl() failed: "
		    "%s: %s\n", devpath, strerror(retval));
		rcm_finalize(&rarg, -1);
		goto out;
	}

	dp->rt_debug(dp->rt_hdl, "[INFO]: retire modctl() succeeded: %s\n",
	    devpath);

	rcm_finalize(&rarg, 0);

	retval = 0;

out:
	if (rarg.rcm_handle)
		(void) librcm_free_handle(rarg.rcm_handle);

	RIO_ASSERT(dp, rarg.rcm_cons_nodes == NULL);
	RIO_ASSERT(dp, rarg.rcm_rsrc_minors == NULL);

	(void) dlclose(librcm_hdl);

	free(constraint);

	if (rarg.rcm_node != DI_NODE_NIL)
		di_fini(rarg.rcm_node);

	return (retval);
}

/*ARGSUSED*/
int
di_unretire_device(char *devpath, di_retire_t *dp)
{
	if (dp == NULL || dp->rt_debug == NULL || dp->rt_hdl == NULL)
		return (EINVAL);

	if (devpath == NULL || devpath[0] == '\0') {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: NULL devpath\n");
		return (EINVAL);
	}

	if (devpath[0] != '/' || strlen(devpath) >= PATH_MAX ||
	    strncmp(devpath, "/devices/", strlen("/devices/")) == 0 ||
	    strstr(devpath, "../devices/") || strrchr(devpath, ':')) {
		dp->rt_debug(dp->rt_hdl, "[ERROR]: invalid devpath: %s\n",
		    devpath);
		return (EINVAL);
	}

	if (modctl(MODUNRETIRE, devpath) != 0) {
		int err = errno;
		dp->rt_debug(dp->rt_hdl, "[ERROR]: unretire modctl() failed: "
		    "%s: %s\n", devpath, strerror(err));
		return (err);
	}

	dp->rt_debug(dp->rt_hdl, "[INFO]: unretire modctl() done: %s\n",
	    devpath);

	return (0);
}