OpenSolaris_b135/lib/libdscfg/common/cfg_vols.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mkdev.h>
#include <strings.h>
#include <stdarg.h>
#include <stdlib.h>
#include <locale.h>
#include <errno.h>

#include <sys/nsctl/cfg.h>

#include <sys/unistat/spcs_s.h>
#include <sys/unistat/spcs_s_u.h>
#include <sys/unistat/spcs_errors.h>
#include <sys/unistat/spcs_s_impl.h>

#include <sys/nsctl/sv.h>
#include <sys/nsctl/nsc_hash.h>

#define	DEV_EXPAND 32

#define	DO_DISABLE 0
#define	DO_ENABLE 1

/*
 * Utility functions for iiadm and rdcadm/sndradm.
 */

typedef struct hash_data_s {
	union {
		char *users;
		char *mode;
	} u;
	char *path;
	char *node;
	int setno;
} hash_data_t;

typedef struct {
	dev_t	rdev;
	mode_t  mode;
	char	*path;
} device_t;

static hash_data_t *make_svol_data(char *, char *, char *, int);
static hash_data_t *make_dsvol_data(char *, char *, char *, int);
static void delete_svol_data(void *);
static void delete_dsvol_data(void *);
static int sv_action(char *, CFGFILE *, char *, int);

static int add_dev_entry(const char *);
static int compare(const void *, const void *);
static char *find_devid(const char *);
static void free_dev_entries();
static void rebuild_devhash();

static hash_node_t **dsvol;
static int dsvol_loaded = 0;

static hash_node_t **svol;
static int svol_loaded = 0;

static hash_node_t **shadowvol;

static hash_node_t **devhash;
static device_t *devlist;
static int devcount = 0;
static int devalloc = 0;

/*
 * cfg_add_user
 *
 * Description:
 *	Adds the calling tool as a user of the volume.
 *
 * Inputs:
 *	char *path: The pathname of the volume to be enabled.
 *	char *cnode: The device group name, or NULL if -C local or not cluster
 *	CFGFILE *cfg: A pointer to the current config file, or NULL if this
 *		function is to open/write/commit/close the change itself.
 *
 * Return values:
 *	CFG_USER_FIRST: Indicates that this is the first user of this
 *		particular volume.
 *	CFG_USER_OK: Indicates that the volume has already been entered into
 *		the config file.
 *	CFG_USER_ERR: Indicates that some failure has occurred and no changes
 *		to the config file have been made.
 *	CFG_USER_REPEAT: Indicates that this user has already registered for
 *		the volume.
 */
int
cfg_add_user(CFGFILE* cfg, char *path, char *cnode, char *user)
{
	int self_open, self_loaded, change_made;
	char *ctag, search_key[ CFG_MAX_KEY ], buf[ CFG_MAX_BUF ];
	int retval, rc;
	hash_data_t *data;

	self_open = (cfg == NULL);
	self_loaded = 0;
	change_made = 0;

	if (self_open) {
		cfg = cfg_open(NULL);
		if (cfg == NULL) {
			return (CFG_USER_ERR);
		}

		if (!cfg_lock(cfg, CFG_WRLOCK)) {
			/* oops */
			cfg_close(cfg);
			return (CFG_USER_ERR);
		}
	}

	/* Check cnode */
	ctag = cfg_get_resource(cfg);
	if (cnode) {
		if (ctag) {
			if (strcmp(cnode, ctag))
				return (CFG_USER_ERR);
		} else
			cfg_resource(cfg, cnode);
	} else
		cnode = ctag;

	if (!dsvol_loaded) {
		if (cfg_load_dsvols(cfg) < 0) {
			if (self_open) {
				cfg_close(cfg);
			}
			return (CFG_USER_ERR);
		}
		self_loaded = 1;
	}

	/* find the volume */
	(void) snprintf(search_key, CFG_MAX_KEY, "%s:%s", path, cnode);
	data = nsc_lookup(dsvol, search_key);

	if (!data) {
		/* whoops, not found.  Add as new user */
		cfg_rewind(cfg, CFG_SEC_CONF);
		(void) snprintf(buf, CFG_MAX_BUF, "%s %s %s", path, cnode,
		    user);
		rc = cfg_put_cstring(cfg, "dsvol", buf, strlen(buf));
		if (rc < 0) {
			if (self_loaded) {
				cfg_unload_dsvols();
			}
			if (self_open) {
				cfg_close(cfg);
			}
			return (CFG_USER_ERR);
		}
		/* reload hash, if we need to */
		if (!self_loaded) {
			cfg_unload_dsvols();
			if (cfg_load_dsvols(cfg) < 0) {
				if (self_open) {
					cfg_close(cfg);
				}
				return (CFG_USER_ERR);
			}
		}
		retval = CFG_USER_FIRST;
		change_made = 1;
	} else {
		/* Check to ensure we're not already listed */
		char *p = strdup(data->u.users);
		char *q = strtok(p, ",");
		while (q && (strcmp(q, user) != 0)) {
			q = strtok(0, ",");
		}
		free(p);	/* not using data; only testing 'q' ptr */

		if (!q) {
			/* not listed as a user */
			cfg_rewind(cfg, CFG_SEC_CONF);
			(void) snprintf(buf, CFG_MAX_BUF, "%s %s %s,%s",
			    data->path, data->node, data->u.users, user);
			(void) snprintf(search_key, CFG_MAX_KEY, "dsvol.set%d",
			    data->setno);
			if (cfg_put_cstring(cfg, search_key, buf,
			    strlen(buf)) < 0) {
				if (self_loaded) {
					cfg_unload_dsvols();
				}
				if (self_open) {
					cfg_close(cfg);
				}
				return (CFG_USER_ERR);
			}

			/*
			 * Since we deleted an entry from the config
			 * file, we don't know what all the new
			 * set numbers are.  We need to reload
			 * everything
			 */
			if (!self_loaded) {
				cfg_unload_dsvols();
				if (cfg_load_dsvols(cfg) < 0) {
					if (self_open) {
						cfg_close(cfg);
					}
					return (CFG_USER_ERR);
				}
			}
			change_made = 1;
			retval = CFG_USER_OK;
		} else {
			retval = CFG_USER_REPEAT;
		}
	}

	if (self_loaded) {
		cfg_unload_dsvols();
	}

	if (self_open) {
		if (change_made)
			(void) cfg_commit(cfg);
		cfg_close(cfg);
	}

	return (retval);
}

/*
 * cfg_rem_user
 *
 * Description:
 *	Removes a user from the config file.
 *
 * Inputs:
 *	char *path: The pathname of the volume to be enabled.
 *	char *cnode: The device group name, or NULL if -C local or not cluster
 *	char *user: The subsystem that is adding this tag (sv, ii, sndr)
 *	CFGFILE *cfg: A pointer to the current config file, or NULL if this
 *		function is to open/write/commit/close the change itself.
 * Return values:
 *	CFG_USER_ERR: An error occurred during the processing of this
 *		directive.
 *	CFG_USER_OK: User successfully removed; volume in use by other(s).
 *	CFG_USER_LAST: User successfuly removed; no other users registered
 *	CFG_USER_GONE: The volume is no longer listed in the dsvol section,
 *		indicating some sort of application-level error.
 *
 */
int
cfg_rem_user(CFGFILE *cfg, char *path, char *cnode, char *user)
{
	int self_open, self_loaded, change_made;
	char *ctag, search_key[ CFG_MAX_KEY ], buf[ CFG_MAX_BUF ];
	char cfg_key[ CFG_MAX_KEY ];
	hash_data_t *data;
	int retval;
	int force_remove;

	self_open = (cfg == NULL);
	self_loaded = 0;
	change_made = 0;
	force_remove = (strcmp(user, "sv") == 0);

	if ('-' == *user) {
		++user;
	}

	/* Check cnode */
	ctag = cfg_get_resource(cfg);
	if (cnode) {
		if (ctag) {
			if (strcmp(cnode, ctag))
				return (CFG_USER_ERR);
		} else
			cfg_resource(cfg, cnode);
	} else
		cnode = ctag;

	if (self_open) {
		cfg = cfg_open(NULL);
		if (cfg == NULL) {
			return (CFG_USER_ERR);
		}

		if (!cfg_lock(cfg, CFG_WRLOCK)) {
			/* oops */
			cfg_close(cfg);
			return (CFG_USER_ERR);
		}
	}


	change_made = 0;
	if (!dsvol_loaded) {
		if (cfg_load_dsvols(cfg) < 0) {
			if (self_open) {
				cfg_close(cfg);
			}
			return (CFG_USER_ERR);
		}
		self_loaded = 1;
	}

	/* find the volume */
	(void) snprintf(search_key, CFG_MAX_KEY, "%s:%s", path, cnode);
	data = nsc_lookup(dsvol, search_key);

	if (!data) {
		/* yipes */
		retval = CFG_USER_GONE;
	} else if (force_remove) {
		retval = CFG_USER_LAST;
		cfg_rewind(cfg, CFG_SEC_CONF);
		(void) snprintf(cfg_key, CFG_MAX_KEY, "dsvol.set%d",
		    data->setno);
		if (cfg_put_cstring(cfg, cfg_key, NULL, 0) < 0) {
			if (self_loaded) {
				cfg_unload_dsvols();
			}
			if (self_open) {
				cfg_close(cfg);
			}
			return (CFG_USER_ERR);
		}
		if (!self_loaded) {
			cfg_unload_dsvols();
			if (cfg_load_dsvols(cfg) < 0) {
				if (self_open) {
					cfg_close(cfg);
				}
				return (CFG_USER_ERR);
			}
		}
	} else {
		char *p = strdup(data->u.users);
		char *q = strtok(p, ",");
		int appended = 0;

		(void) snprintf(buf, CFG_MAX_BUF, "%s %s ", data->path,
		    data->node);
		while (q && (strcmp(q, user) != 0)) {
			if (appended) {
				strcat(buf, ",");
				strcat(buf, q);
			} else {
				strcat(buf, q);
				appended = 1;
			}
			q = strtok(0, ",");
		}

		if (!q) {
			/* uh-oh */
			retval = CFG_USER_GONE;
		} else {
			/* old user skipped; add in remaining users */
			while (q = strtok(0, ", ")) {
				if (appended) {
					strcat(buf, ",");
					strcat(buf, q);
				} else {
					strcat(buf, q);
					appended = 1;
				}
			}

			if (appended) {
				retval = CFG_USER_OK;
				cfg_rewind(cfg, CFG_SEC_CONF);
				(void) snprintf(cfg_key, CFG_MAX_KEY,
				    "dsvol.set%d", data->setno);
				if (cfg_put_cstring(cfg, cfg_key, buf,
				    strlen(buf)) < 0) {
					if (self_loaded) {
						cfg_unload_dsvols();
					}
					if (self_open) {
						cfg_close(cfg);
					}
					return (CFG_USER_ERR);
				}
				if (!self_loaded) {
					cfg_unload_dsvols();
					if (cfg_load_dsvols(cfg) < 0) {
						if (self_open) {
							cfg_close(cfg);
						}
						return (CFG_USER_ERR);
					}
				}
			} else {
				retval = CFG_USER_LAST;
				cfg_rewind(cfg, CFG_SEC_CONF);
				(void) snprintf(cfg_key, CFG_MAX_KEY,
				    "dsvol.set%d", data->setno);
				if (cfg_put_cstring(cfg, cfg_key, NULL,
				    0) < 0) {
					if (self_loaded) {
						cfg_unload_dsvols();
					}
					if (self_open) {
						cfg_close(cfg);
					}
					return (CFG_USER_ERR);
				}
				/*
				 * Since we deleted an entry from the config
				 * file, we don't know what all the new
				 * set numbers are.  We need to reload
				 * everything
				 */
				if (!self_loaded) {
					cfg_unload_dsvols();
					if (cfg_load_dsvols(cfg) < 0) {
						if (self_open) {
							cfg_close(cfg);
						}
						return (CFG_USER_ERR);
					}
				}
			}
			change_made = 1;
		}
	}

	if (self_loaded) {
		cfg_unload_dsvols();
	}

	if (self_open) {
		if (change_made)
			(void) cfg_commit(cfg);
		cfg_close(cfg);
	}

	return (retval);
}

/*
 * Enable a volume under SV control (or add this char *user to the list
 * of users of that volume).
 *
 * Parameters:
 *	cfg	- The config file to use.
 *	path	- The pathname of the volume
 *	ctag	- The cluster tag for this volume (if any)
 *	user	- The user (sv, ii, sndr) of the volume.
 */
int
cfg_vol_enable(CFGFILE *cfg, char *path, char *ctag, char *user)
{
	int rc;
	int retval;

	if (!ctag || *ctag == '\0') {
		ctag = "-";
	}

	retval = -1;
	rc = cfg_add_user(cfg, path, ctag, user);
	switch (rc) {
	case CFG_USER_ERR:
		spcs_log("dsvol", NULL,
		    gettext("unable to set up dsvol section of config for %s"),
		    path);
		break;
	case CFG_USER_OK:
		retval = 0;
		break;
	case CFG_USER_FIRST:
		/* enable sv! */
		retval = sv_action(path, cfg, ctag, DO_ENABLE);
		if (retval < 0) {
			(void) cfg_rem_user(cfg, path, ctag, user);
		}
		break;
	default:
		spcs_log("dsvol", NULL,
		    gettext("unexpected return from cfg_add_user(%d)"), rc);
		break;
	}

	return (retval);
}

/*
 * Disable a volume from SV control (or remove this char *user from the list
 * of users of that volume).
 *
 * Parameters:
 *	cfg	- The config file to use.
 *	path	- The pathname of the volume
 *	ctag	- The cluster tag for this volume (if any)
 *	user	- The user (sv, ii, sndr) of the volume.
 */
int
cfg_vol_disable(CFGFILE *cfg, char *path, char *ctag, char *user)
{
	int rc;
	int retval;

	if (!ctag || *ctag == '\0') {
		ctag = "-";
	}

	retval = -1;
	rc = cfg_rem_user(cfg, path, ctag, user);
	switch (rc) {
	case CFG_USER_ERR:
		spcs_log("dsvol", NULL,
		    gettext("unable to set up dsvol section of config for %s"),
		    path);
		break;
	case CFG_USER_OK:
		retval = 0;
		break;
	case CFG_USER_GONE:
		spcs_log("dsvol", NULL,
		    gettext("%s tried to remove non-existent tag for %s"),
		    user, path);
		break;
	case CFG_USER_LAST:
		/* diable sv! */
		retval = sv_action(path, cfg, ctag, DO_DISABLE);
		break;
	default:
		spcs_log("dsvol", NULL,
		    gettext("unexpected return from cfg_rem_user(%d)"), rc);
		break;
	}

	return (retval);
}

/*
 * cfg_load_dsvols
 *
 * Description:
 *	Loads the dsvol section of the config file into a giant hash, to
 *	make searching faster.  The important bit to remember is to not
 *	release the write lock between calling cfg_load_dsvols() and the
 *	cfg_*_user() functions.
 *
 * Assumptions:
 *	1/ cfg file is open
 *	2/ cfg file has been write-locked
 *	3/ user of this routine may already be using hcreate/hsearch
 *
 * Return value:
 *	-1 if error, or total number of sets found
 */
int
cfg_load_dsvols(CFGFILE *cfg)
{
	int set, rc, entries;
	char search_key[ CFG_MAX_KEY ];
	char *buf;
	char **entry, *path, *cnode, *users;
	hash_data_t *data;
	int devs_added = 0;
	int offset = 0;
	char *ctag = cfg_get_resource(cfg);
	if (!ctag || *ctag == '\0') {
		ctag = "-";
	}

	dsvol = nsc_create_hash();
	if (!dsvol) {
		return (-1);
	}

	rc = 0;
	cfg_rewind(cfg, CFG_SEC_CONF);
	entries = cfg_get_section(cfg, &entry, "dsvol");
	for (set = 1; set <= entries; set++) {
		buf = entry[set - 1];

		/* split up the line */
		if (!(path = strtok(buf, " "))) {
			/* oops, now what? */
			free(buf);
			break;
		}
		if (!(cnode = strtok(0, " "))) {
			free(buf);
			break;
		}
		if (ctag && (strcmp(cnode, ctag) != 0)) {
			++offset;
			free(buf);
			continue;
		}

		if (!(users = strtok(0, " "))) {
			free(buf);
			break;
		}

		data = make_dsvol_data(path, cnode, users, set - offset);
		if (!data) {
			free(buf);
			break;
		}
		(void) snprintf(search_key, CFG_MAX_KEY, "%s:%s", path, cnode);
		rc = nsc_insert_node(dsvol, data, search_key);
		if (rc < 0) {
			free(buf);
			break;
		}

		/* we also need to keep track of node information */
		rc = add_dev_entry(path);
		if (rc < 0) {
			free(buf);
			break;
		} else if (rc)
			++devs_added;

		free(buf);
		rc = 0;
	}

	while (set < entries)
		free(entry[set++]);
	if (entries)
		free(entry);

	if (devs_added) {
		qsort(devlist, devcount, sizeof (device_t), compare);
		rebuild_devhash();
	}

	dsvol_loaded = 1;
	return (rc < 0? rc : entries);
}

/*
 * cfg_unload_dsvols
 *
 * Description:
 *	Free all memory allocated with cfg_load_dsvols.
 */
void
cfg_unload_dsvols()
{
	if (dsvol) {
		nsc_remove_all(dsvol, delete_dsvol_data);
		dsvol = 0;
		dsvol_loaded = 0;
	}
}

/*
 * cfg_load_svols
 *
 * Description:
 *	Loads the sv section of the config file into a giant hash, to make
 *	searching faster.  The important bit to remember is to not release
 *	the write lock between calling cfg_load_svols() and the cfg_*_user()
 *	functions.
 *
 * Assumptions:
 *	1/ cfg file is open
 *	2/ cfg file has been write-locked
 *	3/ user of this routine may already be using builtin hcreate/hsearch
 */
int
cfg_load_svols(CFGFILE *cfg)
{
	int set, entries, offset = 0;
	char *buf, **entry;
	char *path, *mode, *cnode;
	hash_data_t *data;
	char *ctag = cfg_get_resource(cfg);
	if (!ctag || *ctag == '\0') {
		ctag = "-";
	}

	svol = nsc_create_hash();
	if (!svol) {
		return (-1);
	}

	cfg_rewind(cfg, CFG_SEC_CONF);
	entries = cfg_get_section(cfg, &entry, "sv");
	for (set = 1; set <= entries; set++) {
		buf = entry[set - 1];

		/* split up the line */
		if (!(path = strtok(buf, " "))) {
			free(buf);
			break;
		}
		if (!(mode = strtok(0, " "))) {
			free(buf);
			break;
		}
		if (!(cnode = strtok(0, " "))) {
			cnode = "";
		}

		if (ctag && (strcmp(cnode, ctag) != 0)) {
			++offset;
			free(buf);
			continue;
		}

		data = make_svol_data(path, mode, cnode, set - offset);
		if (!data) {
			free(buf);
			break;
		}
		if (nsc_insert_node(svol, data, path) < 0) {
			free(buf);
			break;
		}
		free(buf);
	}
	while (set < entries)
		free(entry[set++]);
	if (entries)
		free(entry);

	svol_loaded = 1;
	return (0);
}

/*
 * cfg_unload_svols
 *
 * Description:
 *	Frees all memory allocated with cfg_load_dsvols
 */
void
cfg_unload_svols()
{
	if (svol) {
		nsc_remove_all(svol, delete_svol_data);
		svol = 0;
		svol_loaded = 0;
	}
}

/*
 * cfg_get_canonical_name
 *
 * Description:
 *	Find out whether a device is already known by another name in
 *	the config file.
 *
 * Parameters:
 *	cfg - The config file to use
 *	path - The pathname of the device
 *	result - (output) The name it is otherwise known as.  This parameter
 *			must be freed by the caller.
 *
 * Return values:
 *	-1: error
 *	0: name is as expected, or is not known
 *	1: Name is known by different name (stored in 'result')
 */
int
cfg_get_canonical_name(CFGFILE *cfg, const char *path, char **result)
{
	int self_loaded;
	char *alt_path;
	int retval;

	if (devlist) {
		self_loaded = 0;
	} else {
		if (cfg_load_shadows(cfg) < 0) {
			return (-1);
		}
		self_loaded = 1;
	}

	/* see if it exists under a different name */
	alt_path = find_devid(path);
	if (!alt_path || strcmp(path, alt_path) == 0) {
		*result = NULL;
		retval = 0;
	} else {
		/* a-ha */
		*result = strdup(alt_path);
		retval = 1;
	}

	if (self_loaded) {
		free_dev_entries();
	}

	return (retval);
}

/*
 * cfg_load_shadows
 *
 * Description:
 *	Load in shadow and bitmap volumes from the II section of the
 *	config file.  SNDR's volumes are handled already by cfg_load_dsvols.
 *	Not all shadow volumes are listed under dsvol: they can be exported.
 *
 * Parameters:
 *	cfg - The config file to use
 *
 * Return values:
 *	-1: error
 *	0: success
 */
int
cfg_load_shadows(CFGFILE *cfg)
{
	int set, self_loaded, rc, entries;
	char *buf, **entry, *ptr;
	int devs_added = 0;

	if (dsvol_loaded) {
		self_loaded = 0;
	} else {
		if (cfg_load_dsvols(cfg) < 0) {
			return (-1);
		}
		self_loaded = 1;
	}

	shadowvol = nsc_create_hash();
	if (!shadowvol) {
		return (-1);
	}

	rc = 0;
	cfg_rewind(cfg, CFG_SEC_CONF);
	entries = cfg_get_section(cfg, &entry, "ii");
	for (set = 1; set <= entries; set++) {
		buf = entry[set - 1];

		/* skip the master vol */
		ptr = strtok(buf, " ");

		/* shadow is next */
		ptr = strtok(NULL, " ");

		rc = add_dev_entry(ptr);
		if (rc < 0) {
			free(buf);
			break;
		} else if (rc)
			++devs_added;

		/* and next is bitmap */
		ptr = strtok(NULL, " ");

		rc = add_dev_entry(ptr);
		if (rc < 0) {
			free(buf);
			break;
		} else if (rc)
			++devs_added;
		rc = 0;
		free(buf);
	}
	while (set < entries)
		free(entry[set++]);
	if (entries)
		free(entry);

	if (self_loaded) {
		cfg_unload_dsvols();
	}

	if (devs_added) {
		/* sort it, in preparation for lookups */
		qsort(devlist, devcount, sizeof (device_t), compare);
		rebuild_devhash();
	}

	return (rc);
}

void
cfg_unload_shadows()
{
	/* do nothing */
}

/* ---------------------------------------------------------------------- */

static hash_data_t *
make_dsvol_data(char *path, char *cnode, char *users, int set)
{
	hash_data_t *data;

	data = (hash_data_t *)malloc(sizeof (hash_data_t));
	if (!data) {
		return (0);
	}

	data->u.users = strdup(users);
	data->path = strdup(path);
	data->node = strdup(cnode);
	data->setno = set;

	return (data);
}

static void
delete_dsvol_data(void *data)
{
	hash_data_t *p = (hash_data_t *)data;

	free(p->u.users);
	free(p->path);
	free(p->node);
	free(p);
}

static hash_data_t *
make_svol_data(char *path, char *mode, char *cnode, int set)
{
	hash_data_t *data;

	data = (hash_data_t *)malloc(sizeof (hash_data_t));
	if (!data) {
		return (0);
	}

	data->u.mode = strdup(mode);
	data->path = strdup(path);
	data->node = strdup(cnode);
	data->setno = set;

	return (data);
}


static void
delete_svol_data(void *data)
{
	hash_data_t *p = (hash_data_t *)data;

	free(p->u.mode);
	free(p->path);
	free(p->node);
	free(p);
}

static int
sv_action(char *path, CFGFILE *caller_cfg, char *ctag, int enable)
{
	struct stat stb;
	sv_conf_t svc;
	int fd = -1;
	int cfg_changed = 0;
	CFGFILE *cfg;
	int print_log = 0;
	int err = 0, rc;
	int sv_ioctl, spcs_err, self_loaded;
	char *log_str1, *log_str2;
	char key[ CFG_MAX_KEY ];
	char buf[ CFG_MAX_BUF ];
	hash_data_t *node;
	device_t *statinfo = 0;

	if (caller_cfg == NULL) {
		cfg = cfg_open(NULL);
		if (cfg == NULL)
			return (-1);

		if (ctag)
			cfg_resource(cfg, ctag);
	} else
		cfg = caller_cfg;


	self_loaded = 0;
	sv_ioctl = (enable? SVIOC_ENABLE : SVIOC_DISABLE);
	log_str1 = (enable? gettext("enabled %s") : gettext("disabled %s"));
	log_str2 = (enable? gettext("unable to enable %s") :
	    gettext("unable to disable %s"));
	spcs_err = (enable? SV_EENABLED : SV_EDISABLED);
	bzero(&svc, sizeof (svc));

	if (devhash)
		statinfo = nsc_lookup(devhash, path);

	if (statinfo) {
		if (!S_ISCHR(statinfo->mode))
			goto error;
		svc.svc_major = major(statinfo->rdev);
		svc.svc_minor = minor(statinfo->rdev);
	} else {
		if (stat(path, &stb) != 0)
			goto error;

		if (!S_ISCHR(stb.st_mode))
			goto error;
		svc.svc_major = major(stb.st_rdev);
		svc.svc_minor = minor(stb.st_rdev);
	}

	strncpy(svc.svc_path, path, sizeof (svc.svc_path));

	fd = open(SV_DEVICE, O_RDONLY);
	if (fd < 0)
		goto error;

	svc.svc_flag = (NSC_DEVICE | NSC_CACHE);
	svc.svc_error = spcs_s_ucreate();

	do {
		rc = ioctl(fd, sv_ioctl, &svc);
	} while (rc < 0 && errno == EINTR);

	if (rc < 0) {
		if (errno != spcs_err) {
			spcs_log("sv", &svc.svc_error, log_str2, svc.svc_path);
			if (enable)
				goto error;
			else
				err = errno;
		} else
			err = spcs_err;
	}

	spcs_log("sv", NULL, log_str1, svc.svc_path);

	/* SV enable succeeded */
	if (caller_cfg == NULL)	 /* was not previously locked */
		if (!cfg_lock(cfg, CFG_WRLOCK))
			goto error;

	if (err != spcs_err) { /* already enabled, already in config */
		if (enable) {
			cfg_rewind(cfg, CFG_SEC_CONF);
			(void) snprintf(buf, CFG_MAX_BUF, "%s - %s", path,
			    ctag? ctag : "-");
			if (cfg_put_cstring(cfg, "sv", buf, CFG_MAX_BUF) < 0) {
				/* SV config not updated, so SV disable again */
				(void) ioctl(fd, SVIOC_DISABLE, &svc);
				print_log++;
			} else
				cfg_changed = 1;
		} else {
			/* pull it out of the config */
			if (!svol_loaded) {
				if (cfg_load_svols(cfg) < 0) {
					if (NULL == caller_cfg) {
						cfg_close(cfg);
					}
					return (-1);
				}
				self_loaded = 1;
			}
			node = nsc_lookup(svol, svc.svc_path);
			if (node) {
				cfg_rewind(cfg, CFG_SEC_CONF);
				(void) snprintf(key, CFG_MAX_KEY, "sv.set%d",
				    node->setno);
				if (cfg_put_cstring(cfg, key, NULL, NULL) < 0) {
					spcs_log("sv", NULL,
					    gettext("failed to remove %s from "
					    "sv config"), svc.svc_path);
				}
				/*
				 * Since we deleted an entry from the config
				 * file, we don't know what all the new
				 * set numbers are.  We need to reload
				 * everything
				 */
				if (!self_loaded) {
					cfg_unload_svols();
					if (cfg_load_svols(cfg) < 0) {
						if (NULL == caller_cfg) {
							cfg_close(cfg);
						}
						return (-1);
					}
				}
				cfg_changed = 1;
			}
			if (self_loaded) {
				cfg_unload_svols();
				self_loaded = 0;
			}
		}
	}

#ifdef lint
	(void) printf("extra line to shut lint up %s\n", module_names[0]);
#endif

error:
	if (fd >= 0)
		(void) close(fd);

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

	if (cfg_changed)
		if (caller_cfg == NULL) /* we opened config */
			(void) cfg_commit(cfg);

	if (caller_cfg == NULL)
		cfg_close(cfg);
	if ((cfg_changed) || (err == spcs_err))
		return (1);
	if (print_log)
		spcs_log("sv", NULL,
			gettext("unable to add to configuration, disabled %s"),
			svc.svc_path);
	spcs_s_ufree(&svc.svc_error);

	return (-1);
}

/*
 * add_dev_entry
 *
 * Add an entry into the devlist and the devhash for future lookups.
 *
 * Return values:
 *  -1  An error occurred.
 *   0  Entry added
 *   1  Entry already exists.
 */
static int
add_dev_entry(const char *path)
{
	struct stat buf;
	device_t *newmem;
	hash_data_t *data;

	if (!devhash) {
		devhash = nsc_create_hash();
		if (!devhash) {
			return (-1);
		}
	} else {
		data = nsc_lookup(devhash, path);
		if (data) {
			return (1);
		}
	}

	if (stat(path, &buf) < 0) {
		/* ignore error, we are most likely deleting entry anyway */
		buf.st_rdev = 0;
	}

	if (devcount >= devalloc) {
		/* make some room */
		devalloc += DEV_EXPAND;
		newmem = (device_t *)realloc(devlist, devalloc *
		    sizeof (device_t));
		if (!newmem) {
			free_dev_entries();
			return (-1);
		} else {
			devlist = newmem;
		}
	}

	devlist[ devcount ].path = strdup(path);
	devlist[ devcount ].rdev = buf.st_rdev;
	devlist[ devcount ].mode = buf.st_mode;

	if (nsc_insert_node(devhash, &devlist[devcount], path) < 0) {
		return (-1);
	}

	++devcount;
	return (0);
}

static void
rebuild_devhash()
{
	int i;

	if (!devhash)
		nsc_remove_all(devhash, 0);

	devhash = nsc_create_hash();
	if (!devhash)
		return;

	for (i = 0; i < devcount; i++) {
		nsc_insert_node(devhash, &devlist[i], devlist[i].path);
	}
}

static int
compare(const void *va, const void *vb)
{
	device_t *a = (device_t *)va;
	device_t *b = (device_t *)vb;

	return (b->rdev - a->rdev);
}

static char *
find_devid(const char *path)
{
	device_t key;
	device_t *result;
	struct stat buf;

	if (!devlist || !devhash)
		return (NULL);

	/* See if we already know the device id by this name */
	result = (device_t *)nsc_lookup(devhash, path);
	if (result) {
		return (NULL);
	}

	/* try to find it by another name */
	if (stat(path, &buf) < 0)
		return (NULL);

	key.rdev = buf.st_rdev;

	/* it's storted, so we use the binary-chop method to find it */
	result = bsearch(&key, devlist, devcount, sizeof (device_t), compare);

	if (result) {
		return (result->path);
	}

	return (NULL);
}

static void
free_dev_entries()
{
	int i;
	device_t *p;

	if (!devlist) {
		return;
	}
	for (i = 0, p = devlist; i < devcount; i++, p++) {
		free(p->path);
	}
	free(devlist);
	devlist = NULL;
	devcount = 0;
	devalloc = 0;

	if (devhash) {
		nsc_remove_all(devhash, 0);
		devhash = NULL;
	}
}